【ECS】CodeBuildからrun-taskでRDSにマイグレーションしてみた
はじめに
CodeBuildはビルドの実行環境にVPCを指定することができるので、実行環境からRDSに接続したい場合はVPCを指定することが多いかと思います。
その場合、NATを設置することになる場合が多いと思うのですが、CI/CDの中でマイグレーションをしたいという要望のみでNATを設置するのは少々割高に感じました。
そこで、NATを設置せずにCodeBuildからRDSにマイグレーションを実行できないか試してみました。
前提
今回は以下の構成を前提としています。
- ECSからRDSに接続できる
- CI/CDにCodePipelineを使っている
ただ、run-taskできれば良いので他のCI/CDツールでも同様のことはできるかなと思います。
概要
CodePipelineに下記二つのActionを作成してRDSにマイグレーションされるようにします。
- コンテナをビルドするCodeBuild
- ビルドしたコンテナからrun-taskでマイグレーションするCodeBuild
コンテナをビルドするCodeBuild
こちらについては詳細は省略しますが、マイグレーションできるコンテナをビルドしてECRなどにpushされていれば問題ないです。
ビルドしたコンテナからrun-taskでマイグレーションするCodeBuild
ここから本題になります。
タスク定義
下記の内容でマイグレーション用のタスク定義をあらかじめ作成しておきます。
[
{
"name": "php",
"image": "${aws_account_id}.dkr.ecr.${region}.amazonaws.com/php:${tag}",
"command": ["php", "artisan", "migrate", "--force"],
"essential": true
}
]
マイグレーションしたいのはデプロイ前だったので、この時点ではアプリケーションのタスク定義は更新されていません。
そのため、アプリケーションとマイグレーションでタスク定義を分けています。
今回はLaravelを使っていたのでコマンドはphp artisan migrate
でマイグレーションします。
CodeBuild
マイグレーション用のCodeBuildの環境の設定ですが、今回はCodeBuildの動作環境のイメージにaws/codebuild/standard:7.0
を選択しています。
マイグレーション用のCodeBuildをCodePipelineのActionに追加しておきます。
buildspec
次にbuildspec.ymlについてです。
やることとしては下記の通りです。
- 現在のタスク定義を取得
- タスク定義をビルドしたイメージに差替え
- タスク定義を更新
- タスクを起動
- マイグレーションの成否判定
buildspec.ymlに落とし込んだものが下記になります。
version: 0.2
env:
shell: bash
phases:
build:
commands:
- set -eu
- AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
- PHP_IMAGE_REPO=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ENV-php:$CODEBUILD_RESOLVED_SOURCE_VERSION
- SUBNETS=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=$ENV-public*" --query="Subnets[].SubnetId | join(',', @)" --output=text)
- SECURITY_GROUPS=$(aws ec2 describe-security-groups --filters="Name=group-name,Values=$ENV-app" --query="SecurityGroups[].GroupId | join(',', @)" --output text)
# タスク定義を取得し、イメージの差し替えと不要な情報を削除
- >
aws ecs describe-task-definition --task-definition $ENV-migrate --query 'taskDefinition' |
jq '.containerDefinitions[0].image = "'$PHP_IMAGE_REPO'"' |
jq 'del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)'
> /tmp/modified-task-definition.json
# タスク定義を更新
- aws ecs register-task-definition --family $ENV-migrate --cli-input-json file:///tmp/modified-task-definition.json
# タスクを起動
- >
TASK_ARN=$(aws ecs run-task --cluster $ENV --count 1 --launch-type FARGATE
--enable-execute-command
--task-definition $ENV-migrate
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SECURITY_GROUPS],assignPublicIp=ENABLED}"
--output json | jq -r .tasks[0].containers[0].taskArn)
# タスクが終了するまで待機
- aws ecs wait tasks-stopped --cluster $ENV --tasks $TASK_ARN
# マイグレーションが成功したかどうか判定
- EXIT_CODE=$(aws ecs describe-tasks --cluster $ENV --tasks $TASK_ARN | jq -r '.tasks[0].containers[0].exitCode')
- >
if [[ "$EXIT_CODE" -eq 0 ]]; then
echo "migration succeeded."
exit 0
else
echo "migration failed."
exit 1
fi
AWS CLIを駆使してタスク定義の更新だったりタスクの起動を行っています。
各リソース名などはルールを決めているのでサブネットやセキュリティグループをAWS CLIから取得しているのですが、そうではない場合はCodeBuildの環境変数などで渡してあげると良いと思います。
run-taskのコマンド実行時のサブネットとセキュリティグループはRDSに接続できるものを指定して下さい。
あとはbuildspec.ymlを追加してCodePipelineが走れば、run-taskでタスクが起動し、タスク定義で設定していたcommandが実行されます。
まとめ
権限のある環境であればどこからでもrun-taskが実行可能なので、CodeBuild以外でも活用できる場面はありそうだなと思いました。
アプリケーションと同じ環境でコマンドなどを実行できるので使い道が幅広そうです。
Discussion