殴り書き。
- docker-compose.ymlで楽したい
- ECS Blue/Greenを使いたい
ディプロイライフサイクルは二つ
- タスク定義の更新に伴ったライフサイクル(インフラの変更に伴うOpsサイクルやDevOpsサイクルにおける環境変数の追加など)
- イメージの更新に伴ったライフサイクル(純粋なアプリケーションイメージの更新に伴うDevOpsサイクル)
2のサンプルはいっぱいあるけど、1のサンプルはみつけられなかった。
1の更新では、過去のタスク定義は不要になる。と考える。
2の更新では、タスク定義は基本変わらず、イメージのみ更新していく。(イメージ変更に伴うrevの積立)
1のライフサイクルが大きく周り、その中で2のライフサイクルが小さく回る感じ。
awsのblue/greenのサンプルは、2を対象としている。
1. タスク定義の更新に伴ったライフサイクル
このライフサイクルのゴールは、タスク定義を更新し、それに伴う、 taskdef.json
と (固定の)appspec.yml
を生成すること。
この二つの成果を用いて、2のライフサイクルを回す。
楽したいのでecs-cli
を用いる。
-
ecs-cli
を用いて、docker-compose.yml
+ecs-params.yml
から、taskdefを登録する。 - この際、既存のtaskdefのrevを全て消す。(変数が変わるため、過去のrevを使うことはほぼないと推測。 汚くしたくない+inactiveのワーニング表示で気持ちいい。)
-
aws describe-tasks
+jq
でtaskdef.json
を生成
っということで、上のルールを、 Makefile
で
準備
docker-compose.yml
2つに分けて書いておくと、良い。
# docker-compose.yml
version: 3.0
services:
app:
build: ./app
image: ...:${VERSION:-latest}
# docker-compose.ecs.yml
version: 3.0
services:
app:
env_file:
- app/.env
logging:
...
ports:
- 0:80
ecs-params.yml
version: 1.0
task_definition:
...
Makefile
上記のルールをMakefile化しておくと楽。
AWS_PROFILE?=default
TASKDEF_NAME:=my-sample-task
AWS_REGION?=ap-northeast-1
CLUSTER_NAME:=my-cluster
.PHONY: init
init: deinit register-task
# init 完了後、taskdef.jsonを生成する。
$(MAKE) -e taskdef.json
# 過去のrev削除
.PHONY: deinit
deinit:
aws ecs --profile=${AWS_PROFILE} list-task-definitions --family-prefix ${TASKDEF_NAME} --query taskDefinitionArns --output text | xargs -J % -t -n1 aws ecs --profile=${AWS_PROFILE} deregister-task-definition --task-definition %
rm taskdef.json
# タスク登録
.PHONY: register-task
register-task:
ecs-cli compose --aws-profile ${AWS_PROFILE} -p ${TASKDEF_NAME} --region ${AWS_REGION} --ecs-params ecs-params.yml --cluster ${CLUSTER_NAME} -f docker-compose.yml -f docker-compose.ecs.yml create --create-log-groups
# taskdef.jsonの生成
taskdef.json:
# コンテナの数に合わせて、適当に、 `select(.name == "app") | .image) = "<APP_IMAGE>" `を増やす。
aws ecs --profile=${AWS_PROFILE} describe-task-definition --task-definition ${ECS_PROFILE} | jq '.taskDefinition | (.containerDefinitions[] | select(.name == "app") | .image) = "<APP_IMAGE>" | del(.taskDefinitionArn, .revision, .status, .requiresAttributes) ' > $@
実行
make init
これで、過去のtaskdefを抹殺+docker-composeにリンクしたtaskdef.json
に更新される・・・はず。
2. イメージの更新に伴ったライフサイクル
次は、イメージを更新していく。
いろいろな例があるけど、個人的には、 release
とか prod
をpushしたら、あとはpipelineに任せたい。
ってことで・・・。
- CodeBuildでポチッとすると、
VERSION=prod docker-compose build app
- ECRの
app:prod
をソースに、CodePipeline (CloudTrailないとイベント飛ばないかも・・・・) - CodePipelineの一発目に、 CodeBuild
retag
- あとは、定番。 taskdef.json + appspec.yamlからBlue/Green
特記することはないので、
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html
こことかを参考に。
ここでは、 retag
だけ。
retag
ECRソースは、taskDetail.json
を渡してくれる。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/file-reference.html#file-reference-ecs-bluegreen
(generated by ECR
)
ってこで、taskDetail.json
を受け取って、 taskDetail.json
をartifactするCodeBuildを作ればいける。
# ソースがECRなので、consoleのエディタなどでコピペ
# とりあえず適当に
version: 0.2
env:
variables:
AWS_DEFAULT_REGION: "ap-northeast-1"
TARGET_TAG: "prod"
phases:
install:
runtime-versions:
docker: 18
commands:
- apt -y update
- apt -y install jq
pre_build:
commands:
- cat ${CODEBUILD_SRC_DIR}/imageDetail.json
- REPO=`jq -rM ".ImageURI" ${CODEBUILD_SRC_DIR}/imageDetail.json | awk -F'@|:' '{print $1;}'`
- DATETIME=`date +%Y%m%d_%H%M`
- printf 'REPO:%s,TAG=%s,DATETIME=%s' $REPO $TARGET_TAG $DATETIME
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
build:
commands:
- docker pull ${REPO}:${TARGET_TAG}
- docker tag ${REPO}:${TARGET_TAG} ${REPO}:${TARGET_TAG}_${DATETIME}
- docker push ${REPO}:${TARGET_TAG}_${DATETIME}
post_build:
commands:
- printf '{"ImageURI":"%s"}' ${REPO}:${TARGET_TAG}_${DATETIME} > imageDetail.json
artifacts:
files:
- imageDetail.json
まとめ
とりあえずこんな感じ。