CDK bootstrap リポジトリの ECR イメージが無限に増え続ける問題と対策
Amazon Bedrock AgentCoreでエージェントを作るときなど、知らず知らずのうちにECRへDockerイメージを登録していることがあります。ECR コンソールを開いて、cdk-hnb659fds-cont […]
目次
Amazon Bedrock AgentCoreでエージェントを作るときなど、知らず知らずのうちにECRへDockerイメージを登録していることがあります。ECR コンソールを開いて、cdk-hnb659fds-container-assets-* のイメージ一覧を見たことはありますか?
20 個、50 個、100 個。デプロイのたびにイメージが増えていきます。どれが使われていて、どれが不要なのか判断がつかず、削除にも踏み切れません。そっとタブを閉じた結果、来月の請求書がまた少し膨らみます。
この記事では、CDK bootstrap リポジトリにタグ付きイメージが蓄積する仕組みを説明し、ライフサイクルポリシー(Lifecycle policy)で自動削除する手順を解説します。
CDK bootstrap リポジトリとは何か
cdk deploy でコンテナイメージを含むスタックをデプロイすると、CDK はイメージを ECR リポジトリにプッシュします。このリポジトリは CDK bootstrap 時に自動作成されるもので、名前は以下のフォーマットです。
cdk-hnb659fds-container-assets-<アカウントID>-<リージョン>
DockerImageAsset や fromAsset() を使うと、CDK はイメージの内容からハッシュ値を計算し、そのハッシュをタグとしてプッシュします。コンテンツハッシュタグ(content hash tag)と呼ばれる仕組みです。
abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
Dockerfile やソースコードが 1 文字でも変われば、ハッシュが変わり、新しいイメージがプッシュされます。デプロイのたびに新しいタグ付きイメージが 1 枚ずつ積み上がっていきます。
ここが問題の核心です。このリポジトリは CDK が bootstrap 時に CloudFormation で作成するものであり、アプリケーションの CDK コードからは直接管理できません。自分のスタックで ecr.Repository を定義して addLifecycleRule() を呼ぶ、というやり方が通用しない場所にイメージが溜まるのです。
デフォルトのライフサイクルポリシーでは何が起きるか
CDK bootstrap テンプレートには、ライフサイクルポリシーが 1 つだけ設定されています。bootstrap-template.yaml から該当部分を抜粋します。
{
"rules": [
{
"rulePriority": 1,
"description": "Untagged images should not exist, but expire any older than one year",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 365
},
"action": { "type": "expire" }
}
]
}
tagStatus: "untagged" のイメージだけが対象です。
先ほど説明した通り、fromAsset() がプッシュするイメージにはコンテンツハッシュタグが付きます。さらに、bootstrap リポジトリは ImageTagMutability: IMMUTABLE に設定されているため、既存タグの上書きも起きません。結果として、すべてのイメージがタグ付き(tagged)の状態で残り続けます。
デフォルトポリシーは、このタグ付きイメージには一切触れません。
コスト影響を計算する
ECR のストレージ料金は $0.10/GB/月 です。
たとえば最近作った AgentCore Runtime のコンテナイメージは圧縮後で約 133MB ありました。これを例に試算しましょう。
| デプロイ回数 | 蓄積イメージ容量 | 月額ストレージコスト |
|---|---|---|
| 10 回 | 約 1.3 GB | 約 $0.13 |
| 50 回 | 約 6.7 GB | 約 $0.67 |
| 100 回 | 約 13.3 GB | 約 $1.33 |
| 500 回 | 約 66.5 GB | 約 $6.65 |
1 つのプロジェクトだけなら大した金額には見えません。しかし、複数のプロジェクトを同じアカウント・リージョンにデプロイしていると、すべてのイメージが同じ bootstrap リポジトリに入ります。開発中のアクティブなプロジェクトが 3 つあり、それぞれ週 5 回デプロイするなら、1 年で 780 イメージに達します。気づかないうちに請求額が膨れていく構造です。
ライフサイクルポリシーを設定する
AWS CLI でライフサイクルポリシーを上書きします。設定するルールは 2 つで、Rule 1 がタグ付きイメージを直近 N 個だけ保持して残りを削除するルール、Rule 2 がタグなしイメージを 1 日で削除するルールです。
まず、ポリシーの JSON ファイルを作成します。
{
"rules": [
{
"rulePriority": 1,
"description": "Keep only the last N tagged images",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["*"],
"countType": "imageCountMoreThan",
"countNumber": 5
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Expire untagged images after 1 day",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": { "type": "expire" }
}
]
}
Rule 1 の countNumber: 5 は「直近 5 世代のタグ付きイメージを保持する」という意味です。6 番目以降に古いイメージが削除対象になります。tagPatternList: ["*"] で、すべてのタグパターンにマッチさせています。
Rule 2 は、何らかの理由でタグなしイメージが発生した場合のセーフティネットです。デフォルトの 365 日を 1 日に短縮しました。bootstrap リポジトリは IMMUTABLE なので、通常タグなしイメージは発生しませんが、念のために設定しておきます。
次に、AWS CLI でポリシーを適用します。リポジトリ名は自分の環境に合わせて変更してください。
aws ecr put-lifecycle-policy \
--repository-name "cdk-hnb659fds-container-assets-123456789012-ap-northeast-1" \
--lifecycle-policy-text "file://lifecycle-policy.json"
なぜ初回デプロイの前に設定すべきか
ポリシーは既存のイメージにも適用されるため、デプロイ後に設定しても機能します。ただし、既に大量のイメージが蓄積している場合、ポリシー適用後に一斉削除が走り、CloudTrail のイベントログが大量に発生します。
初回の cdk deploy の前に設定しておけば、最初からイメージ数が制御された状態を維持できます。cdk bootstrap はリポジトリを作成するだけでイメージはプッシュしないので、bootstrap 後・deploy 前が設定のベストタイミングです。
注意事項
削除のタイミング
ライフサイクルポリシーによる削除は、ポリシー適用後、最大 24 時間以内に実行されます。即座に削除されるわけではありません。プレビュー機能(aws ecr start-lifecycle-policy-preview)で、どのイメージが削除対象になるかを事前に確認することをおすすめします。
保持世代数の決め方
countNumber の値は、同時に存在する必要があるイメージの最大数で決めます。
たとえば、開発(dev)・ステージング(stg)・本番(prod)の 3 環境に順番にデプロイする場合、それぞれが異なるイメージを参照する期間があります。最低 3 世代の保持が必要になるため、ロールバックの余裕も含めて 5〜10 程度に設定してください。
1 つのプロジェクトしかデプロイしていないなら 3〜5 で十分です。複数プロジェクトが同じリポジトリを共有している場合は、プロジェクト数 × 環境数を考慮して数値を大きめに設定してください。
リージョンやアカウントが変わるとリポジトリも変わる
CDK bootstrap リポジトリはアカウント ID とリージョンを含む名前で作成されます。別のリージョンに新たに cdk bootstrap を実行すると、そのリージョンにも別のリポジトリが作られます。ライフサイクルポリシーはリポジトリ単位の設定なので、新しいリポジトリにも個別に設定が必要です。
まとめ
CDK bootstrap リポジトリは、CDK が作って CDK が面倒を見てくれない場所です。
fromAsset() がプッシュするタグ付きイメージは、デフォルトのライフサイクルポリシーでは永久に残ります。放置すればストレージコストが積み上がる一方ですが、AWS CLI でライフサイクルポリシーを 1 回設定すれば、あとは自動で古いイメージが消えていきます。
cdk bootstrap 直後、最初の cdk deploy の前にライフサイクルポリシーを設定してください。新しい環境を立ち上げるときのチェックリストに加えておくと、忘れずに済みます。