🐕

Cloud Run Jobs で Cloud SQL にマイグレーション実行

2024/07/28に公開

はじめに

便利だろうと思いつつ、これまであまり触れる機会のなかったCloud Run Jobsですが、DBのマイグレーション(up/down)の自動化を試みる際に使ってみることにしました。

今回は、CloudSQL(PostgreSQL)に対して、Cloud Run Jobs上でマイグレーションを実行する実装を行いました。本記事では、そのための環境構築方法をTerraformコードを交えて説明します。サンプルコードはこちら(github)にあります。

なお、PostgreSQLを使用していますが、MySQLを使用する際にもドライバを変えるだけで同様に実装できるかと思います。また、AlloyDBに関しても、接続情報やプロキシを変更することで同様に実装できることを試しました[1]

0. 前提

ジョブを実装する前に、Google Cloud上に構築されている環境の前提条件を記載します。この環境に追加して、ジョブの実装を進めていきます。

Cloud SQLインスタンスが存在し、VPCネットワークからプライベートIPアドレスでアクセス可能な状態であるとします。Terraformを使用して構成した場合、次のようなリソースが作成されている状態です。

data "google_project" "current" {}

resource "google_compute_network" "db_network" {
  name                    = "db-network"
  auto_create_subnetworks = false
}

resource "google_compute_global_address" "db_private_ip" {
  name          = "db-private-ip"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  address       = "10.0.0.0"
  prefix_length = 16
  network       = google_compute_network.db_network.id
}

resource "google_service_networking_connection" "db_conn" {
  network                 = google_compute_network.db_network.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.db_private_ip.name]
}

resource "google_sql_database_instance" "db" {
  name             = "db"
  database_version = "POSTGRES_16"
  region           = "asia-northeast1"

  settings {
    tier = "db-f1-micro"
    ip_configuration {
      private_network = google_compute_network.db_network.id
      ipv4_enabled    = false

      enable_private_path_for_google_cloud_services = true
    }
  }
  depends_on = [google_service_networking_connection.db_conn]
}

resource "google_sql_user" "db_admin" {
  project  = data.google_project.current.project_id
  instance = google_sql_database_instance.db.name
  name     = "db-admin"
  # 本来はシークレットマネージャなどを使用するべき
  password = var.password
}

resource "google_sql_database" "db" {
  project  = data.google_project.current.project_id
  instance = google_sql_database_instance.db.name
  name     = "db"
}

1. Artifact Registry の作成

まず、マイグレーションジョブ用のDockerイメージを格納するためのレジストリを作成します。

resource "google_artifact_registry_repository" "migration" {
  location      = "asia-northeast1"
  repository_id = "migration"
  description   = "migration docker repository"
  format        = "DOCKER"
}

2. マイグレーション関連ファイル作成

Cloud Run Jobs上で実行するマイグレーション関連ファイルを作成します。

ここではサンプルとしてalembicを使用していますが、Knexなど他のマイグレーションツールでも同様にマイグレーションを実行できると思います。alembicを使用しない場合、このセクションをスキップしてください。また、以降のセクションでもalembicに関するコードや説明については、適宜読み換えご利用ください。

まず、依存パッケージを準備し、マイグレーションの初期化を実行します。Ryeを使用する場合は、以下のコマンドを実行します(必要に応じて pip install などに読み換えてください)。

rye add sqlalchemy alembic psycopg2-binary
alembic init alembic

次に、マイグレーション関連ファイルのDB接続情報を設定します。この際、必要に応じて環境変数[2]を使用すると良いでしょう。alembicを使用する場合、前述のinitコマンドで自動生成される alembic.inisqlalchemy.url を編集します。リビジョンファイルも作成し、以下のコマンドを実行して、自動生成されたファイルの upgrade/downgrade 関数を実装します。

alembic revision -m "message"

3. Dockerfile作成

マイグレーションを実行するコンテナのDockerfileを作成します。このDockerfileには、マイグレーション関連ファイル一式とCloudSQL Proxyをインストールします。そして、コンテナ起動時にプロキシを介してマイグレーションを実行するように設定します。

FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y wget gnupg && rm -rf /var/lib/apt/lists/*

RUN wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy && \
    chmod +x cloud_sql_proxy

COPY alembic.ini /app/
COPY alembic /app/alembic
COPY pyproject.toml .
COPY requirements.lock .

RUN python -m venv /venv
RUN /venv/bin/pip install --no-cache-dir -r requirements.lock

CMD ./cloud_sql_proxy -dir=/cloudsql -instances=[PROJECT_ID]:asia-northeast1:[DB_INSTANCE_NAME]=tcp:5432 & \
    sleep 10 && \
    /venv/bin/alembic upgrade head

このCMDを編集することで、downマイグレーション用のコンテナも作成できます。

4. イメージのビルド&プッシュ

3で作成したDockerfileをビルドし、1で作成したArtifact Registryにプッシュします。

まず、Google Cloudのレジストリに認証を設定します。

gcloud auth configure-docker asia-northeast1-docker.pkg.dev

次に、イメージのビルドを行います。ビルド環境がMacである場合、CloudRunの実行環境とCPUアーキテクチャが異なることに注意し(参考)、以下のようにビルドします。

(docker buildx create --name mybuilder --use)
docker buildx build --platform linux/amd64 -t asia-northeast1-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_NAME]/[IMAGE_NAME]:latest --push .

5. ジョブのデプロイ

4でプッシュされたイメージを使用してジョブをデプロイします。このジョブはCloudSQLに対してVPCアクセスできるように設定します。また、このジョブにアタッチするサービスアカウントはArtifact Registryからの読み込み権限も必要です。VPCアクセスコネクタを含む、Terraformのサンプルコードを以下に書きます。

resource "google_vpc_access_connector" "connector" {
  name          = "vpc-access-con"
  ip_cidr_range = "10.10.0.0/28"
  network       = google_compute_network.db_network.id
}

resource "google_service_account" "migration_job" {
  account_id  = "migration-job"
  description = "for migration job"
}

resource "google_project_iam_member" "migration_job" {
  for_each = toset([
    "roles/artifactregistry.reader",
    "roles/cloudsql.admin",
  ])
  project = data.google_project.current.project_id
  role    = each.value
  member  = "serviceAccount:${google_service_account.migration_job.email}"
}

resource "google_cloud_run_v2_job" "migration" {
  name     = "migration"
  location = "asia-northeast1"
  template {
    template {
      service_account = google_service_account.migration_job.email
      containers {
        image = "asia-northeast1-docker.pkg.dev/${google_artifact_registry_repository.migration.project}/${google_artifact_registry_repository.migration.repository_id}/migration:latest"
      }
      vpc_access {
        connector = google_vpc_access_connector.connector.id
      }
    }
  }
}

最小権限を付与したい場合は、google_artifact_registry_repository_iam_memberなどのリソースを使用するのが良いでしょう。

これで、一通り構築は完了です。ジョブはコンソールから実行するか、以下のコマンドで実行すれば良いでしょう。

gcloud run jobs execute

マイグレーションが正常に実行できているかどうかは、CloudSQL studioを使えば簡単に確認[3]できます。

(Appendix)GitHub Actionsによる自動化

以上で構築は完了ですが、GitHub Actionsなどで自動化しておくと、CI/CDパイプラインに組み込めむことができ、より便利です。例えば、以下のようにビルドからジョブの実行までを自動化することができます。

name: migration

on:
  push:
    branches:
      - "main"

jobs:
  migration:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    env:
      WORKLOAD_IDENTITY_PROVIDER: 'projects/[PROJECT_ID]/locations/global/workloadIdentityPools/[POOL_ID]/providers/[PROVIDER_ID]'
      GITHUB_SERVICE_ACCOUNT: '[SERVICE_ACCOUNT_EMAIL]'
      IMAGE_PATH: 'asia-northeast1-docker.pkg.dev/[PROJECT_ID]/migration/migration:latest'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ env.GITHUB_SERVICE_ACCOUNT }}

      - name: Set up gcloud CLI
        uses: google-github-actions/setup-gcloud@v2
        with:
          version: 'latest'

      - name: Init docker for gcloud
        run: gcloud auth configure-docker asia-northeast1-docker.pkg.dev

      - name: Build docker image
        run: docker build -t ${{ env.IMAGE_PATH }} .

      - name: Push docker image for container registory
        run: docker push ${{ env.IMAGE_PATH }}

      - name: Execute Cloud Run Job
        run: |
          gcloud run jobs deploy migration \
            --image ${{ env.IMAGE_PATH }} \
            --region asia-northeast1
          gcloud run jobs execute migration \
            --region asia-northeast1

上記はWorkload Identity連携を使用しています。

これに加えて、3で記載したようにdownマイグレーションのジョブも簡単に作成できるので、こちらもワークフローに自動化しておくと良いでしょう。

まとめ

Cloud Run Jobs を使用して、Cloud SQLに対してDBのup/downマイグレーションを実行するための環境構築方法を記載しました。また、これを使用してマイグレーションを自動化する例についても紹介しました。

本記事の内容が、読者の皆様にとってお役に立てれば幸いです。

脚注
  1. https://zenn.dev/optimind/articles/125130c6c9b017 ↩︎

  2. シークレットマネージャから読み込んでCloudRunJobsジョブに注入できます(参考)。 ↩︎

  3. 踏み台サーバを立てるなどの準備なしに、コンソールから簡単にクエリを実行できます。GAになったのは最近かと思いますが、非常に便利です。 ↩︎

OPTIMINDテックブログ

Discussion