ecrmを定期実行してECRの不要イメージを安全に削除する
こんにちは、株式会社スマートラウンドSREの@shonansurvivorsです。
当社はプロダクトのコンテナイメージをAmazon ECRに保存しています。
今回、コスト削減の一環でECRに溜まっていく不要イメージを削除することにしました。
その際、ecrmを定期的に実行して削除するようにしました。
ecrmとは
ecrmは「使用されていない」ECRのイメージを削除するOSSです。
ECRのライフサイクルポリシーは、世代数や、イメージがpushされてからの日数などに基づいて古いイメージを削除してくれますが、ECSタスク等で使用中のイメージを削除してしまう懸念があります。
ecrmはそうしたこと無く、安全に古いイメージを削除することのできる大変ありがたいOSSです。
詳細な仕様はecrmを紹介するブログを参照ください。
定期削除の構成図
ecrmはCodeBuild上で実行することにしました。また、CodeBuildの定期実行にあたっては、EventBridge Schedulerを使うことにしました。
なお、構成図中にECSやLambdaが登場していますが、これはecrmが不要イメージの洗い出しにあたり、ECSやLambdaを参照するためです。
環境
- ecrm v0.3.3
- Terraform v1.4.6
- AWS Provider v4.64.0
ecrm.yamlの作成
ecrmによるイメージ削除にあたっては、専用のコンフィグファイルが必要です。ecrm generate
で作成可能なので、ローカルで実行します。
brew install fujiwara/tap/ecrm
ecrm generate
clusters:
- name_pattern: example-*
task_definitions:
- name_pattern: example-*
keep_count: 5
lambda_functions: []
repositories:
- name_pattern: example-*
expires: 14d
keep_count: 5
keep_tag_patterns:
- latest
設定ファイルの詳細は以下を参照ください。
Terraformファイル構成
先ほどの構成図で登場した各種AWSリソースはTerraformで作成することにしました。
Terraformおよび各種yaml(yml)のファイル構成は以下です。tfファイルは説明のためにかなり細かくファイルを分けていますが、実際は1ファイルにまとめるなりして良いと思います。
├── ecrm.yaml
├── ecrm_buildspec.yml
├── ecrm_codebuild.tf
├── ecrm_codebuild_iam.tf
├── ecrm_codebuild_scheduler.tf
└── ecrm_codebuild_scheduler_iam.tf
CodeBuild(ecrm)実行用のIAM
ecrmを実行させるCodeBuild用のIAMは以下の通りです。ECRのイメージを削除する権限のほか、ECSやLambdaを参照する権限を付与しています。
data "aws_iam_policy_document" "codebuild_assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["codebuild.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "codebuild_ecrm" {
name = "codebuild-ecrm"
assume_role_policy = data.aws_iam_policy_document.codebuild_assume_role.json
managed_policy_arns = [
aws_iam_policy.codebuild_ecrm.arn,
]
}
resource "aws_iam_policy" "codebuild_ecrm" {
name = "codebuild-ecrm"
policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource" : [
"arn:aws:logs:region:account_id:log-group:/aws/codebuild/codebuld_project_name",
"arn:aws:logs:region:account_id:log-group:/aws/codebuild/codebuld_project_name:*"
]
},
{
"Effect" : "Allow",
"Action" : [
"lambda:Get*",
"lambda:List*",
"ecs:Describe*",
"ecs:Get*",
"ecs:List*",
"ecr:BatchGet*",
"ecr:BatchDelete*",
"ecr:Describe*",
"ecr:Get*",
"ecr:List*"
],
"Resource" : "*"
}
]
}
)
}
CodeBuild
CodeBuildプロジェクトは以下の通りです。
resource "aws_codebuild_project" "ecrm" {
name = "ecrm"
description = ""
build_timeout = "60" # minutes
service_role = aws_iam_role.codebuild_ecrm.arn
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
source {
type = "GITHUB"
location = "https://github.com/organization/repository.git"
buildspec = "path/to/ecrm_buildspec.yml"
git_clone_depth = 1
}
source_version = "main"
}
buildspec
CodeBuildでの実行内容を定義するbuildspecは以下の通りです。
version: 0.2
env:
shell: bash
variables:
ECRM_VERSION: 0.3.3
phases:
install:
commands:
- curl -sL -O https://github.com/fujiwara/ecrm/releases/download/v${ECRM_VERSION}/ecrm_${ECRM_VERSION}_linux_amd64.tar.gz
- tar xzvf ecrm_${ECRM_VERSION}_linux_amd64.tar.gz
- sudo install ecrm /usr/local/bin/ecrm
- ecrm --version
build:
commands:
- ecrm --config path/to/ecrm.yaml delete --force
ecrmは、カレントディレクトリのecrm.yaml
を自動的に読み込んでくれます。今回はリポジトリルートには存在しない想定で、--config
オプションでパスを指定しています。
通常のdelete
では、削除候補のイメージを洗い出した後に削除するかしないかを確認してきます。
REPOSITORY | TOTAL | EXPIRED | KEEP
----------------------+-------------+-------------+--------------
example-web | 55 (924 MB) | -5 (84 MB) | 50 (840 MB)
example-app | 55 (19 GB) | -5 (1.7 GB) | 50 (17 GB)
Do you delete 5 images on example-app? (y/n) [n]:
今回はCodeBuildによる自動実行なので、--force
オプションを付けることで確認無しでそのまま削除させます。
EventBridge Scheduler用のIAM
CodeBuildを定期実行するためのEventBridge Scheduler用のIAMは以下の通りです。
なお、これは別途マネジメントコンソールで作成したIAMをそのままコード化しています。
data "aws_caller_identity" "current" {}
resource "aws_iam_role" "codebuild_ecrm_scheduler" {
name = "codebuild-ecrm-scheduler"
path = "/service-role/"
assume_role_policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Service" : "scheduler.amazonaws.com"
},
"Action" : "sts:AssumeRole",
"Condition" : {
"StringEquals" : {
"aws:SourceAccount" : data.aws_caller_identity.current.account_id
}
}
}
]
}
)
managed_policy_arns = [
aws_iam_policy.codebuild_ecrm_scheduler.arn
]
}
resource "aws_iam_policy" "codebuild_ecrm_scheduler" {
name = "${var.project_name}-${var.env}-codebuild-ecrm-scheduler"
policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"codebuild:StartBuild"
],
"Resource" : [
aws_codebuild_project.ecrm.arn
]
}
]
}
)
}
EventBridge Scheduler
EventBridge Schedulerは以下の通りです。日本時間の月曜8時に実行するように設定しています。
resource "aws_scheduler_schedule" "codebuild_ecrm" {
name = "codebuild-ecrm"
group_name = "default"
state = "ENABLED"
flexible_time_window {
maximum_window_in_minutes = 60
mode = "FLEXIBLE"
}
schedule_expression = "cron(0 8 ? * 2 *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = aws_codebuild_project.ecrm.arn
input = jsonencode({})
role_arn = aws_iam_role.codebuild_ecrm_scheduler.arn
retry_policy {
maximum_event_age_in_seconds = 86400
maximum_retry_attempts = 2
}
}
}
実行結果
以上で、必要なリソースの構築は完了です。
指定した時間になると、EventBridge Scheduler経由でCodeBuildが起動し、ECRのイメージが削除されます。
// 略
REPOSITORY | TOTAL | EXPIRED | KEEP
----------------------+-------------+-------------+--------------
example-web | 55 (924 MB) | -5 (84 MB) | 50 (840 MB)
example-app | 55 (19 GB) | -5 (1.7 GB) | 50 (17 GB)
// 略
2023/06/11 23:31:41 [info] Deleted 5 images on example-web
// 略
2023/06/11 23:31:42 [info] Deleted 5 images on example-app
[Container] 2023/06/11 23:31:42 Phase complete: BUILD State: SUCCEEDED
[Container] 2023/06/11 23:31:42 Phase context status code: Message:
[Container] 2023/06/11 23:31:42 Entering phase POST_BUILD
[Container] 2023/06/11 23:31:42 Phase complete: POST_BUILD State: SUCCEEDED
[Container] 2023/06/11 23:31:42 Phase context status code: Message:
おまけ1:Slack通知
CodeBuildの開始や終了をSlack通知したい場合は、aws_codestarnotifications_notification_rule
を作成するのがお手軽です。
resource "aws_codestarnotifications_notification_rule" "codebuild_ecrm" {
name = aws_codebuild_project.ecrm.name
resource = aws_codebuild_project.ecrm.arn
detail_type = "BASIC"
event_type_ids = [
"codebuild-project-build-state-failed",
"codebuild-project-build-state-succeeded",
"codebuild-project-build-state-in-progress",
"codebuild-project-build-state-stopped",
]
target {
address = aws_sns_topic.this.arn # AWS ChatbotがサブスクライブしているSNSトピックを指定
}
}
AWS Chatbotを通して以下のようなSlack通知を行えます。これだけでは処理結果の詳細はわかりませんが、定期的に実行されているかどうかはわかります。
なお、SNSトピックのアクセスポリシーではcodestar-notifications.amazonaws.com
からのPublishを許可するようにしてください。
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowPublish",
"Effect": "Allow",
"Principal": {
"Service": [
"codestar-notifications.amazonaws.com",
"events.amazonaws.com"
]
},
"Action": "sns:Publish",
"Resource": "*"
}
]
}
おまけ2:Security Hub
Security Hubのセキュリティ基準の1つである「AWS 基礎セキュリティのベストプラクティス v1.0.0」では、
[ECR.3] ECR リポジトリには、少なくとも 1 つのライフサイクルポリシーが設定されている必要があります
https://docs.aws.amazon.com/securityhub/latest/userguide/ecr-controls.html#ecr-3
というコントロールがあります。
全てのECRについて、ライフサイクルポリシーを使わず、ecrmで不要イメージを削除する運用を行うのであれば、このコントロールは無効化してしまって良いかと思います。
Discussion