Shining Wizzard with ECS
概要
こちらのサイトにあるシャイニングウィザードをECS環境で行おうとしたメモ 本家のシャイニングウィザードは、github の webhookを受け取って、シャイニングウィザードが実施されるけど、そこはまだ作ってなくて、一旦Localから叩くようにしてる。
シャイニングウィザードって?
開発時に、現在開発中のブランチの内容を、ディレクターさんとかにチェックしてほしいってことが良くある。
このとき、stagingとかdevelop ブランチにあげて、確認してもらうと、その確認が終わるまで、他の人がリリースできない。
自分のlocalを直接みてもらうこともできるけど、それはそれで自分が他ブランチで作業できず、いろいろ面倒。
そのため、github pushで、確認環境を自動生成しようというのがシャイニングウィザード。
EC2の時は、先輩が作ったシャイニングウィザードを使ってたけど、ECS環境に変わったので自分で作ってみた。
やったこと
ECRとECSを利用しているのを前提とする。
1. Dockerファイルを作成
// 既にECRにあるイメージを利用することで、yarn install を早くする
FROM {Dev環境で使ってるImageのECR上のURL}
RUN apk update && apk add git
// .git フォルダをgithubからcloneする。
RUN git clone --no-checkout https://{User名}:{GithubToken}@github.com/{レポジトリ名} gittmp
RUN cp -r gittmp/.git .
RUN git reset --hard HEAD
// Branch Nameは外から渡す
ARG BRANCH_NAME
# この時点以降で、キャッシュを効かせないためにdummyfileをADD(必要か微妙)
ADD dummyfile /data/
// fetchしてbranch情報を最新に
RUN git fetch
// 対象ブランチをチェックアウト
RUN git checkout -b ${BRANCH_NAME} origin/${BRANCH_NAME}
// アプリケーションを起動
RUN yarn install
RUN yarn run build
CMD yarn run start
2. Shell ファイルを作成
#!/bin/bash
BRANCH_RAW_NAME="$1"
if [ "$1" == "" ]; then
BRANCH_RAW_NAME=$(git symbolic-ref --short HEAD)
fi
AWS_ACCOUNT_ID={ECRとかECSの頭についてるID}
APP_NAME={アプリケーションの名前}
BRANCH_NAME=`echo ${BRANCH_RAW_NAME} | sed -e 's/#//g' -e 's/\//_/g'`
CLUSTER_NAME=`echo ${APP_NAME}_${BRANCH_NAME} | sed 's/_/-/g'`
echo "${BRANCH_RAW_NAME} shining wizzard start"
echo 'Create Docker Image'
# Dockerログイン
$(aws ecr get-login --no-include-email --region ap-northeast-1)
# docker Build時に、キャッシュ利用をやめさせるために追加
touch dummyfile
# Docker Buildして、push! Imageはいらないので削除。
AWS_IMAGE_NAME=${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/{ECRのフォルダ名}:${BRANCH_NAME}
docker build . -t ${AWS_IMAGE_NAME} -f Dockerfile_nuxt --build-arg BRANCH_NAME=${BRANCH_RAW_NAME}
docker push ${AWS_IMAGE_NAME}
docker rmi ${AWS_IMAGE_NAME}
echo 'ECS Task Create'
# ECSタスク追加
NET_CONFIG="awsvpcConfiguration={subnets=[{subnet}],securityGroups=[{securitygroup}],assignPublicIp=ENABLED}"
VPC=[{VPC}]
ROLE={ROLE}
echo 'Check Task Definition'
TASK_DEFINITION_ARN=$(aws ecs describe-task-definition --task-definition ${CLUSTER_NAME} --query='taskDefinition.taskDefinitionArn')
if [ "$TASK_DEFINITION_ARN" == "" ]; then
echo 'Create Task Definition'
# Task Definition 作成
TASK_DEFINITION= {ECSのタスク定義をJSON形式で挿入}
fi
echo 'Check Cluster'
CLUSTER_STATUS=$(aws ecs describe-clusters --clusters ${CLUSTER_NAME} --query='clusters[].status')
CLUSTER_STATUS=`echo $CLUSTER_STATUS | tr '\n' ' ' | sed -e "s/ //g" -e "s/\"//g" -e "s/\[//g" -e "s/\]//g"`
// Clusterが存在するかチェック
if [ "$CLUSTER_STATUS" == "ACTIVE" ]; then
// Clusterが既にあるなら、タスクもあるよねってことで古いタスクはストップ
echo 'Old Task STOP'
OLD_TASK_ID=$(aws ecs list-tasks --cluster ${CLUSTER_NAME} --query='taskArns[0]')
OLD_TASK_ID=`echo $OLD_TASK_ID | sed "s/\"//g"`
STOP_TASK_INFO=$(aws ecs stop-task --cluster ${CLUSTER_NAME} --task $OLD_TASK_ID)
else
# cloudwatch logs groupを作成しておく
aws logs create-log-group --log-group-name /ecs/${CLUSTER_NAME}
aws logs put-retention-policy --log-group-name /ecs/${CLUSTER_NAME} --retention-in-days 30
# ECSのクラスターとか作成
echo 'ECS Cluster create'
aws ecs create-cluster --cluster-name ${CLUSTER_NAME}
echo 'Task Definition create'
aws ecs register-task-definition --family ${CLUSTER_NAME} --task-role-arn ${ROLE} --execution-role-arn=${ROLE} --network-mode awsvpc --container-definitions ${TASK_DEFINITION} --requires-compatibilities FARGATE --cpu 512 --memory 1024
fi
echo 'New Task Run'
RUN_TASK_INFO=$(aws ecs run-task --cluster ${CLUSTER_NAME} --task-definition arn:aws:ecs:ap-northeast-1:${AWS_ACCOUNT_ID}:task-definition/${CLUSTER_NAME} --network-configuration ${NET_CONFIG} --launch-type FARGATE)
TASK_ID=$(aws ecs list-tasks --cluster ${CLUSTER_NAME} --query='taskArns[0]')
TASK_ID=`echo $TASK_ID | sed "s/\"//g"`
echo 'タスクが生成されるまで待機'
sleep 60
NETWORK_INTERFACE_ID=$(aws ecs describe-tasks --cluster ${CLUSTER_NAME} --tasks ${TASK_ID} --query='tasks[].attachments[].details[?name==`networkInterfaceId`].value[]')
NETWORK_INTERFACE_ID=`echo $NETWORK_INTERFACE_ID | sed -e "s/\[ \"//g" -e "s/\" \]//g"`
IP_INFO=$(aws ec2 describe-network-interfaces --network-interface-ids ${NETWORK_INTERFACE_ID} --query='NetworkInterfaces[].Association[]')
printf "\e[33m IP情報 \e[m"
printf "\e[1;31m ${IP_INFO} \e[m"
// ちなみに削除コマンドもここに
// task definitionは一個しかないはずなので、一個だけDeregister
aws ecs deregister-task-definition --task-definition arn:aws:ecs:ap-northeast-1:${AWS_ACCOUNT_ID}:task-definition/${CLUSTER_NAME}:1
# Taskは常に一個しかないはずなので、一個TaskをSTOP
OLD_TASK_ID=$(aws ecs list-tasks --cluster ${CLUSTER_NAME} --query='taskArns[0]')
OLD_TASK_ID=`echo $OLD_TASK_ID | sed "s/\"//g"`
aws ecs stop-task --cluster ${CLUSTER_NAME} --task $OLD_TASK_ID
# クラスターを削除
aws ecs delete-cluster --cluster ${CLUSTER_NAME}
# ロググループも削除
aws logs delete-log-group --log-group-name /ecs/${CLUSTER_NAME}
printf "\e[33m 削除完了しました \e[m"
3.実行
上のshellファイルを自分の環境に合わせて修正して、実行すればうまく行くはず。
ざっくり流れ
DockerFile上で対象ブランチの変更を取得して、それを利用してDocker buildして、ECR上にDocker push。
ECSのClusterやTask定義、cloudwatchのlog groupを作成して、ECSのTaskを生成。
上記設定だったら、IPアドレスが振られるので、それを表示!
もっとシャイニングウィザードに近づけるために
注意
shellファイル書くの初めてだったので、変なところとかあったらすいません。
terraform とか https://github.com/aws/amazon-ecs-agent で作った方が楽なのかも。
Dockerの部分は、実際はLocalのものをDocker buildして、pushしちゃった方が楽です!
(今回は、一つのコマンドで、Backend API の Railsと、Frontend の Nuxt を行いたかったから、こういう特殊な方法を取りました!)
感想
aws ecs cli をがっつり触るのは初めてだったけど、めっちゃ便利だった。
あと、先輩に流れのアドバイスをもらったぐらいで、作業自体は、自分の力で簡単にできた。