ECRでECSで使用中のイメージを消したくないけどライフサイクルでは難しい?
やりたいこと
ECRでもう使っていないイメージを掃除したい。古くても使っているイメージは消したくない(当然)
前提
- ECS と ECR は同一リージョンにある
- イメージのタグはイメージビルド時点のリポジトリのコミットハッシュを付与している
- デプロイ時に ecspresso のテンプレート機能を使って、コミットハッシュの値をイメージのタグに指定している
ライフサイクルでいいのでは?
ECRのライフサイクルはこういう動作をする。
- タグが付いている(Tagged)、付いていない(Untagged)、全て(Any) のいずれかを対象に
- (Tagged/Anyの場合) タグが指定した prefix にマッチするイメージを
- 指定した世代数分だけ新しいものを残し、古いものを削除する
imageCountMoreThan では、イメージは期間の新しいものから始めて最も古いものへと pushed_at_time に基づいて順に並べられた後、指定したカウントより大きいイメージはすべて期限切れとなります。
https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/LifecyclePolicies.html#lp_description - または、指定した日時より古いイメージを削除する
countType = sinceImagePushed では、countNumber に基づき、pushed_at_time が指定された日数より古いすべてのイメージは期限切れとなります。
ライフサイクルで消すことの問題
ECR ライフサイクルは ECS でそのイメージが使われているかどうかは評価されないので、使用中のイメージを消してしまう可能性がある。
具体的にどういう状況で消える可能性があるか
- サービス内の複数のタスク定義で使用しているサイドカーコンテナ(たとえばfluentd)があり、そのイメージも毎回ビルドされてコミットハッシュのタグが付いている
- 頻繁にデプロイされるサービスと、めったにデプロイされないサービスが同じサイドカーを使用している
- 頻繁にデプロイされるサービス(のタスク定義)によって、サイドカーのイメージも頻繁にpushされてタグが付いていく
- 設定変更がない場合、イメージの内容はほぼ同一だが、ベースイメージやパッケージ更新の影響で完全に同一になるとは限らない (=sha256は異なるので別イメージになる)
- めったにデプロイされないサービス(のタスク定義)は古いタグで動き続ける
頻繁にデプロイされるタスク定義によって世代がどんどん進んでしまうと、古いタグのイメージが世代が古い扱いになり、削除対象になる。
イメージが削除された直後は動作中のタスクには影響しないが、タスクが不意に停止→起動しなおしになるとイメージが存在しないので起動できない→サービスダウンとなる
taskdef | image | tag | 稼働中 |
---|---|---|---|
app:5 | fluentd | eeee | * |
app:4 | fluentd | dddd | |
app:3 | fluentd | cccc | |
app:2 | fluentd | bbbb | |
middleware:2 | fluentd | bbbb | * |
app:1 | fluentd | aaaa | |
middleware:1 | fluentd | aaaa |
この場合、3世代保存にするとタグ eeee, dddd, cccc が生き残り、タグ bbbb, aaaa のイメージが消えるので、稼働中の middleware:2 が使っているイメージが消えてしまう。
ライフサイクルで上手くいく方法
要するに、実際にそれぞれの ECS サービスにデプロイされていないタグのイメージがpushされていることが問題なので、それを避ければよい。
- app にはデプロイされているが middleware にはデプロイされていない、ということが単純なコミットハッシュでは見分けられない
- それで世代が進んでしまうのが問題
こうすればよいのでは。
- ビルドしたイメージは ECR の共通 repo (例: fluentd/common) に対して、コミットハッシュを付けて push しておく
- ECR の repo をタスク定義単位で分ける(例: fluentd/app と fluentd/middleware)
- 実際にサービスをデプロイする際に、common から pull してタスク定義ごとの repo に push しなおす
- タグはコミットハッシュでよい
- それぞれの repo はライフサイクルで単に古い世代を消していくだけでよい
- 個別のrepoには実際にそれぞれのタスク定義が使っているイメージしかpushされていないので、世代が古いものは消して問題ない
- common repoはビルドされてからデプロイまでの間だけ存在していればよいので、古いものはどんどん消してよい (実際に ECS が使うのは個別の repo なので)
難点としては以下ぐらい?
- 同じイメージが複数の repo に重複して配置されるので容量を食う
- デプロイする前にタスク定義を読み、そのタスク定義で使用されているイメージのみ common から pull して個別 repo に push する、という処理を書く必要がある (ツールがほしい)
ライフサイクルを使わないでなんとかする方法
ECS で今使っているイメージのタグ (ロールバックを考慮すると、直前に使っていたタグも含む) を避けて、一定以上古いイメージを消していけばよい。
- 今使っている = ECSサービスで使用しているタスク定義に含まれている
- 直前で使っていた = ECSサービスで使用しているタスク定義のrevisionの直前のrevisionに含まれている
(ECS サービスではない)単体タスクを起動することを考慮すると、最新revisionから現在サービスで使っているタスク定義のrevisionまでを保護対象とする。
タスク定義:revision | 保護 | |
---|---|---|
app:11 | 最新 | * |
app:10 | * | |
app:9 | ECSサービスに設定中 | * |
削除済み | ||
app:7 | 9 からロールバックするのはここ | * |
app:6 | これ以前のは見なくてよい | |
app:5 |
そういうことをするツールが欲しい。