ECS(Fargate)でDBマイグレーションを行う

はじめに

この記事ではECS(Fargate)のCDパイプラインに、DBマイグレーション処理を組み込む一例を解説します。

ECS(Fargate)へのデプロイは、ecspressoを使用します。

また、題材はLaravel、CIツールとしてはGitHub Actionsを使いますが、他のフレームワークや、CIツールでも考え方は応用できるかと思います。

目次

前提

本記事で取り扱うECSタスクでは、以下2つのコンテナが起動します。

  1. nginxコンテナ。ベースイメージはnginx:1.20-alpine
  2. 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というコマンドで、新しいリビジョンのタスク定義の登録のみを行うようにします。

なぜ、わざわざタスク定義の登録のみを先に行うのかというと、まず行わなかった場合、一連のデプロイ処理は以下の流れとなります。

  1. ecspresso runにより、新しいリビジョンのタスク定義を登録するとともにDBマイグレーションのみを行うためのECSタスクを起動する(DBマイグレーション完了後にタスクは停止)

  2. ecspresso deployにより、新しいリビジョンのタスク定義を登録するとともにWebアプリケーションとしてのタスクをローリングアップデートする

この流れだと、一連のデプロイ処理で、タスク定義のリビジョンが2つ上がります。このようにしてしまうと、もし新しいタスク定義に基づいて起動させたタスクに不具合があってロールバック(ecspresso rollback)したい場合に、運用がしづらくなります。ecspresso rollbackでは、1世代古いタスク定義でタスクを起動し直してくれますが、上記の流れでは2世代古いタスク定義を使う必要があるためです。

一方、ecspressoには--latest-task-definitionというオプションがあり、これを使うとタスク定義の登録は行わずに、登録済みのうち、最新のリビジョンのタスク定義を使ってタスクを起動させます。 これを利用し、本記事では以下の流れを取るようにします。

  1. ecspresso registerにより、新しいリビジョンのタスク定義の登録のみを行う(タスクの起動は行わない)

  2. ecspresso run --latest-task-definitionにより、タスク定義の登録は行わず、最新のタスク定義を使って、DBマイグレーションのみを行うためのタスクを起動させる(DBマイグレーション完了後にタスクは停止)

  3. 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!

techbookfest.org

booth.pm