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>-<リージョン>
    

    DockerImageAssetfromAsset() を使うと、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 の前にライフサイクルポリシーを設定してください。新しい環境を立ち上げるときのチェックリストに加えておくと、忘れずに済みます。

    参考リンク

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark