[4章] CDK for Terraformで、AWS Fargateの環境構築 [後編]

Yusuke Yasuoka
nextbeat-engineering
36 min readNov 22, 2022

1. はじめに

こんにちは、株式会社ネクストビートに新卒入社した安岡と申します。

本記事は、会社の先輩である富永さんが以前書かれていた記事の続きになります。記事の題材は「AWSにECS on Fargateを構築し、そこにPlay Frameworkで作成したアプリケーションをデプロイする。」というものです。

私は普段はフロントエンド、バックエンドの開発を担当しており、インフラやアプリケーションの細かい設定に触れる機会が少ないです。それらの技術に触れてみたいと思っていたところ、富永さんが執筆した記事を見つけました。富永さんの記事の内容は私が興味を持っていたものでしたが、途中までしか公開されていませんでした。そこで、記事の続きを執筆してみたいと富永さんにお願いしたところ、快く承諾していただけました。

これまでに富永さんが公開した記事を以下に載せておきます。まだ、これらの記事を読んだことがない方は先に読むことをおすすめします。

目次

  1. はじめに
  2. 概要
  3. ECRの構築
  4. S3の構築
  5. Lambdaの構築
  6. SNSの構築
  7. EventBridgeの構築
  8. ALBの構築
  9. ECSの構築
  10. Play Frameworkの修正
  11. CDK for Terraformのデプロイ・デストロイ
  12. おわりに

2. 概要

本記事、4章後半では、AWSのアプリケーションレイヤーに関わる部分の構築を行います。

これまでの章でAWSのマネジメントコンソールから手動でAWSサービスを作成しました。まずは、これらをCDK for Terraformで構築します。具体的なサービスは以下です。

・ECR
・S3
・Lambda
・SNS
・EventBridge

サービス名は重複できないため、これまでの章で作成したAWSサービスは削除しておきましょう。

次に、以下の新規サービスを構築します。

・ALB
・ECS

構成図

今回作成するCDK for Terraformのコードはこちら、Play Frameworkアプリケーションのコードはこちらから確認できます。

3. ECRの構築

まずはECRを構築します。

4章前半で作成したsample-cdktfディレクトリのmain.tsファイルにコードを追記します。

以下のように.genに置いてあるprovidersからawsを指定して、EcrRepositoryパッケージをインポートします。

import { ..., EcrRepository } from './.gen/providers/aws';

インポート後、以下のようにEcrRepositoryの設定を記述します。

const ecrRepository = new EcrRepository(this, 'sample-cdktf-repository', {
name: 'project/sample-ecr'
});

再度になりますが、サービス名は重複できないため、これまでの章で作成したAWSサービスは削除しておきましょう。

これで、ECRの構築は完了です。

4. S3の構築

次は、S3を構築します。

まずは、S3のバケットを作成するためにS3Bucketパッケージをインポートします。

import { ..., S3Bucket } from './.gen/providers/aws';

バケットの作成は、以下のようにバケット名とリージョンを設定するだけで作成できます。

const s3Bucket = new S3Bucket(this, 'sample-cdktf-s3', {
bucket: 'sample-cdktf-s3',
region: 'ap-northeast-1'
});

次に、3章で作成した、Lambdaで実行されるアプリをS3オブジェクトとして保存します。

まず、notification-to-Slackディレクトリを同リポジトリ内に配置してください。配置が終わったら以下の手順でS3オブジェクトを作成します。

S3のオブジェクトは、S3BucketObjectパッケージをインポートして作成します。

import { ..., S3BucketObject } from './.gen/providers/aws';

パスから該当のファイルを指定するために、以下をインポートします。

import * as path from 'path';

S3のオブジェクトを以下のように、zip形式で作成します。(sourceに指定するパスはnotification-to-Slackリポジトリを配置したパスによって書き換えてください)

const s3BucketObject = new S3BucketObject(this, 'notification-to-Slack-dist.zip', {
bucket: s3Bucket.bucket,
key: 'notification-to-Slack-dist.zip',
contentType: 'zip',
source: path.resolve('./notification-to-Slack/notification-to-Slack-dist/notification-to-Slack-dist.zip')
});

これで、S3の構築は完了です。

5. Lambdaの構築

次は、Lambdaを構築します。

5.1 Lambda用の実行ロールの構築

まずは、Lambda用の実行ロールを作成します。

ロールを作成するためのIamRoleパッケージをインポートします。

import { ..., IamRole } from './.gen/providers/aws';

Lambdaの権限と、後々使用するのECSタスクの権限を付与しています。

const lambdaExecutionRole = new IamRole(this , 'lambdaExecutionRole',{
name: 'sample-cdktf-lambdaExecutionRole',
assumeRolePolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": [
"lambda.amazonaws.com",
"ecs-tasks.amazonaws.com"
]
},
"Effect": "Allow",
"Sid": ""
}
]
}`
});

次は、先ほど作成したロール用にポリシーを作成します。
ポリシーを作成するためのIamPolicyパッケージをインポートします。

import { ..., IamPolicy } from './.gen/providers/aws';

以下のように、ロールで使用したい機能をActionの中に追記します。
ECS関連とCloudWatch用の権限を付与しています。

const lambdaExecutionIamPolicy = new IamPolicy(this, 'lambda-logging', {
name: 'sample-cdktf-lambda-logging',
description: 'IAM policy for logging from a lambda',
policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:PassRole",
"ecs:RegisterTaskDefinition",
"ecs:UpdateService",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*",
"arn:aws:ecs:*:*:*",
"${ecsTaskExecutionRole.arn}"
],
"Effect": "Allow"
}
]
}`,
dependsOn: [ecsTaskExecutionRole]
});

Resourceで使用されているecsTaskExecutionRoleは後で作成します。

dependsOnに特定のサービスを指定すると依存関係を記述でき、このサービスが作成されるのが依存先のサービスの作成が終わった後になります。ecsTaskExecutionRole.arnを参照するためecsTaskExecutionRoleののちに作成します。

次は、ロールとポリシーを紐付けるためのIamRolePolicyAttachmentの設定を追記します。

import { ..., IamRolePolicyAttachment } from './.gen/providers/aws';

作成したロールとポリシーを紐付けて、Lambda用のロール設定は完了です。

new IamRolePolicyAttachment(this, 'attach-lambda-policy', {
role: lambdaExecutionRole.name,
policyArn: lambdaExecutionIamPolicy.arn
});

5.2 Lambda本体の構築

ロールの作成が完了したので、Lambdaの作成に入ります。
まずは、LambdaFunctionパッケージをインポートします。

import { ..., LambdaFunction } from './.gen/providers/aws';

Lambdaの設定は、3章で作成したものと同じ内容となっています。

const notificationToSlack = new LambdaFunction(this, 'Sample-Lambda-Notification-to-Slack', {
functionName: 'Sample-Lambda-Notification-to-Slack',
handler: 'index.handler',
role: lambdaExecutionRole.arn,
runtime: 'nodejs12.x',
s3Bucket: s3Bucket.bucket,
s3Key: s3BucketObject.key,
timeout: 30,
environment: [{
variables: {
['SLACK_API_TOKEN']: 'xoxb-xxxxxxxxxxxxxxxxxxxxxx',
['SLACK_CHANNEL']: 'xxxxxxxxxxxxx'
}
}]
});

次は、作成したLambdaでログ出力できるようにCloudWatchの設定を行います。
CloudwatchLogGroupパッケージをインポートしてロググループを作成します。

import { ..., CloudwatchLogGroup } from './.gen/providers/aws';

名前は、対象のLambda関数名を含んだものに設定します。

new CloudwatchLogGroup(this, 'sample-cdktf-notification-to-slack-log-group', {
name: `/aws/lambda/${notificationToSlack.functionName}`
});

これで、Lambdaの構築は完了です。

6. SNSの構築

次は、SNSを構築します。
SNSに関しても3章で作成したものと同じになります。

まずは、SNSトピックを作成するためにSnsTopicパッケージをインポートします。

import { ..., SnsTopic } from './.gen/providers/aws';

SNSトピックは、名前の設定だけで作成できます。

const snsTopic = new SnsTopic(this, 'sample-cdktf-sns', {
name: 'sample-cdktf-sns'
});

次は、サブスクリプションを作成します。
SnsTopicSubscriptionパッケージをインポートして設定を行います。

import { ..., SnsTopicSubscription } from './.gen/providers/aws';

以下のようにLambda用のサブスクリプションの設定を行います。

new SnsTopicSubscription(this, 'sample-cdktf-sns-subscription', {
endpoint: notificationToSlack.arn,
protocol: 'lambda',
topicArn: snsTopic.arn
});

次は、SNS用のポリシーをSnsTopicPolicyを使用して作成します。

import { ..., SnsTopicPolicy } from './.gen/providers/aws';

AWS EventBridgeを使用できるように、設定を行います。

new SnsTopicPolicy(this, 'sample-cdktf-sns-policy', {
arn: snsTopic.arn,
policy: `{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Sid": "",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": [
"SNS:Publish"
],
"Resource": [
"*"
]
}
}`
});

最後に、先ほど作成したLambdaとSNSの紐付けを行います。
紐付けは、LambdaPermissionを使用します。

import { ..., LambdaPermission } from './.gen/providers/aws';

以下設定を追記します。

new LambdaPermission(this, 'permission-to-sns', {
action: 'lambda:InvokeFunction',
functionName: notificationToSlack.functionName,
principal: 'sns.amazonaws.com',
sourceArn: snsTopic.arn,
statementId: 'AllowExecutionFromSNS'
});

これで、SNSの構築は完了です。

7. EventBridgeの構築

次は、EventBridgeを構築します。EventBridgeはCDK for TerraformではCloudwatchEventRuleとCloudwatchEventTargetを用いて構築します。名前が結構違いますね。

まず、CloudwatchEventRuleを使用してCloudWatchのイベントルールを作成します。

import { ..., CloudwatchEventRule } from './.gen/providers/aws';

ECRの更新をトリガーとしています。

new CloudwatchEventRule(this, 'sample-cdktf-event-rule', {
name: 'capture-ecr-update',
description: 'Capture each AWS ECR Update',
eventPattern: `{
"source": ["aws.ecr"],
"detail-type": ["ECR Image Action"],
"detail": {
"action-type": ["PUSH"],
"result": ["SUCCESS"]
}
}`
});

次は、CloudwatchEventTargetを使用してCloudWatchのターゲット設定を行います。

import { ..., CloudwatchEventTarget } from './.gen/providers/aws';

以下のように、SNSをターゲットとします。

new CloudwatchEventTarget(this, 'sample-cdktf-event-target', {
arn: snsTopic.arn,
rule: eventRule.name,
targetId: 'SendToSNS'
});

これで、EventBridgeの構築は完了です。

8. ALBの構築

次に、ALBを構築します。
ALBについて知りたい場合は富永さんが以前出されていた記事[4]の「ELBの設定」を確認するとよいと思います。
また、ここではALBのコンポーネントとしてターゲットグループ、リスナー、リスナールールが出てきます。これらの関係は以下のようになっています。

リスナーで定義したポートでリクエストを受け付け、パスなどの一定のルールに基づき、 ロードバランサの背後にいるターゲットにリクエストを転送します。[6]

これらを図として表したものが図8.1です。

ALBのコンポーネントの関係性[9]

それでは、Albパッケージをインポートします。

import { ..., Alb } from './.gen/providers/aws';

インポート後、以下のようにALBの設定を追記します。

const alb = new Alb(this, 'sample-cdktf-alb', {
name: 'sample-cdktf-alb',
internal: false,
loadBalancerType: 'application',
securityGroups: [Token.asString(security.id), Token.asString(vpc.defaultSecurityGroupId)],
subnets: [Token.asString(publicSubnet1.id), Token.asString(publicSubnet2.id)],
ipAddressType: 'ipv4',
enableDeletionProtection: false
});

Internal
ALB が「インターネット向け」なのか「VPC 内部向けか」なのかを指定します。インター ネット向けの場合は、internal を false にします。[5]

internalにはfalseを指定することでインターネットと接続させます。

enableDeletionProtectionは削除保護の設定です。これをfalseにしないと、CDK for TerraformからAWSサービスを一斉に削除しようとしても、永遠に終わりません。enableDeletionProtectionが有効だとALBが削除されていないのに、ゲートウェイを削除しようとすることが原因のようです[6]。

次は、ALBのターゲットグループを作成します。

AlbTargetGroupパッケージをインポートします。

import { ..., AlbTargetGroup } from './.gen/providers/aws';

そして、以下のようにAlbTargetGroupの設定を追記します。

const albTargetGroup = new AlbTargetGroup(scope, 'sample-cdktf-alb-target-group', {
name: 'sample-cdktf-alb-target-group',
port: 80,
protocol: 'HTTP',
targetType: 'ip',
vpcId: Token.asString(vpc.id),
healthCheck: [{
interval: 30,
path: '/',
port: 'traffic-port',
protocol: 'HTTP',
timeout: 5,
unhealthyThreshold: 2
}],
dependsOn: [alb]
});

healthCheckではヘルスチェックの設定をします。
ヘルスチェックは配下のEC2やECSと正常に通信できるか確認する機能です。
healthCheckに指定する項目は以下のようになっています[5]。
・path: ヘルスチェック時にリクエストされるパス
・port: ヘルスチェックで使用するポート(traffic-portを指定した場合albTargetGroupに指定したportが使用される。ここでは3行目のportが使用される)
・unhealthThreshold: 異常判定を行うまでのヘルスチェック実行回数

LambdaのIamPolicy作成時に使用したdependsOnをここでも使用しています。ALBとターゲットグループをECSサービスと同時に作成するとエラーになる[5]ため、albを依存先として指定します。

次は、AlbListenerを作成します。

AlbListenerパッケージをインポートします。

import { ..., AlbListener } from './.gen/providers/aws';

そして、以下のようにAlbListenerの設定を追記します。

const albListener = new AlbListener(scope, 'sample-cdktf-alb-listener', {
loadBalancerArn: Token.asString(alb.arn),
port: 80,
protocol: 'HTTP',
defaultAction: [{
type: 'fixed-response',
fixedResponse: [{
contentType: 'text/plain',
messageBody: 'OK',
statusCode: '200'
}]
}]
});

defaultActionは、レスポンスがAlbListenerRuleのどれにも合致しない場合に実行されるアクションを設定できます。ここではtypeにfixed-responseを指定することで固定のHTTPレスポンスを応答するよう設定しています。

次は、AlbListenerRuleを設定します。

AlbListenerRuleパッケージをインポートします。

import { ..., AlbListenerRule } from './.gen/providers/aws';

そして、以下のようにAlbListenerRuleの設定を追記します。

new AlbListenerRule(this, 'sample-cdktf-alb-listener-rule', {
listenerArn: albListener.arn,
priority: 100,
action: [{
type: 'forward',
targetGroupArn: albTargetGroup.arn
}],
condition: [{
field: 'path-pattern',
values: ['*']
}]
});

priorityはリクエストが複数のリスナールールにマッチした際に、どちらのリスナールールを適用するか判断するための優先順位です。数字が小さいほど優先順位が高いです。

conditionはこのリスナールールが対象とするリクエストの条件です。’*’は全てのパスが対象になっています。

これで、ALBの構築は完了です。

9. ECSの構築

次に、ECSを構築します。
ECSはAWS上で手軽にDockerコンテナを利用できるフルマネージド型サービスです。ECSのコンポーネントとしてEcsCluster、EcsTaskDefinition、EcsServiceが出てきます。これらについてはこちらの動画[11]の6:37~7:53の説明がわかりやすかったです。

9.1 ECS用のロールの構築

まずは、ECS用のタスクロールを作成します。
以下のようにECS用の設定を記述します。

const ecsTaskRole = new IamRole(this, 'ecsTaskRole', {
name: 'sample-cdktf-ecsTaskRole',
assumeRolePolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}`
});

次は、ポリシーを作成します。

const ecsTaskIamPolicy = new IamPolicy(scope, 'ecs-task-policy', {
name: 'ecs-task-policy',
description: 'Policy for updating ECS tasks',
policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ecs:DescribeServices",
"ecs:CreateTaskSet",
"ecs:UpdateServicePrimaryTaskSet",
"ecs:DeleteTaskSet",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:ModifyListener",
"elasticloadbalancing:DescribeRules",
"elasticloadbalancing:ModifyRule",
"lambda:InvokeFunction",
"cloudwatch:DescribeAlarms",
"sns:Publish",
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}`
});

先ほど作成した、ロールとポリシーを以下のように紐付けます。

new IamRolePolicyAttachment(scope, 'attach-ecs-task-policy', {
role: ecsTaskRole.name,
policyArn: ecsTaskIamPolicy.arn
});

次は、同じようにECS用のタスク実行ロールを設定します。
先ほどのタスクロールとほとんど同じです。

const ecsTaskExecutionRole = new IamRole(this, 'ecsTaskExecutionRole',{
name: 'sample-cdktf-ecsTaskExecutionRole',
assumeRolePolicy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}`
});

タスク実行ロール用のポリシーを作成します。
標準で用意されているAmazonECSTaskExecutionRolePolicyと同じように、ECRからイメージをPullするための権限と、CloudWatchLogsにログを記録するための権限を付与しています。

const ecsTaskExecutionIamPolicy = new IamPolicy(scope, 'ecs-task-execution-policy', {
name: 'ecs-task-execution-policy',
description: 'Policy for updating ECS tasks',
policy: `{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}`
});

あとは、タスクロールと同じようにロールとポリシーの紐付けを行います。

new IamRolePolicyAttachment(scope, 'attach-ecs-task-execution-policy', {
role: ecsTaskExecutionRole.name,
policyArn: ecsTaskExecutionIamPolicy.arn
});

9.2 ECS本体の構築

ロールの設定が終わったら、次はEcsClusterパッケージをインポートします。

import { ..., EcsCluster } from './.gen/providers/aws';

インポート後、以下のようにEcsClusterの設定を追記します。

const ecsCluster = new EcsCluster(this, 'sample-cdktf-cluster', {
name: 'sample-cdktf-cluster'
});

次はECSのタスク定義を設定します。
タスク定義を設定するために、EcsTaskDefinitionパッケージをインポートします。

import { ..., EcsTaskDefinition } from './.gen/providers/aws';

EcsTaskDefinitionを使用して、タスク定義を設定する前にコンテナを設定します。
EcsTaskDefinitionの引数に直接以下設定を記載することも可能ですが、今回は見やすいように分けます。

const containerDefinition: string = `[
{
"essential": true,
"name": "sample-cdktf-container",
"image": "${ecrRepository.repositoryUrl}:latest",
"portMappings": [
{
"hostPort": 9000,
"protocol": "tcp",
"containerPort": 9000
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/aws/ecs/task-for-cdktf",
"awslogs-stream-prefix": "ecs",
"awslogs-region": "ap-northeast-1"
}
}
}
]`

コンテナの essential パラメータが true とマークされている場合、そのコンテナが何らかの理由で失敗または停止すると、タスクに含まれる他のすべてのコンテナは停止されます。

すべてのタスクには少なくとも 1 つの必須コンテナが必要です。[7]

今回作成するコンテナ定義はこれだけなのでessentialはtrueに設定します。

コンテナ定義の設定が終わったら、次は以下のようにタスク定義を設定します。

const ecsTaskDefinition = new EcsTaskDefinition(this, 'sample-cdktf-task', {
containerDefinitions: containerDefinition,
family: 'task-for-cdktf',
networkMode: 'awsvpc',
executionRoleArn: ecsTaskExecutionRole.arn,
taskRoleArn: ecsTaskRole.arn,
cpu: '512',
memory: '1024',
requiresCompatibilities: [ 'FARGATE' ]
});

familyはタスク定義の名前のようなものです。なぜnameでないのか気になりますがあまり深く考える必要はなさそうです。

次は、ECSのサービスを設定します。
サービスは、EcsServiceをインポートして設定を行います。

import { ..., EcsService } from './.gen/providers/aws';

以下のようにECSサービスの設定を追記します。

new EcsService(this, 'sample-cdktf-ecs-service', {
cluster: Token.asString(ecsCluster.id),
desiredCount: 1,
deploymentMaximumPercent: 200,
deploymentMinimumHealthyPercent: 100,
launchType: 'FARGATE',
name: 'sample-cdktf-ecs-service',
platformVersion: 'LATEST',
taskDefinition: Token.asString(ecsTaskDefinition.id),
networkConfiguration: [{
securityGroups: [Token.asString(vpc.defaultSecurityGroupId)],
subnets: [Token.asString(privateSubnet1.id), Token.asString(privateSubnet2.id)]
}],
loadBalancer: [{
containerName: 'sample-cdktf-container',
containerPort: 9000,
targetGroupArn: albTargetGroup.arn
}]
});

desiredCountはECSサービスが維持するタスク数です。

deploymentMaximumPercentはECSサービスが実行できるタスク数の上限で、desiredCountに対する割合を指定します。
deploymentMinimumHealthyPercentは最低限保持されなければならない実行タスクの数の下限で、desiredCountに対する割合を指定します。今回はdesiredCountが1なのでdeploymentMaximumPercentが200以上なければrolling updateができません。

最後に、ECSタスクからログ出力できるようにCloudWatchの設定を行います。nameに指定する値は、containerDefinitionのawslogs-groupに指定した値と同じである必要があります。

new CloudwatchLogGroup(this, 'sample-cdktf-ecs-task-log-group', {
name: `/aws/ecs/task-for-cdktf`
});

これで、ECSの構築は完了です。

10.Play Frameworkの修正

以上でCDK for Terraformの設定は完了です。しかし、このままではECSのタスク上でPlay Frameworkを動作させた際にエラーが発生してしまうため修正を行います。

1つ目のエラーは以下です。

java.nio.file.AccessDeniedException: /opt/docker/RUNNING_PID

これを解決するために以下のコードをconf/application.confに追加します。

play.server.pidfile.path=/dev/null

Play FrameworkはデフォルトでPIDファイルを生成しますが、コンテナ環境では必要ないため生成しないよう設定しています[10]。

2つ目のエラーは以下です。

The application secret has not been set, and we are in prod mode. Your application is not secure.
To set the application secret, please read http://playframework.com/documentation/latest/ApplicationSecret

本番環境ではsecretが必要なため、これを設定する必要があると言われています。
まず以下のコマンドをターミナルで実行し、secretを生成します。

$ sbt playGenerateSecret

そして生成されたsecretを以下のようにapplication.confに追加します。

play.http.secret.key="xxxxxxxxxxxxxxxx"

3つ目のエラーはECSサービスのイベントに以下が表示されます。

service sample-cdktf-ecs-service (port 9000) is unhealthy in target-group sample-cdktf-alb-target-group due to (reason Health checks failed with these codes: [400]).

また、ECSタスクの停止理由には以下が表示されます。

Task failed ELB health checks in (target-group arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/sample-cdktf-alb-target-group/xxxxxxxx)

これらのエラーについてはこの記事[4]の後半で触れられています。

Play Framework 2.6.x以降からデフォルトで[localhost, local]からしかアクセスを受け付けない設定になっているらしいです。

そのため、他のアドレスからアクセスする際は、許可させる設定を行う必要があります。[4]

記事に書かれているように、以下のコードをapplication.confに追加します。

play.filters.hosts {
allowed = ["."]
}

以上の変更が完了したらmasterブランチにマージして、ECRにコンテナイメージをプッシュしましょう。

11. CDK for Terraformのデプロイ・デストロイ

以上で設定は完了です。では、前回同様にCDK for Terraformで構築したものを以下コマンドで、AWSにデプロイします。

$ cdktf deploy

デプロイが完了したら、AWSのマネジメントコンソールで、設定した環境が生成されているかを確認します。

全て生成が確認できたら、次はECSが正しく動作しているか確かめましょう。以下の手順で画面遷移します。

ECS → クラスター → sample-cdktf-cluster → sample-cdktf-ecs-service → イベントタブ

イベントの中にservice sample-cdktf-ecs-service has reached a steady state.という文言があれば成功です。
もし、文言がない場合はタスクの生成に失敗しています。ログタブからログを確認して、問題点を修正しましょう。

ECSが正しく動作していたら、実際にアプリケーションにアクセスしてみましょう。ALBのDNS名でアクセスし、Play Frameworkの画面が表示されていれば成功です。

無事に成功していたら、以下コマンドでAWSの環境を削除しておきます。

$ cdktf destroy

無事削除されていれば、今回の工程は全て完了です。

今回作成したCDK for Terraformのコードはこちら、Play Frameworkアプリケーションのコードはこちらから確認できます。

12. おわりに

長くなりましたが、最後まで読んでいただき、ありがとうございます。

次は、3章でSlackに送ったメッセージを元にECSのタスク及びサービスを更新させる処理の作成を行います。

参考文献

[1] CDK for Terraform: Enabling Python & TypeScript Support
[2] AWS CDKでプロバイダーとしてTerraformが使える!!CDK for Terraformが発表されました!!
[3] terraform-cdk で AWSにリソースをdeployする
[4] [2章]Play Framework(Scala)をAWS(EC2)にデプロイしてWelcome to Play!を表示させる
[5] Pragmatic Terraform on AWS
[6] AWS ELB を Terraform で消そうとしたらハマった
[7] [AWS Black Belt Online Seminar] Amazon ECS Deep Dive
[8] タスク定義パラメータ
[9] Application Load Balancer とは?
[10]Production Configuration

We are hiring!

株式会社ネクストビートでは

「人口減少社会において必要とされるインターネット事業を創造し、ニッポンを元気にする。」
を理念に掲げ一緒に働く仲間を募集しております。

https://www.nextbeat.co.jp/recruit

--

--