AWS CDKでECSにデプロイするDockerイメージのマシな管理方法
大前提の問題
AWS CDKを使ってECSへの継続的なデプロイを行う場合、単純にイメージをfromEcrRepository
などで指定すると、
Dockerイメージのみが変更された時、素直にイメージを指定しているだけだと変更が検知されないのでECSタスクの更新が行われません。
主な解決策
3つあります。
ContainerImage.fromAsset
を使う- イメージのダイジェストをイメージのタグとして付与する
- gitコミットハッシュをイメージのタグとして付与する
それぞれ利点や問題点を書いていきます。
ContainerImage.fromAsset
を使う
一見一番良さそうに思える方法です。
aws-ecr-assets module · AWS CDK にある class ContainerImage · AWS CDK のメソッドを利用します。
ContainerImage.fromAsset
とContainerImage.fromDockerImageAsset
の両方ありますが、
ContainerImage.fromAsset
で基本的に良いでしょう。イメージを共有して複数箇所に配置するとかなら後者の出番もあるかもしれません。
ContainerImage.fromAsset
にDockerfileのあるディレクトリを指定するだけで、
ECRリポジトリの作成、イメージのビルド、プッシュ、全て自動で行ってくれます。
これ最高では?
experimental扱いですが…
と思ったのですが、
aws-cdk
みたいなリポジトリが出来てしまいます。ではリポジトリ名を指定すれば良いのでは?
と思ったら引数のrepositoryName
が非推奨で、書き換え先もまた非推奨です。
何故だろうなと思って調べた所、
Confusing Deprecations: IDeploymentEnvironment / Stack.addDockerImageAsset / DockerImageAsset.repositoryName · Issue #8483 · aws/aws-cdk で議論されていて、 aws-ecr-assetsが作るリポジトリは一時的な領域であることが分かりました。だからタスクがイメージを読み込み終えたらイメージが即座に消失するのですね。
これはcontextでassets-ecr-repository-name
を指定すれば良いとかそういう次元の問題ではありませんね。もしcdk deploy
した後問題が起きてとりあえずrollbackしたくなった場合、イメージが消えていたらrollback出来ません。
なので、社内向けとかそういう手軽な用途ならともかく、広く一般に公開するときの手法としては適さないでしょう。
イメージのダイジェストをイメージのタグとして付与する
ECS(Fargate)のServiceをCDKで構築・デプロイしてみた | Developers.IO
で使われている方法です。
CloudFormationのImageではtagはサポートされていてもdigestはサポートされていないため、泥臭くタグに移し替えす必要があります。
これをもうちょっと洗練させて、
Cannot reference EcrImage by digest instead of tag · Issue #5082 · aws/aws-cdk
の方が貼っているDigestibleEcrImage
を使うと少しスッキリするかもしれません。
しかしこれにも問題があります。それはリモートリポジトリのlatestのdigestを見ているため、複数ブランチで開発した時に間違ったイメージを見てしまう可能性が出来るという点です。
例えばステージをdeve
, stag
と分けていた場合、
deve
で実験しているつもりがstag
の実験のためのイメージを読み込んでしまうことなどが考えられます。
またせっかくgit resetでリビジョンを戻しても、 docker pushしていなかったら元のイメージがpushされてしまいます。
gitコミットハッシュをイメージのタグとして付与する
コミットハッシュを付与するのが一番マシなようです。
ECRへのプッシュは以下のようなシェルスクリプトで別個に行いましょう。
#!/usr/bin/env bash
set -eux
docker build -t foo .
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin bar.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag foo:latest bar.dkr.ecr.ap-northeast-1.amazonaws.com/foo:$(git rev-parse HEAD)
docker push bar.dkr.ecr.ap-northeast-1.amazonaws.com/foo:$(git rev-parse HEAD)
ただこれだと、 CDKのデプロイ時にリポジトリに指定のタグのイメージがあるか分かりません。無くてもそのままデプロイ実行出来てしまって、 ECSがイメージを取得できないと言うエラーが出現します。
仕方がないのでaws cliを使ってチェックしましょう。
aws CLIをspawnSyncで呼び出してECRに目的のイメージが存在するかチェックしてるんですがこれCDKのAPI使って楽に出来ないのかな
— エヌユル (@ncaq) September 28, 2020
見つかりませんでした。
/** デプロイ対象のDockerイメージのタグを返します */
export function taskImageTag(): string {
return spawnSync("git", ["rev-parse", "HEAD"]).stdout.toString().trim();
}
/** デプロイ対象のDockerイメージが存在するかチェックします */
function ecrImageExist(): boolean {
return (
spawnSync("aws", [
"ecr",
"describe-images",
"--repository-name=foo",
`--image-ids=imageTag=${taskImageTag()}`,
]).status === 0
);
}
aws cliはcdkの--profile
引数を当然読まないので、
AWS_PROFILE
環境変数にAWSのプロファイルを入れてプッシュスクリプトごと実行するのが良いでしょう。
あるいはもうCDK内部でプッシュスクリプトを呼び出しても良いかもしれませんね。
スナップショットテストを実行しているとコミットごとにイメージタグが変わるため、毎回差分が発生して必ずテスト失敗になります。よって、ダミーのタグ値を引数で入れられるように設定しておきましょう。
何かもっと良い解決策がある気がするのですが、現状これが一番マシなので仕方なくこれを使っていきます。
より良い方法を常に求めています。
2021-02-06 追記: AWS SDKを使えば良さそうです
イメージが存在するかチェックする方法は、 DescribeImagesCommand | @aws-sdk/client-ecr などを使うのが可読性が高そうです。
aws cliとそこまで変わっているかと言うとそうでもありませんが。
2021-03-01 追記: AWS SDKを使ってチェックする方法
/** デプロイ対象のDockerイメージが存在するかチェックします */
async function ecrImageExist(): Promise<boolean> {
try {
await ecrClient.send(
new DescribeImagesCommand({
repositoryName: "foobar",
imageIds: [{ imageTag: await taskImageTag() }],
})
);
return true;
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e?.name === "ImageNotFoundException") {
// eslint-disable-next-line no-console
console.info("ECRにデプロイ指定するためのイメージが見つかりませんでした");
return false;
}
// eslint-disable-next-line no-console
console.error(e);
return false;
}
}