はじめに
この記事ではECS(Fargate)のCDパイプラインに、DBマイグレーション処理を組み込む一例を解説します。
ECS(Fargate)へのデプロイは、ecspressoを使用します。
また、題材はLaravel、CIツールとしてはGitHub Actionsを使いますが、他のフレームワークや、CIツールでも考え方は応用できるかと思います。
目次
前提
本記事で取り扱うECSタスクでは、以下2つのコンテナが起動します。
- nginxコンテナ。ベースイメージは
nginx:1.20-alpine
。 - phpコンテナ(Laravelが稼働するコンテナ)。ベースイメージは
php:8.0-fpm-buster
。
DBマイグレーションを含む一連のデプロイの流れ
本記事では、GitHub Actionsによる一連のデプロイ処理の過程で、ecspresso run
というコマンドを使って、DBマイグレーションのみを行うためのタスクを起動するようにします。
ecspresso run
は、ECSサービスのdesired_count
(必要タスク数)とは関係無く、独立してタスクを起動させます。バッチ処理などのためにタスクを起動させたい場合などに使用します。
本記事では、ecspresso run
によるDBマイグレーション完了とともにそのタスクも停止させ、その後にWebアプリケーションとしてのタスクをローリングアップデート(新しいタスクを起動し、古いタスクを停止)するようにします。
DBマイグレーションの前に行うこと
ecspresso run
によってDBマイグレーションのみを行うためのタスクを起動する前に、ecspresso register
というコマンドで、新しいリビジョンのタスク定義の登録のみを行うようにします。
なぜ、わざわざタスク定義の登録のみを先に行うのかというと、まず行わなかった場合、一連のデプロイ処理は以下の流れとなります。
ecspresso run
により、新しいリビジョンのタスク定義を登録するとともにDBマイグレーションのみを行うためのECSタスクを起動する(DBマイグレーション完了後にタスクは停止)ecspresso deploy
により、新しいリビジョンのタスク定義を登録するとともにWebアプリケーションとしてのタスクをローリングアップデートする
この流れだと、一連のデプロイ処理で、タスク定義のリビジョンが2つ上がります。このようにしてしまうと、もし新しいタスク定義に基づいて起動させたタスクに不具合があってロールバック(ecspresso rollback
)したい場合に、運用がしづらくなります。ecspresso rollback
では、1世代古いタスク定義でタスクを起動し直してくれますが、上記の流れでは2世代古いタスク定義を使う必要があるためです。
一方、ecspressoには--latest-task-definition
というオプションがあり、これを使うとタスク定義の登録は行わずに、登録済みのうち、最新のリビジョンのタスク定義を使ってタスクを起動させます。
これを利用し、本記事では以下の流れを取るようにします。
ecspresso register
により、新しいリビジョンのタスク定義の登録のみを行う(タスクの起動は行わない)ecspresso run --latest-task-definition
により、タスク定義の登録は行わず、最新のタスク定義を使って、DBマイグレーションのみを行うためのタスクを起動させる(DBマイグレーション完了後にタスクは停止)ecspresso deploy --latest-task-definition
により、タスク定義の登録は行わず、最新のタスク定義を使って、Webアプリケーションとしてのタスクをローリングアップデートする
この流れであれば、一連のデプロイ処理で、タスク定義のリビジョンは1つだけ上がります。もし、起動させたタスク定義に不備があってロールバックしたければ、ecspresso rollback
を実行すれば良いことになります。
ワークフロー例
前述の流れに沿った、GitHub Actionsのワーフフロー例です。
.github/workflows/deploy.yml
name: deploy on: push: branches: - main env: // 略 jobs: deploy: name: Deploy app to AWS Fargate runs-on: ubuntu-latest steps: // 略(DockerイメージのビルドやECRへのプッシュ等のステップ) - name: Download ecspresso uses: kayac/ecspresso@v0 with: version: v1.5.3 - name: Register task definition run: ecspresso register --config config.yaml - name: Migrate database run: | ecspresso run --config config.yaml \ --latest-task-definition \ --watch-container=php \ --overrides='{"containerOverrides":[{"name":"nginx", "command":["nginx", "-v"]},{"name":"php", "command":["php", "artisan", "migrate", "--force"]}]}'} - name: Deploy to ecs run: ecspresso deploy --config config.yaml --latest-task-definition
--watch-containerオプション
ecspresso run
はデフォルトでは、コンテナが停止するまで待機します。
--watch-container
オプションは、どのコンテナの停止を待つかを指定します。ここでは、DBマイグレーションを行うphpコンテナを指定しています。
また、--watch-container
に指定したコンテナのログ(CloudWatch Logs)が、ecspressoのログの一部として表示されます。今回のケースであれば、GitHub Actionsのログに、phpコンテナでのDBマイグレーションの実行ログが表示されることになります。
--overridesオプション
指定した内容でタスクの動作を上書きします。値はJSON文字列で指定します。
本記事で指定しているJSON文字列を見やすく整形すると以下になります。
{ "containerOverrides": [ { "name": "nginx", "command": ["nginx", "-v"] }, { "name": "php", "command": ["php", "artisan", "migrate", "--force"] } ] }
containerOverrides
に指定した内容で、タスクの動作を上書きします。
name
には、対象のコンテナ名を指定します。
また、今回はcommand
を指定することで、コンテナ起動時の動作をデフォルトのものから変更しています。
本記事のnginxコンテナのベースイメージであるnginx:1.20-alpine
のもともとのコマンド(CMD
)は、["nginx", "-g", "daemon off;"]
です。
これにより、nginxのプロセスが起動してnginxコンテナも起動し続けますが、今回これを["nginx", "-v"]
に上書きしています。その結果、nginxコンテナはバージョン表示を行った後、停止します。
DBマイグレーションを行うにあたり、nginxコンテナは停止していて構いませんので、nginxコンテナを停止させられるような適当なコマンドで上書きしています。
GitHub Actions実行結果(抜粋)
マイグレーションおよびデプロイがうまく行くと、GitHub Actionsでは以下のようなログが出力されるかと思います。
Register task definition 2021/06/29 00:15:15 example-prod-foobar/example-prod-foobar Starting register task definition 2021/06/29 00:15:15 example-prod-foobar/example-prod-foobar Registering a new task definition... 2021/06/29 00:15:16 example-prod-foobar/example-prod-foobar Task definition is registered example-prod-foobar:71 Migrate database 2021/06/29 00:15:18 example-prod-foobar/example-prod-foobar Running task 2021/06/29 00:15:19 example-prod-foobar/example-prod-foobar Task ARN: arn:aws:ecs:ap-northeast-1:***:task/example-prod-foobar/xxx... 2021/06/29 00:15:19 example-prod-foobar/example-prod-foobar Watching container: php 2021/06/29 00:15:19 example-prod-foobar/example-prod-foobar Waiting for run task...(it may take a while) 2021/06/29 00:15:19 example-prod-foobar/example-prod-foobar logGroup: /ecs/example-prod-foobar/php 2021/06/29 00:15:19 example-prod-foobar/example-prod-foobar logStream: ecs/php/xxx... 2021/06/29 00:16:09 Migration table created successfully. 2021/06/29 00:16:09 Migrating: 2014_10_12_000000_create_users_table 2021/06/29 00:16:09 Migrated: 2014_10_12_000000_create_users_table (70.18ms) 2021/06/29 00:16:09 Migrating: 2014_10_12_100000_create_password_resets_table 2021/06/29 00:16:09 Migrated: 2014_10_12_100000_create_password_resets_table (58.96ms) 2021/06/29 00:16:09 Migrating: 2019_08_19_000000_create_failed_jobs_table 2021/06/29 00:16:09 Migrated: 2019_08_19_000000_create_failed_jobs_table (225.03ms) // 略 (注: ecspresso run終了まで、PHPコンテナのログが数秒間隔で繰り返し表示されます) 2021/06/29 00:17:03 example-prod-foobar/example-prod-foobar Run task completed! Deploy to ecs 2021/06/29 00:17:04 example-prod-foobar/example-prod-foobar Starting deploy Service: example-prod-foobar Cluster: example-prod-foobar TaskDefinition: example-prod-foobar:70 Deployments: PRIMARY example-prod-foobar:70 desired:0 pending:0 running:0 Events: 2021/06/29 00:17:06 example-prod-foobar/example-prod-foobar Updating service attributes... // 略 2021/06/29 00:18:31 example-prod-foobar/example-prod-foobar Service is stable now. Completed!