ecrm - Amazon ECRから不要イメージを安全に削除するOSSを作った

SREチームの藤原です。今回は、AWSのコンテナレジストリであるAmazon ECRから、不要になったコンテナイメージを安全に削除するツールをOSSとして作った話です。

  • Amazon ECRのライフサイクルポリシーでは、設定によっては実際に利用中のイメージを削除してしまうことがあります
  • 現在利用中のイメージを避けて、それ以外の不要なイメージを安全に削除できるCLIツールをOSSとして作成しました

Amazon ECSとECRでのイメージ運用

カヤックでは、コンテナのオーケストレーションにAmazon ECSを主に使用しています。ECSにタスクをデプロイする場合は、イメージのタグにアプリケーションのGitリポジトリのコミットハッシュ(git log -1 --format=%Hで計算した値)を付与してAmazon ECRにpushし、タスク定義ではそのタグを含めたURLを指定しています。

例: タスク定義に指定するイメージURL 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/app:5ff700463f8852c8556fbf9d11dd041eb7234016

このようにデプロイごとに一意のタグを付与することで、どの時点のアプリケーションがイメージに含まれているかが明確になります。デプロイした結果に問題があってECSサービスをロールバックした場合にも、以前動作していたイメージに確実に戻ることができる、というメリットがあります。

しかしこのように運用すると、デプロイを重ねるごとにECRにイメージが溜まっていくことになります。ECRの容量が増加するとコストも増加するため、適宜不要になったイメージを削除する運用が必要になってきます。

ECRに保存されたイメージでは1GBあたり0.10USDの課金が発生します。例えば1GBのイメージを毎日10回(月に20回)保存した場合、1ヶ月で200GB(20USD)のイメージが溜まります。一切削除せずにこれを1年間続けた場合は、2ヶ月目は40USD、3ヶ月目には60USD……と積み上がっていくため、1年間の合計では1560USDもの課金が発生することになります。

ECRライフサイクルポリシーによる削除で発生した問題

ECRでは、ライフサイクルポリシーという設定によって、条件に一致したイメージを自動で削除できます。pushされてからの日数や、世代数を指定して削除する設定をタグのprefixごと指定して、不要イメージを削除できます。

しかし、ライフサイクルポリシーを設定していたあるプロダクトで、ECSサービスで利用中のイメージが削除された結果、新しいタスクが起動できないという問題が発生しました。

調査の結果、次のような事情で、実際にはまだ稼働しているECSタスクが利用しているイメージが削除されたのが原因と判明しました。

  • このプロダクトでは同一AWSアカウントに複数のマイクロサービスがあり、ECSサービスとタスク定義がそれぞれ存在する
  • 複数のマイクロサービスが共通で利用するサイドカーのイメージ(例: ログ転送のためのFluentdなど)は、共通のECRリポジトリを参照している
  • それぞれのマイクロサービスがデプロイするたびに新しいタグでECRにイメージがpushされる
  • 頻繁にデプロイするサービスAがイメージタグを更新して世代が積まれていく
  • めったにデプロイされないサービスBが使用していたイメージタグが、世代管理で削除された

ライフサイクルルールによる削除では、そのイメージが実際にECSで利用されているかは考慮されません。(一旦pullされたイメージがどう利用されているかはコンテナレジストリが関知できることではないので、当然ではあります)

そのため、単純にイメージを世代で管理すると、まだ実際には利用しているイメージが削除されてしまう可能性があります。

世代管理で使用中のイメージが削除される例

pushからの日数を条件にして削除する場合、頻繁にデプロイされないサービスのイメージを確実に保持するためには(頻繁にデプロイするサービスが更新した)大量の世代を残す必要があります。

デプロイするごとに、コミットハッシュではなく特定のprefixを持つタグ(例: service-a-20220830-112233, service-b-20220830-112233 など) を付けて、そのタグを対象に世代管理をすることも検討しました。

しかしライフサイクルポリシーにtagPrefixListの設定を複数行った場合、そのいずれかにマッチすると削除対象になってしまうため、利用中のイメージを確実に残すことはできなさそうでした。複数のサービスが同時にデプロイされる場合は同一のイメージを参照することもあるため、片方のサービスで条件にマッチしてしまうと削除されてしまいます。

ecrm を作った

ということで、ライフサイクルポリシーによらず、ECRから「使用されていない」イメージを削除するCLIツールを作ってみました。

github.com

ここでは、ECRのイメージが「使用されている」とは、以下の状態を指します。

  • ECSクラスタで現在実行中のタスクに含まれるイメージ
  • ECSサービスで指定されているタスク定義に含まれるイメージ
  • ECSタスク定義で指定されているイメージ(最新リビジョンから指定した世代数分)
  • Lambda関数で指定されているイメージ(最新バージョンから指定した世代数分)

これらのイメージはいままさに利用中だったり、デプロイがロールバックされて利用される可能性があるイメージのため、削除してはいけません。

それらのイメージ以外で、かつpushから一定以上の日数が経過したイメージは安全に削除できるものとして、削除を実行するツールです。

ecrm の使い方

設定ファイルの生成

AWSアカウントのcredentialを適切に環境変数などで設定した状態で ecrm generateを実行すると、アカウントに存在するECRやECS、Lambdaのリソースを元に、設定ファイル(ecrm.yaml)を自動生成します。

クラスタやタスク定義はワイルドカードのパターンで指定できるため、prefixが記号([_/-])で区切れてまとめられそうなものは、適宜まとめたパターンで設定ファイルを生成するようになっています。

clusters:
  - name_pattern: prod-*
  - name_pattern: stg-*
task_definitions:
  - name_pattern: prod-*
    keep_count: 5
  - name_pattern: stg-*
    keep_count: 5
lambda_functions:
  - name_pattern: prod-*
    keep_count: 5
    keep_aliase: true
  - name_pattern: stg-*
    keep_count: 5
    keep_aliase: true
repositories:
  - name_pattern: prod/*
    expires: 30d
    keep_count: 5
    keep_tag_patterns:
      - latest
  - name_pattern: stg/*
    expires: 30d
    keep_count: 5
    keep_tag_patterns:
      - latest

デフォルトでは以下の条件にマッチしたイメージを残して、それ以外を削除する設定を生成します。

  • ECSタスク定義に含まれるもの
    • 最新から5世代
    • 実際にECSサービスやタスクで利用中のものがあればそれも
  • Lambda関数で指定されているもの
    • 最新バージョンから5世代
    • バージョンにaliasが設定されているもの
  • イメージがpushされれてから30日以内
  • イメージに latest タグが付いているもの

対象のリポジトリ、ECSクラスタやタスク定義、Lambda関数の名前、保存する世代、ECRにpushされてからの日数、保護するタグのパターンは設定ファイルで適宜カスタマイズが可能です。

削除可能なイメージの確認と削除

ecrm plan を実行すると、AWSアカウント内に存在するECSやLambdaをスキャンし、ECRから安全に削除可能なイメージを検知し、情報を表示します。

EXPIREDの欄に表示されているのが、安全に削除可能なイメージ数と容量です。

$ ecrm plan

       REPOSITORY      |    TOTAL     |    EXPIRED    |    KEEP      
-----------------------+--------------+---------------+--------------
      dev/app          | 732 (594 GB) | -707 (574 GB) | 25 (21 GB)   
      dev/nginx        | 720 (28 GB)  | -697 (27 GB)  | 23 (875 MB)  
      prod/app         | 97 (80 GB)   | -87 (72 GB)   | 10 (8.4 GB)  
      prod/nginx       | 95 (3.7 GB)  | -85 (3.3 GB)  | 10 (381 MB)  

ecrm delete を実行すると、planで検出された、安全に削除可能と思われるイメージを実際にECRから削除します。

ecrm利用上の注意

ecrmでは現在、ECS(タスク、タスク定義、サービス)とLambda関数をスキャンして利用中のイメージを検知しますが、AWS App RunnerやAmazon EKSで利用されているイメージについては一切関知しません。App RuunerやEKSをご利用の場合はご注意ください。

まとめ

  • Amazon ECRのライフサイクルポリシーは、設定によっては実際に利用中のイメージを削除してしまうことがあります
  • 現在利用中のイメージを避けて、それ以外の不要なイメージを安全に削除できるCLIツールをOSSとして作成しました

カヤックでは、運用目的に応じたツールを必要に応じて作成できるSREを募集しています!

hubspot.kayac.com