はじめに
NewMatchのバックエンドシステムではCloudRunを使用しています。サービスのデプロイ時には、データベースマイグレーションを安全に実行する必要がありますが、これにはいくつかの課題がありました。本記事では、CloudRun Jobsを活用してデータベースマイグレーションを安全かつ効率的に行う方法について紹介します。
特に以下のようなエラーが発生することがありました:
STARTUP HTTP probe failed 5 times consecutively for container "app-1" on path "/health". The instance was not started.
Connection failed with status ERROR_CONNECTION_FAILED.
これは、新しいリビジョンがデプロイされた際に、アプリケーションの起動前にデータベースマイグレーションが必要だったにも関わらず、起動時のヘルスチェックが失敗してしまうという問題です。マイグレーションを適用していないデータベースに対して新しいコードが動作しようとして失敗するケースでした。この問題を解決するために、CloudRun Jobsを使ったマイグレーション実行フローを導入しました。
Cloud Run Jobsとは
Cloud Run Jobsは、一度実行して完了するタスクをサーバーレス環境で実行するためのサービスです。Webサーバーのように常時稼働する必要がないバッチ処理やマイグレーションなどのタスクに最適です。
従来のCloud Runサービスとの主な違いは、常時リクエストを待ち受けるプロセスである必要がなく、タスクが完了すると終了することです。これにより、データベースマイグレーションのような一時的なタスクを効率的に実行できます。
マイグレーション用Cloud Run Jobの実装
まず、Terraformを使用してCloud Run Jobを定義しました:
resource "google_cloud_run_v2_job" "migrate" {
name = "app-migrate"
location = local.region
deletion_protection = false
template {
template {
containers {
image = "asia-northeast1-docker.pkg.dev/${local.project_id}/cloud-run-source-deploy/app:latest"
command = ["pnpm"]
args = ["run", "cloudrun:migration:run"]
env {
name = "NODE_ENV"
value = "staging"
}
# データベース接続情報の設定
env {
name = "DATABASE_HOST"
value = google_sql_database_instance.newmatch_db.private_ip_address
}
# その他の環境変数...
resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
}
timeout = "900s"
max_retries = 1
service_account = google_service_account.cloudrun_app.email
vpc_access {
connector = google_vpc_access_connector.vpc_connector[local.region].id
egress = "PRIVATE_RANGES_ONLY"
}
}
}
}
このJobの設定のポイントは以下の通りです:
- 同一コンテナの使用 - アプリケーションと同じコンテナイメージを使用
- マイグレーション専用コマンド - package.jsonに定義した
cloudrun:migration:run
コマンドを実行 - タイムアウト - 長時間実行可能な900秒の設定
- リソース制限 - 適切なCPUとメモリの割り当て
- VPCアクセス - プライベートネットワーク内のデータベースにアクセス
package.jsonへのマイグレーションコマンド追加
TypeORMを使用したマイグレーション実行用のコマンドをpackage.jsonに追加しました:
{
"scripts": {
// 既存のコマンド...
"cloudrun:migration:run": "typeorm-ts-node-commonjs migration:run -d ./src/database/data-source.ts",
"cloudrun:migration:revert": "typeorm-ts-node-commonjs migration:revert -d ./src/database/data-source.ts"
}
}
これらのコマンドはTypeORMのCLIを使用して、データソース設定に基づいてマイグレーションを実行します。
デプロイフローへの統合
GitHubActionsのワークフローを以下のように構成しました:
- 新リビジョンのデプロイ - トラフィックを移行せずに新バージョンをデプロイ
- データベースマイグレーション - Cloud Run Jobsを実行
- トラフィック移行 - マイグレーション成功後に新バージョンへトラフィックを移行
jobs:
# 新しいリビジョンをデプロイするが、トラフィックは移行しない
deploy-revision:
# 設定略...
# データベースマイグレーションはデプロイ後に実行
db-migration:
runs-on: ubuntu-latest
needs: [deploy-revision, prepare-deploy]
steps:
- uses: actions/checkout@v4
- name: Authenticate GCP # 省略
- name: Run Database Migration
run: sh .github/scripts/migrate.sh
env:
PROJECT_ID: ${{ needs.prepare-deploy.outputs.project }}
REGION: ${{ env.GCP_REGION }}
ENVIRONMENT: ${{ needs.prepare-deploy.outputs.environment }}
# マイグレーション完了後にトラフィックを移行
traffic-migration:
runs-on: ubuntu-latest
needs: [deploy-revision, db-migration, prepare-deploy]
steps:
- uses: actions/checkout@v4
- name: Authenticate GCP # 省略
- name: Migrate traffic to latest revision
run: |
gcloud run services update-traffic ${{ matrix.app }} \
--region=${{ matrix.region }} \
--to-latest \
--project=${{ needs.prepare-deploy.outputs.project }}
# 設定略...
マイグレーションスクリプトの実装
マイグレーション実行のためのシェルスクリプト(migrate.sh)を作成しました:
#!/bin/bash
# GitHub Actions内でCloud Run Jobを使用したマイグレーション実行スクリプト
set -e
# 環境変数確認
if [ -z "$PROJECT_ID" ] || [ -z "$REGION" ]; then
echo "エラー: 必要な環境変数(PROJECT_ID, REGION)が設定されていません"
exit 1
fi
# 環境パラメータのデフォルト値
ENVIRONMENT=${ENVIRONMENT:-production}
echo "対象環境: $ENVIRONMENT"
JOB_NAME="app-migrate"
# マイグレーションジョブが存在するか確認
if ! gcloud run jobs describe $JOB_NAME --region=$REGION --project=$PROJECT_ID; then
echo "エラー: マイグレーションジョブ「$JOB_NAME」が存在しません。Terraformで作成されていることを確認してください。"
exit 1
fi
# マイグレーションの実行
echo "マイグレーションを実行します..."
gcloud run jobs execute $JOB_NAME \
--region=$REGION \
--project=$PROJECT_ID \
--wait
echo "マイグレーションが完了しました"
このスクリプトの重要なポイント:
- 環境変数の検証 - 必要なパラメータが設定されているか確認
- ジョブの存在確認 - 実行前にJobが存在するか確認
- --wait フラグ - マイグレーションの完了を待機し、失敗時はエラーコードを返す
ゼロダウンタイムデプロイの実現
この実装により、以下のようなゼロダウンタイムデプロイフローを実現しました:
- 新しいリビジョンをデプロイ(トラフィックは古いバージョンのまま)
- データベースマイグレーションを実行
- マイグレーション成功後、新リビジョンにトラフィックを移行
この方法には以下のメリットがあります:
- 安全性: マイグレーションが失敗した場合、トラフィックは古いバージョンのままなのでサービスは継続して利用可能
- 可視性: Cloud Run Jobsのログでマイグレーション実行状況を確認可能
- 一貫性: 環境間(ステージングと本番)で同じデプロイフローを使用
- 権限管理: マイグレーション用のサービスアカウントを適切に設定可能
まとめ
Cloud Run Jobsを活用したデータベースマイグレーション実行フローを導入することで、以下のメリットが得られました:
- 安全性の向上 - マイグレーション失敗時にユーザーへの影響を最小限に抑制
- デプロイの自動化 - GitHub Actionsとの統合により完全自動化されたデプロイフロー
- 一貫性の確保 - すべての環境で同一の手順でマイグレーションを実行
- 運用オーバーヘッドの削減 - マイグレーション実行のための特別な手続きが不要
この仕組みにより、NewMatchのデプロイにおける信頼性とセキュリティが大幅に向上しました。Cloud Run Jobsはこのようなタスクに最適なソリューションであり、他のプロジェクトでも同様のアプローチを検討する価値があります。