CDK にアプリコード変更を検出させつつ、ECS へデプロイ
はじめに
こんにちは、build サービスチームで Platform エンジニアをやっている t.g. です。
以前、「CDK を使った ECS アプリコードの変更検出のやり方」をご紹介しました。その記事ではECR へのイメージ push 止まりでしたので、今回は、ECS (Fargate)にデプロイする部分について解説したいと思います。
前回のサマリ
CDK + ECS + ECR の構成で、自前の ECR レポジトリを使いたいならcdklabs/cdk-docker-image-deployment が便利という結果でした。
詳細をお知りになりたい方は、CloudFormation+ECS で困っていたアプリコード変更検出をCDKで実施 を御覧ください。
ECS デプロイに満たしてほしい要件
前回同様、アプリコードに変更があったときのみタスクを更新してもらいたい、という要件で調査しました。
以下、各種の実験結果になります。
案1: タグを指定せずにデプロイ
はじめにトライしてみたテンプレートが以下になります。
- ECR を作成
- cdklabs/cdk-docker-image-deployment で 指定リポジトリに push
- Fargate をデプロイ
// ECR を作成
const repository = new ecr.Repository(this, 'Repository', {
repositoryName: 'cdk-ecs-test4',
autoDeleteImages: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// Docker Image を ECR に push
const newImage = new imagedeploy.DockerImageDeployment(this, 'DeployImage', {
source: imagedeploy.Source.directory(
path.join(__dirname, '../', 'app')
),
destination: imagedeploy.Destination.ecr(repository, {}),
});
// そのまま ECS Fargate をデプロイ
const applicationLoadBalancerFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'FargateService', {
cluster: new ecs.Cluster(this, 'Cluster', {
clusterName: 'cdk-ecs-test',
}),
memoryLimitMiB: 512,
cpu: 256,
desiredCount: 1,
taskImageOptions: {
image: ecs.ContainerImage.fromEcrRepository(repository), // latest タグを探すのでエラー
},
});
すると、ECS タスクのデプロイ中に、タスクに以下のエラーが発生しました。
applicationLoadBalancerFargateService の taskImageOptions オプションにタグを指定しないと、下記のようなエラーが発生してタスクが起動しません。
これは、タグ未指定の場合 `latest` を探すようになっていることが原因です
案2: タグを指定してデプロイ
cdklabs/cdk-docker-image-deployment の imagedeploy で push したDocker イメージのタグが見つかれば良いのですが、ちょっとわからず。。
ここで、前回使っていたアレを思い出しました。
const dummyAsset = new ecrAssets.DockerImageAsset(this, 'dummy-image', {
directory: path.join(__dirname, "../", "app"),
});
前回使用を諦めた aws_ecr_assets です。
こちらは、cdk 管理の ECR リポジトリにイメージが push されるため使用を見送っていましたが、以下の利点があります。
HASH tag を作ってくれる
HASH tag を output してくれる <-- ほしい!
cdklabs/cdk-docker-image-deployment が内部的に aws_ecr_assets を使っていることから、作成される HASH tag は同一のものになります。
この結果、定義したテンプレートが以下になります。
- ECR を作成
- aws_ecr_assets でイメージをpush
- cdklabs/cdk-docker-image-deployment で 指定リポジトリに push
- Fargate をデプロイ
- tagとして、aws_ecr_assets の値を指定
// ECR 作成
(snip)
// Docker Image を ECR に push
const newImage = new imagedeploy.DockerImageDeployment(this, 'DeployImage', {
source: imagedeploy.Source.directory(
path.join(__dirname, '../', 'app')
),
destination: imagedeploy.Destination.ecr(repository, {}),
});
// ダミーで asset を作成。使わないけどTAGが欲しいので.
const dummyAsset = new ecrAssets.DockerImageAsset(this, 'dummy-image', {
directory: path.join(__dirname, "../", "app"),
});
// そのまま ECS Fargate をデプロイ
const applicationLoadBalancerFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'FargateService', {
cluster: new ecs.Cluster(this, 'Cluster', {
clusterName: 'cdk-ecs-test',
}),
memoryLimitMiB: 512,
cpu: 256,
desiredCount: 1,
taskImageOptions: {
// image: ecs.ContainerImage.fromEcrRepository(repository), // latest タグを探すのでエラー
image: ecs.ContainerImage.fromEcrRepository(repository, dummyAsset.assetHash), // こうすると、指定したタグが使われる
},
});
ポイントは、イメージの HASH をアウトプットしてくれる aws_ecr_assets (dummyAsset.assetHash)を利用しているところです。
同一イメージが異なるリポジトリに重複して push されるため、ビルド時間が余計にかかるわけではありません。
唯一考えられる弱点は、重複分 ECR ストレージを余計に消費することでしょうか
案3: 自前で hash を計算する
同僚から、「cdk synth 前に自前でアプリフォルダの hash を計算し、タグを明示的に指定するといいのでは?」と指摘をもらいました。
たしかに、これなら余分な ECR も使わないし、hash 計算にかかる計算コストなんて微々たるものなのでいいですね。
package.json の scripts に `cdk build` と一緒に書いておくか、CICD でビルド前のプリタスクとして定義すればできそうです。
案4: 他の手は?
カスタムリソースを作るのも手だと思います。
ただ、依存関係の解決が難しそうなのと、moving parts を最小限にしたいという観点から、これ以上の深追いはやめました
まとめ
DockerImageAsset を利用することで、ECS Fargate へのデプロイもこちらの意図したとおりになりました。
アプリコードに変更がない場合とある場合で、cdk deploy したときの挙動は以下のようになりました。
ひと工夫で思い通りのクリーンな状況になって御の字です。
プロジェクトでも使ってみていますが、今のところおかしな挙動もなく動いてますね。
みなさまも使ってみてください