2020年11月12日木曜日
AWS ECRのライフサイクルポリシーを設定して自動クリーンアップ
今回は軽く、AWS ECR (Elastic Container Registry) の小ネタです。
AWSでコンテナを使って何かする場合ECRにコンテナイメージを保存しておくことが多いと思いますが、日ごろからイメージをよくビルド&プッシュしているとどんどんイメージが増えていきますね。
イメージサイズがものすごく大きいとかでなければそれほど料金を食うわけでもないのですが、まず使わない古いイメージをずっと保存しておくこともないよなーと思ったのでそのあたりを設定してみました。
ECRではライフサイクルポリシーという機能があり、いろいろな条件を定めてイメージを自動クリーンアップすることができるんですね。
Amazon ECRのライフサイクルポリシーでコンテナイメージのクリーンアップ | Amazon Web Services ブログ
設定できる条件は
- タグ
- イメージ数
- プッシュされてからの経過日数
今回は、「タグが latest
ではない」かつ「プッシュされて90日以上」の条件に合うイメージを削除するポリシーを設定してみました。
AWSコンソールで設定する場合は、ECRのコンソールでリポジトリを選択して左のメニューの Lifecycle Policy
をクリックすると設定画面があります。
「テストルールの編集」ボタンを押すとポリシーを適用した結果が確認できる(実際に保存されているイメージには何もしない)テスト用ページに移動するので、まずはそこで試してみるのがいいですね。
さて、ライフサイクルポリシーは「ルール」を優先順位つきで好きな数設定することで構成します。ルール作成画面はこうなっていて、一致条件は「イメージをプッシュしてから」「次の数値を超えるイメージ数」が選べます。
今回作りたい条件は「タグが latest
ではない」かつ「プッシュされて90日以上」ということで、
- タグ付け済("latest")、次の数値を超えるイメージ数(1)
- すべて、イメージをプッシュしてから(90日)
という2つのルールを作ってこのようになります。
ルールを作ればあとは自動でそれに従ってイメージが毎日掃除されるので、とても楽ですね😊
ここまではコンソールで操作してきましたが、リポジトリが多い場合やコンソール面倒だと言う場合はもちろんCLIが使えます。
put-lifecycle-policy — AWS CLI 2.1.0 Command Reference
まずは先ほどのルールをJSON化して policy.json
とでもファイルを作ります。
{
"rules": [
{
"rulePriority": 1,
"description": "keep latest image",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["latest"],
"countType": "imageCountMoreThan",
"countNumber": 1
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "expire images older than 90 days",
"selection": {
"tagStatus": "any",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 90
},
"action": {
"type": "expire"
}
}
]
}
今回は全リポジトリに適用したかったのでシェルスクリプトを作りました。
#!/bin/sh
# ECRリポジトリにライフサイクルポリシーを設定する
for REPO in $(aws ecr describe-repositories --query "repositories[*].{name:repositoryName}" --output text)
do
echo "repository: $REPO"
aws ecr put-lifecycle-policy --lifecycle-policy-text "file://policy.json" --repository-name $REPO
done
これで一括適用ができますね💪
比較的簡単に設定ができるので、「うわっ…ECRのイメージ、多すぎ…?」と気づいた際には設定してみてはいかがでしょうか。
ちなみにterraformだとaws_ecr_lifecycle_policy
リソースで設定できるようです。
aws_ecr_lifecycle_policy | Resources | hashicorp/aws | Terraform Registry
2018年12月7日金曜日
CircleCIでAWS ECSに自動デプロイする(おまけで環境面の切り替えも)
こんにちは、シタテルの茨木です。
今回はCircleCIでDockerをビルドし、そのままAWS ECR/ECSにデプロイする為の設定例を紹介します。
Nuxt.jsアプリ用に作ったものですが、言語やAPサーバが違っても共通の部分がほとんどです。
PRマージからの自動デプロイ、いいですよね。
https://circleci.com/docs/2.0/ecs-ecr/
https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr
基本的にはCircleCI公式ガイドの上記リンクを参考にしていますが、元ネタはterraform前提だったりでなかなか大仰です。(下記に説明漏れがあったら原典を見てください…)
今回紹介するのはリンク先のスクリプト類を簡素化し、少し機能を追加(リポジトリ名からデプロイ先の環境面を切り替えできるように)したものです。
以下で省略しているけれど必要なモノ
- ECS/ECRの設定
これだけで別の記事が書けてしまうので割愛します
ecsTaskExecutionRoleの割り当てとか忘れがちですかね
- githubリポジトリ
言わずもがな。CircleCIとの接続設定はされているものとします
- Dockerfile
リポジトリのルートに。Dockerfileの書き方は解説しません
- requirements.txt
リポジトリのルートに。awscliを使えるようにします
https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr/blob/master/requirements.txt
- CircleCIの環境変数設定
github上にaccess_keyを晒さなくていいように、入れておきます
https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project
この手順で設定した環境変数はCircleCIのジョブから参照できます
.circleci/config.yml:build
version: 2
jobs:
~省略~
build:
docker:
- image: circleci/node:10-stretch
steps:
- checkout
- setup_remote_docker
- run:
name: Setup common environment variables
command: |
echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
echo 'export FULL_IMAGE_NAME="${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECR=REPOSITORY_NAME}:${CIRCLE_SHA1}"' >> $BASH_ENV
- run:
name: Build image
command: |
docker build -t $FULL_IMAGE_NAME .
- run:
name: Save image to an archive
command: |
mkdir docker-image
docker save -o docker-image/image.tar $FULL_IMAGE_NAME
- persist_to_workspace:
root: .
paths:
- docker-image
~省略~
さっそくですがビルドジョブです。
- YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください
- image: circleci/node:10-stretch
node:10を使っていますが、これはビルドする対象がNuxt.jsアプリだからですね。Dockerビルド時に必要なモノに合わせて修正してください
.circleci/config.yml:deploy
~省略~
deploy:
docker:
- image: circleci/python:3.6.1
environment:
AWS_DEFAULT_OUTPUT: json
steps:
- checkout
- setup_remote_docker
- attach_workspace:
at: workspace
- restore_cache:
key: v1-{{ checksum "requirements.txt" }}
- run:
name: Install awscli
command: |
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
- save_cache:
key: v1-{{ checksum "requirements.txt" }}
paths:
- "venv"
- run:
name: Load image
command: |
docker load --input workspace/docker-image/image.tar
- run:
name: Setup target environment variables
command: |
echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV
source $BASH_ENV
- run:
name: Setup common environment variables
command: |
echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
echo 'export ECS_CLUSTER_NAME="YOUR-ECS-CULSTER-NAME-PREFIX${TARGET}"' >> $BASH_ENV
echo 'export ECS_SERVICE_NAME="YOUR-ECS-SERVICE-NAME-PREFIX${TARGET}"' >> $BASH_ENV
- run:
name: Push image
command: |
. venv/bin/activate
eval $(aws ecr get-login --region ap-northeast-1 --no-include-email)
docker push $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$ECR_REPOSITORY_NAME:$CIRCLE_SHA1
- run:
name: Deploy
command: |
. venv/bin/activate
export AWS_DEFAULT_REGION="ap-northeast-1"
export ECS_TASK_FAMILY_NAME="YOUR-ECS-TASK-NAME-PREFIX${TARGET}"
export ECS_CONTAINER_DEFINITION_NAME="YOUR-ECS-CONTAINER-DEF-NAME-PREFIX${TARGET}"
export EXECUTION_ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/ecsTaskExecutionRole"
bash ./deploy.sh
~省略~
デプロイジョブです。
- ブランチ名から環境名へ
echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV
ブランチ名に応じた値(developブランチなら"dev")を環境変数に入れています。これをsuffixとして使い、デプロイ先のクラスタ名等に反映しています
- YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください
余談ですが、ECSはクラスタ・サービス・タスク・タスク定義といろいろ設定しないといけないのですが、なかなか直感的にわかりにくくてイマイチな印象です
.circleci/config.yml:workflows
~省略~
workflows:
version: 2
build-deploy:
jobs:
- build:
filters:
branches:
only:
- develop
- /release\/.*/
- master
- deploy:
requires:
- build
filters:
branches:
only:
- develop
- /release\/.*/
- master
この辺はよしなに
deploy.sh
#!/usr/bin/env bash
set -eo pipefail
# more bash-friendly output for jq
JQ="jq --raw-output --exit-status"
deploy_cluster() {
make_task_def
register_definition
if [[ $(aws ecs update-service --cluster $ECS_CLUSTER_NAME --service $ECS_SERVICE_NAME --task-definition $revision | \
$JQ '.service.taskDefinition') != $revision ]]; then
echo "Error updating service."
return 1
fi
# wait for older revisions to disappear
# not really necessary, but nice for demos
for attempt in {1..30}; do
if stale=$(aws ecs describe-services --cluster $ECS_CLUSTER_NAME --services $ECS_SERVICE_NAME | \
$JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$revision\") | .taskDefinition"); then
echo "Waiting for stale deployment(s):"
echo "$stale"
sleep 30
else
echo "Deployed!"
return 0
fi
done
echo "Service update took too long - please check the status of the deployment on the AWS ECS console"
return 1
}
make_task_def() {
task_template='[
{
"name": "%s",
"image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s",
"essential": true,
"portMappings": [
{
"containerPort": 80
}
],
"environment": [
{
"name": "TARGET",
"value": "%s"
}
]
}
]'
task_def=$(printf "$task_template" $ECS_CONTAINER_DEFINITION_NAME $AWS_ACCOUNT_ID $AWS_DEFAULT_REGION $ECR_REPOSITORY_NAME $CIRCLE_SHA1 $TARGET)
}
register_definition() {
if revision=$(aws ecs register-task-definition --requires-compatibilities FARGATE --cpu 256 --memory 1024 --network-mode awsvpc --execution-role-arn $EXECUTION_ROLE_ARN --container-definitions "$task_def" --family $ECS_TASK_FAMILY_NAME | $JQ '.taskDefinition.taskDefinitionArn'); then
echo "New deployment: $revision"
else
echo "Failed to register task definition"
return 1
fi
}
deploy_cluster
デプロイジョブから呼び出すスクリプトです。
task_templateの内容を色々弄るとカスタマイズできます
設定可能な項目はこちら
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-task-definition.html
ここでは、environmentで環境変数TARGETを設定しています
ECSがdocker runする際に引数として付与され、docker内のアプリから環境変数として参照可能になります
実際には、nuxt.config.js内でTARGETを参照し、APIサーバの向け先を切り替えたり等しています
以上、ご参考になれば幸いです。