PHP Slim 環境構築(16) ECS(EC2)+ローリングデプロイ
Introduction
前回は、ECS(Fargate)の構築を行いました。
今回は、一つ戻って、ECS(EC2)のDeployを試してみます。
この一連のシリーズは、自分への備忘録が第一目的のため、だいぶ不親切です。
申し訳ございません。
ローリングデプロイ
手動
手動で行う方法は、前々回に記載していますが、適当なec2のshell上で、docker build → docker push → task 更新 → サービス更新です (詳細は割愛)。
# docker build -t hoge-repo:latest --build-arg environment=devaws -f compose/web_hoge/Dockerfile.ecs .
# docker tag hoge-repo:latest $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo:latest
# docker push $ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-repo:latest
コンソールでタスク更新。そのタスクを使ってサービス更新。
バッチ
次に、Code Deployなどを使わずにバッチでやってみます。なお、デプロイの種類はローリングデプロイです。Blue/Greenはまた後程。。
バッチは、--service-updateオプションを付けるとサービス更新まで行うようにしています。付けない場合は、タスク更新までです。
なお、バッチ内でawsコマンドを実行しているのでaws configureによる設定済みであると想定しています。また、json解析にjqコマンドを用いています。
インストールされていない場合は、yum install jqコマンドでインストールしておいてください。
#!/bin/sh
# サービス更新するかどうか
SERVICE_UPDATE=0
# 引数解析
for OPT in "$@"
do
case $OPT in
--service-update)
SERVICE_UPDATE=1
shift 1
;;
esac
done
# Git update (必要ならgit checkoutなども)
git pull
# ECRのログイン手続き
$(aws ecr get-login --no-include-email)
# ECRのリポジトリ名
REPOSITORY="slimtemplate"
# ECSに定義済みのtask名とcluster名
TASK_NAME="hoge-task"
CLUSTER_NAME="hoge-cluster"
# Dockerタグ
DOCKER_TAG="hoge-repo"
# docker buildする際にenvironment環境変数に引き渡す値
ENVIRONMENT="devaws"
# ECRのDockerイメージに付けられるタグ
NOW=$(date -u "+%Y%m%d%H%M%S")
# バッチ実行者のAWSアカウント
ACCOUNT_ID=$(aws sts get-caller-identity | grep Account | grep -oE '[0-9]+')
# docker buildを行う。更新の有無を確認するために、ビルド前後のイメージのidを取得する
OLD_IMAGE_ID=$(docker images ${DOCKER_TAG}:latest -q)
docker build -t ${DOCKER_TAG}:latest --build-arg environment=${ENVIRONMENT} -f compose/web_hoge/Dockerfile.ecs .
NEW_IMAGE_ID=$(docker images ${DOCKER_TAG}:latest -q)
if [ "${OLD_IMAGE_ID}" == "${NEW_IMAGE_ID}" ]; then
# 更新なし
echo "Docker image is not updated."
exit 0
fi
# タグ付けおよびECRへのPUSH
docker tag ${DOCKER_TAG}:latest ${DOCKER_TAG}:${NOW}
docker tag ${DOCKER_TAG}:${NOW} ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:${NOW}
docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:${NOW}
docker tag ${DOCKER_TAG}:latest ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:latest
docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:latest
# タスクを更新
aws ecs describe-task-definition --task-definition ${TASK_NAME} > /tmp/task.json
cat <<__COMMAND__ | tr '\n' ' ' > /tmp/task.cmd
aws ecs register-task-definition --family ${TASK_NAME} --container-definitions
'$(cat /tmp/task.json | jq -Mc '.taskDefinition.containerDefinitions' \
| sed -re "s|(\"image\":[^:]*):[0-9a-z]*|\1:${NOW}|")'
--task-role-arn $(cat /tmp/task.json | jq -Mc '.taskDefinition.taskRoleArn')
--volumes '$(cat /tmp/task.json | jq -Mc '.taskDefinition.volumes')'
> /dev/null
__COMMAND__
sh /tmp/task.cmd
if [ "$SERVICE_UPDATE" == "1" ]; then
# 最新のタスク定義family:revisionを取得
REVISION=$(aws ecs describe-task-definition --task-definition ${TASK_NAME} | jq -M '.taskDefinition.revision')
# サービス名を取得
SERVICE=$(aws ecs list-services --cluster ${CLUSTER_NAME} | grep -oP '(?<=[0-9]:service/)([a-z0-9-]+)')
# サービスのタスクを更新
aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE} --task-definition ${TASK_NAME}:${REVISION} > /dev/null
fi
少しずつ見ていきます。まずはコマンドラインの処理。
#!/bin/sh
# サービス更新するかどうか
SERVICE_UPDATE=0
# 引数解析
for OPT in "$@"
do
case $OPT in
--service-update)
SERVICE_UPDATE=1
shift 1
;;
esac
done
すでにgitリポジトリがあるものとしています。もし無い場合は、あらかじめgit checkoutなどをしておく必要があります。
# Git update (必要ならgit checkoutなども)
git pull
スクリプトなどでecrを使うための処理です。ECRに対して適当なdocker loginコマンドを実行してくれます。
# ECRのログイン手続き
$(aws ecr get-login --no-include-email)
リポジトリ名などの設定です。また、作成したdockerイメージには、ビルドの日時をタグとして張り付けるようにしています。そのための日付を取得しています。
また、このコマンドを実行しているユーザーのAWSアカウントも取得しています。
# ECRのリポジトリ名
REPOSITORY="slimtemplate"
# ECSに定義済みのtask名とcluster名
TASK_NAME="hoge-task"
CLUSTER_NAME="hoge-cluster"
# Dockerタグ
DOCKER_TAG="hoge-repo"
# docker buildする際にenvironment環境変数に引き渡す値
ENVIRONMENT="devaws"
# ECRのDockerイメージに付けられるタグ
NOW=$(date -u "+%Y%m%d%H%M%S")
# バッチ実行者のAWSアカウント
ACCOUNT_ID=$(aws sts get-caller-identity | grep Account | grep -oE '[0-9]+')
docker buildします。更新が無い場合は、ここでおしまい。
# docker buildを行う。更新の有無を確認するために、ビルド前後のイメージのidを取得する
OLD_IMAGE_ID=$(docker images ${DOCKER_TAG}:latest -q)
docker build -t ${DOCKER_TAG}:latest --build-arg environment=${ENVIRONMENT} -f compose/web_hoge/Dockerfile.ecs .
NEW_IMAGE_ID=$(docker images ${DOCKER_TAG}:latest -q)
if [ "${OLD_IMAGE_ID}" == "${NEW_IMAGE_ID}" ]; then
# 更新なし
echo "Docker image is not updated."
exit 0
fi
buildしたイメージに対して、"latest"以外に日付のタグも付けます。
これにより、最新のイメージは"latest"タグで更新され、それ以外のイメージは日付のタグが残ることになります(残り続けるのでなんらかの掃除が必要になります)。
そして、ECRにdockerイメージをpushします。"latest"イメージは上書きされますが、日付イメージはECR内に残ります。これは、なんらかの理由でバックグレードするためのものです。
# タグ付けおよびECRへのPUSH
docker tag ${DOCKER_TAG}:latest ${DOCKER_TAG}:${NOW}
docker tag ${DOCKER_TAG}:${NOW} ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:${NOW}
docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:${NOW}
docker tag ${DOCKER_TAG}:latest ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:latest
docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY}:latest
タスクを更新します。ただし、新規でタスク定義用のjsonを作るのではなく、既存のタスクのjsonのイメージだけを書き換えるようにしています。
したがって、初回は手動でタスクを定義する必要があります。
また、イメージのタグには、日付タグのイメージを指定しています。これにより、サービス内のタスクを前のバージョンにすれば、サービスが巻き戻るようになります(latestにするとイメージが上書きされてしまうため)。
# タスクを更新
aws ecs describe-task-definition --task-definition ${TASK_NAME} > /tmp/task.json
cat <<__COMMAND__ | tr '\n' ' ' > /tmp/task.cmd
aws ecs register-task-definition --family ${TASK_NAME} --container-definitions
'$(cat /tmp/task.json | jq -Mc '.taskDefinition.containerDefinitions' \
| sed -re "s|(\"image\":[^:]*):[0-9a-z]*|\1:${NOW}|")'
--task-role-arn $(cat /tmp/task.json | jq -Mc '.taskDefinition.taskRoleArn')
--volumes '$(cat /tmp/task.json | jq -Mc '.taskDefinition.volumes')'
> /dev/null
__COMMAND__
sh /tmp/task.cmd
必要であれば、サービスを更新します。使用するタスクのrevisionは最新のものを自動的に取得しています。
if [ "$SERVICE_UPDATE" == "1" ]; then
# 最新のタスク定義family:revisionを取得
REVISION=$(aws ecs describe-task-definition --task-definition ${TASK_NAME} | jq -M '.taskDefinition.revision')
# サービス名を取得
SERVICE=$(aws ecs list-services --cluster ${CLUSTER_NAME} | grep -oP '(?<=[0-9]:service/)([a-z0-9-]+)')
# サービスのタスクを更新
aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE} --task-definition ${TASK_NAME}:${REVISION} > /dev/null
fi
CodeDeploy
えー、結論から言うと、現在のバージョンではCodeDeployはローリングデプロイをサポートしてません。
どちらかと言うとBlue/Greenデプロイを使うべきということですね。(たぶん)