2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actions上でecrmを使って不要イメージを安全に定期削除する

Posted at

tl;dr

  • fujiwara/ecrmを使うことで、古く、かつ使っていないECR上のコンテナイメージを削除できる
  • GitHub Actions上でecrmを実行することで、定期的な削除が実現できる

はじめに

私の所属する開発チームでは

  • EC2→ECSへの移行や単純な開発速度の向上に伴って、コンテナイメージのpush回数が増えている
  • acidlemon/mirage-ecsやTerraformを使ってAWS上に気軽に検証環境を立てられるようになったことで、実環境用だけでなく、開発用のイメージもECRにpushするようになった
  • Lambdaの開発zipタイプではなくコンテナイメージを使うケースが増えた

などの要因が重なって、今年に入ってから特にECRに保存されるイメージが増加しています。
ECRはS3などのサービスと同じくストレージ容量に対しての課金が発生するので、いまのうちに定期的な削除の仕組みを構築しておく必要がありました。

そこで、ecspressoなどのツールでお世話になっているfujiwaraさんのツールのひとつ、fujiwara/ecrmをつかうことで、安全にコンテナイメージを削除する仕組みをGitHub Actions上に構築することにしました。

この記事ではなにがわかるのか

この記事では、

  1. ecrmについて
  2. 手元でecrmを動かしてみる
  3. GitHub Actionsでecrmを実行できるようにする

の3点について書いています。

ecrmとは

ecrmは、ECRにあるコンテナイメージを安全に削除するツールです。

コンテナイメージの機械的な削除自体は、ECRのライフサイクルポリシーという機能が提供されています。
ECRに登録されてからの日数や、世代を元に削除することはecrmでもライフサイクルポリシーでも可能です。
ライフサイクルポリシーと大きく違う点は、イメージが使われているかどうかを確認したうえで、ポリシーに基づく削除が実行できることです。

どういうことかというと、ecrmの削除処理はECRの他にECSとLambdaもスキャンし、使用中のイメージについては日数や世代が削除ルールにマッチしていても削除対象から除外してくれます。
これによって、使用中のイメージを削除してしまった、というトラブルが発生しづらくなります。

詳しくはこちらのブログ記事をどうぞ: https://techblog.kayac.com/ecrm-oss

手元でecrmを実行する

まずは手元で動かしてみて、機能を確認します。

インストール

Macの場合、brew経由でインストールすることができます。

brew install fujiwara/tap/ecrm

Linuxの場合はリリースページからバイナリをダウンロードし、適切にパスを通すことで実行できるようになります。
私の手元の環境はLinuxなので、 ~/.local/bin にバイナリを置き、パスを通して実行しています。

設定ファイルを生成する

ecrmはAWS環境を確認して設定ファイルを生成する機能を持っています。
環境変数などに必要な情報を展開し、ECS、Lambda,、ECRにアクセスできる権限を持つIAMの認証ができる状態にしたうえで、

ecrm generate

をすると、該当の環境の

  • ECS
    • クラスター
    • サービス
    • タスク定義
  • Lambda
  • ECRリポジトリ

をスキャンして、

  • すべてのECRリポジトリを対象とする
  • すべてのECS、Lambdaで使われている場合は削除処理から除外する

デフォルト設定がされた ecrm.yaml が生成されます。
これをそのまま使うことも、yamlファイルを変更して設定を変更することもできます。
削除対象のECRリポジトリや、利用状態のチェック対象のECS・Lambdaリソース名はワイルドカードを使って指定することができます
必要に応じて設定してください。

削除の実行計画を確認する

以下を実行すると、ecrm.yaml の設定を読み込んで削除の実行計画が表示されます。

ecrm plan

リポジトリごとに削除前後のイメージ数・容量や削減量を確認できます。

削除を実行する

planで問題なければ、

ecrm delete

を実行することで、実際の削除処理を実行できます。
オプションを指定しなければ、delete時に改めてplanが実行され、y/n入力での削除の確認が行われます。
ここでyを入力することで、BatchDeleteImageリクエストが発行され、イメージが削除されます。

GitHub Actionsなどで実行する場合は、y/nの入力で処理が止まってしまうため、--force オプションを設定することで確認をスキップします。

 ecrm delete --force

GitHub Actionsで削除を定期実行する

手元で実行計画が適切に動くことを確認したら、GitHub Actionsで実行できるようにしていきましょう。

IAMを設定する

以下にIAMロールの設定のサンプルを、Terraformで書いています。
各ポリシーが何で使われているかは、コード中にコメントの形で記載しています。

ecrm用のIAMロールを設定するときに注意が必要なのは、一部すべてのリソースを参照する権限が必要な点です。
必要になるActionはReadなもののみなので、今回は * を許容しています。

iam.tf
resource "aws_iam_role" "cleanup_ecr" {
  name = "github-actions-example-ecrm-runner"

  inline_policy {
    name   = "remove-example-app-images"
    policy = data.aws_iam_policy_document.cleanup_ecr.json
  }

  # 以下は別途定義してある想定。
  # 自分の環境に合わせて、GitHub ActionsのrunnerがこのロールをAssume Roleできるように設定してください。
  assume_role_policy = data.aws_iam_policy_document.oidc_github_actions.json 
}

data "aws_iam_policy_document" "cleanup_ecr" {
  # NOTE: GitHub Actionsで認証するための権限
  statement {
    sid    = "GetAuthorizationToken"
    effect = "Allow"
    actions = [
      "ecr:GetAuthorizationToken",
    ]
    resources = [
      "*"
    ]
  }

  # NOTE: ecrm.yamlで定義されているリポジトリを探すための権限
  statement {
    sid    = "DescribeContainerImage"
    effect = "Allow"
    actions = [
      "ecr:BatchCheckLayerAvailability",
      "ecr:DescribeImages",
      "ecr:DescribeRegistry",
      "ecr:DescribeRepositories",
      "ecr:DescribePullThroughCacheRules",
      "ecr:ListImages",
    ]
    resources = ["*"]
  }

  # NOTE: 削除を実行するための権限
  statement {
    sid    = "DeleteContainerImage"
    effect = "Allow"
    actions = [
      "ecr:BatchDeleteImage",
      "ecr:DeletePullThroughCacheRules",
    ]
    resources = [
      # 必要に応じて、削除対象のリポジトリのARNを設定してください。
      # `*` でもいいですが、ecrmに限らず削除・変更系の処理は対象を絞るのをおすすめします。
    ]
  }

  # NOTE: 実行中のコンテナイメージを検索するための権限(ECS関連)
  statement {
    sid    = "DescribeECSClusters"
    effect = "Allow"
    actions = [
      "ecs:ListAttributes",
      "ecs:ListTasks",
      "ecs:ListServiceDeployments",
      "ecs:DescribeServiceDeployments",
      "ecs:DescribeServiceRevisions",
      "ecs:DescribeServices",
      "ecs:DescribeTaskSets",
      "ecs:ListContainerInstances",
      "ecs:DescribeContainerInstances",
      "ecs:DescribeTasks",
      "ecs:DescribeClusters",
    ]
    resources = [
      # NOTE: ecrm.yamlで定義したクラスターと、その配下のリソースが読めるようにする
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:cluster/YOUR_ECS_CLUSTER_NAME",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:container-instance/YOUR_ECS_CLUSTER_NAME/*",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:service/YOUR_ECS_CLUSTER_NAME/*",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:service-deployment/YOUR_ECS_CLUSTER_NAME/*/*",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:task/YOUR_ECS_CLUSTER_NAME/*",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:service-revision/YOUR_ECS_CLUSTER_NAME/*/*",
      "arn:aws:ecs:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:task-set/YOUR_ECS_CLUSTER_NAME/*/*",
    ]
  }
  
  # NOTE: 実行中のコンテナイメージを検索するための権限(タスク定義関連)
  statement {
    sid    = "DescribeCurrentTasks"
    effect = "Allow"
    actions = [
      "ecs:ListServices",
      "ecs:ListServicesByNamespace",
      "ecs:ListTaskDefinitionFamilies",
      "ecs:ListTaskDefinitions",
      "ecs:DescribeTaskDefinition",
      "ecs:ListClusters",
    ]
    resources = [
      "*",
    ]
  }

  # NOTE: 実行中のコンテナイメージを検索するための権限(Lambda関連)
  statement {
    sid    = "DescribeCurrentLambdaFunctions"
    effect = "Allow"
    actions = [
      "lambda:ListProvisionedConcurrencyConfigs",
      "lambda:ListFunctionEventInvokeConfigs",
      "lambda:ListFunctions",
      "lambda:ListFunctionsByCodeSigningConfig",
      "lambda:ListVersionsByFunction",
      "lambda:ListAliases",
      "lambda:ListEventSourceMappings",
      "lambda:ListFunctionUrlConfigs",
      "lambda:ListLayerVersions",
      "lambda:ListLayers",
      "lambda:ListCodeSigningConfigs",
    ]
    resources = [
      "*",
    ]
  }
}

GitHub Actionsで実行する

IAMを設定したら、workflowを設定します。

name: 'ecrm実行'

# 平日朝9時の定期実行+手動実行
on:
  schedule:
    - cron: '0 0 * * MON-FRI' # 平日朝9時
  workflow_dispatch:

env:
  AWS_DEFAULT_REGION: YOUR_DEFAULT_REGION
  OIDC_AUDIENCE: YOUR_OIDC_AUDIENCE
  GH_TOKEN: ${{ github.token }} # fujiwara/ecrmのcomposite actionを実行するために必要

# OIDC等で必要
permissions:
  id-token: write
  packages: write
  contents: read

jobs:
  # どの環境でも実行内容は同じなのでmatrixを使って全環境並列で実行する(おこのみで)
  remove-images:
    runs-on: ubuntu-latest
    steps:
    - name: "デフォルトブランチにcheckoutする"
      uses: actions/checkout@v4
    - name: "ecrmをインストールする"
      uses: fujiwara/ecrm@main

    - name: "デプロイで使うAWSのアクセストークンをOIDCで認証を行い払い出す"
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-region: ${{ env.AWS_DEFAULT_REGION }}
        audience: ${{ env.OIDC_AUDIENCE }}
        role-to-assume: "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/YOUR_ECRM_ROLE}"
        role-duration-seconds: 900
    - name: "ECRにログインする"
      uses: aws-actions/amazon-ecr-login@v2
      id: login-ecr

    - name: "ecrm delete"
      run: |
        ecrm delete --force

このサンプルは、ecrm.yaml がリポジトリ直下にコミットされている想定で書かれています。

  • ecrm.yaml をリポジトリ直下に置きたくない
  • 環境ごとにリポジトリ名違うとかの都合でyaml複数用意したい
  • 単純に別のファイル名を使いたい

などの場合は、ecrmのコマンドラインオプションで任意のyamlを渡すことができるので、最後のステップを以下のように変更することで対応できます。

    - name: "ecrm delete"
      run: |
        ecrm --config /path/to/your-config.yaml delete --force

参考資料など

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?