AWS ECR のライフサイクルポリシーで dev / latest などの可変タグが消えないようにする

2026-09-18 02:30 (6 hours ago)
AWS ECR のライフサイクルポリシーで dev / latest などの可変タグが消えないようにする

開発環境の Kubernetes Deployment が ImagePullBackOff で起動しなくなった。ECR を見ると dev タグのイメージが無い。

$ aws ecr describe-images --repository-name app --image-ids imageTag=dev
An error occurred (ImageNotFoundException) ... The image with imageId {imageTag:'dev'} does not exist

ECR のトークンは 12 時間で失効するので最初は認証を疑ったが、Secret は更新されたばかりで、同じ Pod の別コンテナは pull できていた。認証ではなくイメージそのものが無い。消したのはライフサイクルポリシーである。

前提: 1 イメージに 2 種類のタグを付けている

このリポジトリでは、ビルドのたびに 1 つのイメージへ 2 種類のタグを push している。

種類 用途
可変タグ latest (本番)、dev (開発環境) Deployment / CronJob が imagePullPolicy: Always で引く。最新の 1 個だけに意味がある
版数タグ 1.12.3701.12.370-dev 追跡とロールバック用。数世代残したい

同じビルドの dev1.12.370-dev同じ digest を指す。これが効いてくる。

原因: ECR はタグではなくイメージを expire する

事故った時のポリシーは 2 ルールだけだった。

{"rules":[
  {"rulePriority":1,"description":"版数タグのイメージは3個残して削除",
   "selection":{"tagStatus":"tagged","tagPatternList":["*.*"],"countType":"imageCountMoreThan","countNumber":3},
   "action":{"type":"expire"}},
  {"rulePriority":2,"description":"タグなしのイメージを自動削除",
   "selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":1},
   "action":{"type":"expire"}}
]}

*.* は「ドットを含むタグ」で、版数タグを狙ったパターンである。dev にはドットが無いので一致しない。一見すると dev は守られている。

ところが ECR が expire するのはタグではなくイメージ (digest) だ。1.12.370-dev*.* に一致する。本番ビルドが 3 回走れば 1.12.370-dev は「新しい 3 個」の外へ押し出され、そのイメージが expire され、同じイメージに付いていた dev タグも一緒に消える。

開発環境のビルド頻度が本番より低いほど起きやすい。今回は開発環境の最後のビルドから 1 か月弱のうちに本番ビルドが 4 回走っていた。

この挙動はライフサイクルポリシーの評価規則に書かれている。「イメージはちょうど 1 つまたは 0 個のルールで expire される」「あるルールのタグ条件に一致したイメージは、より低い優先度のルールでは expire できない」。この後半が対策に使える。

対策: 可変タグを高優先度の keep-1 ルールで保護する

可変タグごとに「最新 1 個だけ残す」ルールを、版数タグのルールより高い優先度で置く。

{"rules":[
  {"rulePriority":1,"description":"latest は最新1個を保持",
   "selection":{"tagStatus":"tagged","tagPatternList":["latest"],"countType":"imageCountMoreThan","countNumber":1},
   "action":{"type":"expire"}},
  {"rulePriority":2,"description":"dev は最新1個を保持",
   "selection":{"tagStatus":"tagged","tagPatternList":["dev"],"countType":"imageCountMoreThan","countNumber":1},
   "action":{"type":"expire"}},
  {"rulePriority":3,"description":"版数タグは3世代残す",
   "selection":{"tagStatus":"tagged","tagPatternList":["*.*"],"countType":"imageCountMoreThan","countNumber":3},
   "action":{"type":"expire"}},
  {"rulePriority":4,"description":"タグなしは1日で削除",
   "selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":1},
   "action":{"type":"expire"}}
]}

imageCountMoreThan: 1 は「1 個より多い分を消す」なので、可変タグが付いたイメージの最新 1 個が残る。可変タグが新しいイメージへ付け替えられると、古いイメージは版数タグだけになり、rule 3 の世代管理に戻る。そこからも落ちればタグなしになって rule 4 で消える。ルール間で綺麗にバトンが渡る。

環境を増やして可変タグが増えたら、その keep-1 ルールも足す。足し忘れると、その環境がいずれ同じ症状になる。

適用前に preview でドライランする

put-lifecycle-policy は適用した瞬間から効き、条件に合うイメージは 24 時間以内に消える。間違ったポリシーを put すると、確認する間もなく消える。先に start-lifecycle-policy-preview へ同じ JSON を渡して結果を見る。

policy=$(cat policy.json)
repo=app

aws ecr start-lifecycle-policy-preview \
  --repository-name "$repo" --lifecycle-policy-text "$policy"

# preview は非同期。固定 sleep ではなく COMPLETE を待つ
until [ "$(aws ecr get-lifecycle-policy-preview --repository-name "$repo" \
           --query status --output text)" = COMPLETE ]; do sleep 5; done

# previewResults は expire 対象のイメージだけを返す
aws ecr get-lifecycle-policy-preview --repository-name "$repo" \
  --query 'previewResults[*].{tags:imageTags,rule:appliedRulePriority}' --output json

aws ecr put-lifecycle-policy \
  --repository-name "$repo" --lifecycle-policy-text "$policy"

previewResultslatestdev を含むイメージが出たら、優先度かパターンが間違っている。

コンソールで手で設定すると再現性が無く、次にリポジトリを作る時に同じ事故を繰り返す。この preview → put をシェルスクリプトにしてポリシー JSON をヒアドキュメントで埋め込み、リポジトリに入れておくと、ルールを変えたい時は編集して再実行するだけで済む。

preview で気付いたこと: 保護されたイメージも件数には数えられる

適用時の preview で意外な結果が出た。版数タグのイメージが 1.12.359 / 1.12.365 / 1.12.368 (latest) / 1.12.370-dev (dev) の 4 つある状態で、1.12.359 が expire 対象になったのである。

dev のイメージが rule 3 の評価から除外されるなら、rule 3 が見る版数タグは 3 つで何も消えないはずだ。実際には dev のイメージも数に入れた上で「新しい 3 個」を決め、dev のイメージは保護されて残り、その分 1 つ古い 1.12.359 が押し出された。

「高優先度ルールに一致したイメージは低優先度ルールで expire されない」は正しいが、「低優先度ルールの件数から除外される」わけではない。開発環境のイメージが本番より新しい間は、版数タグで残る本番の世代が設定値より 1 つ少なくなる。可変タグの保護には影響しないのでそのままにしたが、世代数を厳密に確保したいなら countNumber を可変タグの数だけ増やす。

採らなかった案: dev に版数タグを付けない

開発環境のイメージに dev だけを付け、1.12.N-dev を付けなければ、*.* に一致するタグが無いので旧ポリシーのままでも消えない。付け替えられた古いイメージはタグなしになり 1 日で消える。ワークフローの変更だけで済み、ポリシーは触らなくてよい。

ただし版数タグによる追跡ができなくなる。他のリポジトリでは既に keep-1 で可変タグを保護する構成にしていたので、そちらに揃えた。

消えたイメージは戻らない

ポリシーを直しても、消えた dev は復活しない。開発環境のイメージをビルドし直して push する必要がある。

順序はまずビルドと push で環境を復旧し、続けてポリシーを直す。ポリシーを先に直しても環境は戻らないし、ビルドだけ先にやってポリシーを放置すれば、本番ビルドが 3 回走った時点でまた消える。

評価をお願いします (会員登録・ログイン不要)
まだ評価がありません
著者は、アプリケーション開発会社 Cyberneura を運営しています。
開発相談をお待ちしています。

アーカイブ