【AWS CDK】コンテナイメージをビルドしてECS Fargateで起動する
概要
今回はCDKをデプロイする中でコンテナイメージのビルドを行い、そのままイメージをECRにプッシュ。
さらにプッシュしたイメージを使ってECSのサービスを立ち上がるところまでをワンセットで行うコードを紹介します。
サンプルなので簡単のためシンプルにNginxを立ち上げるだけに留めますが、アプリケーションまるまるコンテナ化している場合でも理屈は同じなので流用可能です。
環境
Node
node -v
v20.11.1
package.jsonから抜粋
{
"dependencies": {
"aws-cdk": "2.127.0",
"aws-cdk-lib": "2.127.0",
"typescript": "~5.3.3"
}
}
ディレクトリ構成
以下のような構成をイメージしています。
cdk
とsrc
(今回は使わないが、アプリケーションのコードが格納されてるイメージです)とinfra
があり、infra
の中にDockerfile
等々が収まっています。
root
- cdk
- lib
- stack.ts
- src(アプリケーション本体)
- infra
- docker
- nginx
- Dockerfile
- default.conf
Dockerfile
Dockerfile
ですが、コンテキストはroot
直下を指定した場合を想定しています。
なのでCOPY
のパス指定が./infra
から始まっています。
後述のStack.synthesizer.addDockerImageAsset
を使う場合にコンテキストを指定することができますが、注意が必要です。
FROM nginx:1.25
# default.confコピー
COPY ./infra/docker/nginx/default.conf /etc/nginx/conf.d/
cdk
いよいよ本体のcdk
です。
Stack.synthesizer.addDockerImageAsset
で先のDockerfile
のパスとコンテキストを指定していますが、基準となるのはスタックファイルのパス(ここでいうところのroot/cdk/lib/stack.ts
)なので、そこからの相対パスを指定する点に注意です。
Nginx
へのアクセスを確認するためにALB
などを作成していますが、用途によっては不要です。
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as path from 'path';
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// nginxのDockerfileからイメージをビルドしてECRにプッシュ
const webLocation = this.synthesizer.addDockerImageAsset({
// buildcontextの指定
directoryName: path.join(__dirname, '../..'),
// Dockerfileの指定
dockerFile: path.join(__dirname, '../../infra/docker/nginx/Dockerfile'),
sourceHash: "web",
});
// VPC作成
const vpc = new ec2.Vpc(this, 'MyVPC', {
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});
// セキュリティグループの作成
const publicSecurityGroup = new ec2.SecurityGroup(this, 'MyPublicSecurityGroup', {
vpc,
description: 'Allow all inbound and outbound traffic',
allowAllOutbound: true, // すべてのアウトバウンド通信を許可
});
// すべてのIPからのすべてのプロトコルを許可するインバウンドルールを追加
publicSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.allTraffic(),
'Allow all inbound traffic'
);
// ECSクラスタを作成
const cluster = new ecs.Cluster(this, 'MyCluster', {
vpc,
enableFargateCapacityProviders: true
});
// ECSタスク定義を作成
const taskDefinition = new ecs.FargateTaskDefinition(this, 'MyTaskDefinition', {
memoryLimitMiB: 3072,
cpu: 1024
});
// コンテナを作成
const container = taskDefinition.addContainer('web', {
containerName: "web",
image: ecs.ContainerImage.fromRegistry(webLocation.imageUri),
portMappings: [
{ name: "web-80-tcp", containerPort: 80, hostPort: 80, protocol: ecs.Protocol.TCP, },
],
logging: new ecs.AwsLogDriver({ streamPrefix: 'web' }),
});
// ALBを作成
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyALB', {
internetFacing: true,
vpc,
vpcSubnets: {
subnets: vpc.publicSubnets
},
securityGroup: publicSecurityGroup
});
// ターゲットグループの作成
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'MyTargetGroup', {
// ヘルスチェックの設定
healthCheck: {
healthyHttpCodes: '200',
healthyThresholdCount: 5,
interval: Duration.seconds(30),
path: '/',
timeout: Duration.seconds(5),
unhealthyThresholdCount: 2
},
port: 3000,
protocol: elbv2.ApplicationProtocol.HTTP,
targetGroupName: 'my-tg',
vpc,
targetType: elbv2.TargetType.IP
});
// HTTP(80)のリスナー設定
alb.addListener('MyHttpListener', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
defaultTargetGroups: [targetGroup]
});
// ALBが参照するFargateサービスを定義
const service = new ecs.FargateService(this, 'FargateService', {
cluster,
taskDefinition,
// Fargateに直接アクセスさせたくないならPrivateSecurityGroupのみアタッチする
securityGroups: [publicSecurityGroup],
capacityProviderStrategies: [
{
capacityProvider: 'FARGATE',
weight: 1
}
],
assignPublicIp: false
});
service.attachToApplicationTargetGroup(targetGroup);
}
}
まとめ
今回はコンテナイメージのビルドからECSのサービス起動までの一連の流れをCDKで行うコードを紹介しました。
Stack.synthesizer.addDockerImageAsset
を使用しましたが、他にも@aws-cdk/aws-ecr-assetsを使うなどがあります。
軽く調べた感触としては使い勝手はそこまで変わらないようですが、そちらは非推奨になった項目が多く、今後どうなるかが不安だったのでStack.synthesizer.addDockerImageAsset
を選択しました。
今回の内容が役立ちましたら幸いです。
Discussion