AWSのECSでデータベースマイグレーションを正しく実装する方法

# AWSのECSでデータベースマイグレーションを正しく実装する方法

## はじめに

AWS ECS環境でLaravelなどのフレームワークを使用する際の、データベースマイグレーションの実装について解説します。ECSでアプリケーションを運用している方なら、デプロイ時のマイグレーション処理でハマった経験があるのではないでしょうか？私はハマりました...

ECSでのマイグレーション処理で問題に直面し、AWSサポートに相談したところ、非常に有益な回答をいただきました。この記事では、その内容をもとに、ECSでのデータベースマイグレーションを正しく実装する方法をご紹介します。

## 前提条件

この記事は以下の環境を前提としています：

```
- AWS ECS (Fargate)
- PHP 8.2
- Laravel（または同様のマイグレーション機能を持つフレームワーク）
- RDSなどのAWSデータベースサービス
- タスク定義に複数のコンテナ（Webサーバー用とPHP実行用など）が含まれる構成
```

## 発生した問題：マイグレーションによるタスクの起動停止ループ

最初に、私が遭遇した問題について説明します。ECSサービスへのデプロイ時にマイグレーションを自動実行したいと考え、タスク定義のDockerコンテナ設定で以下のように設定しました。

```
- エントリポイント: sh, -c
- コマンド: php artisan migrate --force
- 作業ディレクトリ: /work/backend
```

しかし、この設定を行うと、タスクが起動と停止を繰り返すという問題が発生しました。原因を調査してみると、以下のことが分かりました。

1. マイグレーションコマンドは処理完了後に終了コード0を返し、コンテナが停止する
2. マイグレーション用のコンテナが `essential = true` と設定されていたため、このコンテナが停止するとタスク全体が停止
3. ECSサービスはタスクを維持しようとするため、再度タスクを起動し、同じループが繰り返される

この問題を解決するために、まずはタスク定義からマイグレーション設定を削除することで一時的に対応しましたが、根本的な解決が必要でした。

## 解決策：適切なマイグレーション実装方法

AWSサポートからいただいた回答と私の実践から、以下の2つの有効な実装方法が見つかりました。

### 方法1：常駐プロセスを追加する

一つ目の方法は、マイグレーションコマンドの後に常駐プロセスを追加することです：

```
- コマンド: php artisan migrate --force && php-fpm
```

このようにすると、マイグレーション完了後もコンテナが停止せず、稼働し続けるためタスクが正常に動作します。

**メリット**:
- 実装が比較的簡単
- 既存のタスク定義の修正のみで対応可能

**デメリット**:
- タスク起動ごとにマイグレーションが実行される
- 複数タスクがある場合に同時にマイグレーションが実行され、競合やエラーの可能性がある

### 方法2：スタンドアロンタスクとして実行する（推奨）

二つ目の方法は、Webサーバー用コンテナとは別に、マイグレーション専用のタスク定義を作成し、デプロイプロセスの一部として一度だけ実行する方法です：

1. マイグレーション専用のタスク定義を作成
2. CI/CDパイプラインでデプロイ前にスタンドアロンタスクとしてマイグレーションを実行
3. マイグレーション完了後に、本来のアプリケーションタスクをデプロイ

**メリット**:
- マイグレーションが一度だけ確実に実行される
- 複数タスクからの同時マイグレーションによる問題を回避できる
- 本番環境のデプロイプロセスとして適切

**デメリット**:
- CI/CDパイプラインの修正が必要
- 追加のタスク定義管理が必要

## 実装例：CI/CDでのマイグレーション実行

GitHub ActionsやAWS CodePipelineなどのCI/CDツールでの実装例を示します：

```yaml
# GitHub Actionsの例
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # ... 前処理（ビルドなど）

      # マイグレーション実行
      - name: Run database migrations
        run: |
          aws ecs run-task \
            --cluster your-cluster-name \
            --task-definition your-migration-task-definition \
            --network-configuration "awsvpcConfiguration={subnets=[subnet-xxxx],securityGroups=[sg-xxxx],assignPublicIp=ENABLED}" \
            --launch-type FARGATE
          
          # マイグレーションの完了を待機
          # 実際の実装ではタスク完了を確認するロジックが必要

      # アプリケーションのデプロイ
      - name: Deploy application
        run: |
          aws ecs update-service \
            --cluster your-cluster-name \
            --service your-service-name \
            --task-definition your-application-task-definition \
            --force-new-deployment
```

## ベストプラクティス

ECSでのマイグレーション実装において、以下の点を考慮することをお勧めします：

1. **マイグレーションは一度だけ実行する**: タスク起動ごとではなく、デプロイプロセスの一部として実行
2. **エラーハンドリングを考慮する**: マイグレーション失敗時のロールバック戦略を用意
3. **環境変数管理**: データベース接続情報などはAWS Secret Managerなどで安全に管理
4. **ログの確認**: CloudWatch Logsでマイグレーションの実行状況を確認できるように設定
5. **テスト環境での検証**: 本番環境に適用する前に同じプロセスでテスト環境で確認

## まとめ

ECSでのデータベースマイグレーション実装は、一見シンプルに見えて意外な落とし穴があります。特に本番環境では、安全で確実なマイグレーション実行が重要です。

この記事で紹介した方法、特にスタンドアロンタスクとしてCI/CDに組み込む方法は、多くの実運用環境で有効に機能するでしょう。

最後に、この問題解決にあたってサポートしてくれたAWSサポートチームに感謝します。

## 参考リンク

- [Amazon ECS スタンドアロンタスク（AWS公式ドキュメント）](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/standalone-tasks.html)
- [ECS（Fargate）で Laravel のリリース時のDBマイグレーションってどうすんの？](https://zenn.dev/babyjob/articles/012e00d13d43fa)
- [ECS(Fargate)でDBマイグレーションを行う](https://blog.shonansurvivors.com/entry/ecs-db-migration)