LoginSignup
2
1

More than 1 year has passed since last update.

DBマイグレーションを手動コマンド実行からCloud Run Jobsに移行した話

Posted at

はじめに

DBマイグレーションをステージングや本番環境に適用すること、またCI/CDに組み込んで自動化するのはバックエンド/サーバーサイド開発で必ずといっていいほど通る道です。
開発体制やフェーズ、アーキテクチャによって最適解は様々変わってきますが、私が現職で適用した方法を紹介したいと思います。
ちなみに今回は「開発者のローカルから手動でコマンド実行」から「Cloud Run Jobs(Cloud Buildトリガー)を手動で実行」に移行できたまでで、完全自動化までは道半ばです。

マイグレーションツールについて

弊プロダクトではもともとはsqldefmysqldefコマンドを使ってマイグレーションを適用していました。
もちろんmysqldefのみでもマイグレーションを行うことは可能でしたが、下記のような課題があり、別途マイグレーションツールの導入も決めました。

  • DBマイグレーションで実行する SQL ファイルをリポジトリで管理できるようにする
    • どのようなSQL/DDLを実行したのかが明示的にわかるようにしたい
  • DBマイグレーションの実行状況を管理できるようにする
    • 現状は記録が残せていないので、mysqldef で差分が出たときに実行漏れなのか、あるいは実行したけど想定通りに動いていないのかの区別がつかない

で、上記を満たすマイグレーションツールとして、sql-migrateを採用しました。
Flywayと迷っていたのですが、FlywayはMySQL5.7系は無料のCommunityEditionでは対応不可だったので断念せざるを得ず、、、
まぁsql-migrateは前職でも導入していて、CI/CD(CircleCI)にも組み込んで使っていたので、導入自体は簡単だったのでそこは良かったです🥳

sql-migrateについて詳しい説明や使用感はここでは割愛しますが、Go製なこともあり、私が関わったGoのバックエンドのプロジェクトでは必ずといっていいほど採用されていましたね。
気になるかたは公式見てみると良いと思います!

Cloud BuildからCloud Run Jobsでの実行

前述の通り今までのDBマイグレーションは開発者がローカル環境からCloud SQL Proxyで各環境のCloud SQLに接続してmake経由でmysqldefコマンドを叩いていました。
手動だとどうしてもヒューマンエラーがあり、また作業する開発者のストレスもそれなりにあるので、できるだけCIツールなりで半自動化したいところです。

弊プロダクトのAPIサーバーはCI/CDでCloudBuildを利用してビルド&デプロイを行っていたので、APIサーバーのデプロイステップの手前にDBマイグレーションステップを差し込めないかなぁーと考えていたところ、下記のような記事を見て、なるほどCloudRunJobsをキックすれば良いのか!とこちらを採用しました。

Cloud RunのDBマイグレーションどうする問題にCloud Runジョブで対処する

Cloud Run Jobsの起動オプション、特にCloud SQLの接続に関わるVPCコネクタやMySQLユーザー/パスワード周りの環境別の設定は、既存のAPIサービス用設定をそのまま使いまわせてめちゃくちゃラクでした。
ENV等書き換えて伏せていますが、CloudBuildからキックするCloud Run Jobsは下記のような設定です。
上記の参考URLにもあったとおりimageのタグだけ最新のコミットハッシュにして、最新のDockerイメージを使うようにしているだけですね。

gcloud beta run jobs update db-migration \
  --region=$REGION \
  --vpc-connector=$VPC_CONNECTOR \
  --vpc-egress=$VPC_EGRESS \
  --service-account=$SERVICE_ACCOUNT \
  --image=gcr.io/prj-id/schema:$COMMIT_SHA \
  --task-timeout=$TASK_TIMEOUT \
  --tasks=1 \
  --parallelism=1 \
  --max-retries=0 \
  --execute-now \

あとはDockerfileですが、sql-migrateが叩ければ良いのでこちらもシンプルに下記のような形にしています。

FROM golang:1.18-alpine

RUN apk add --update --no-cache ca-certificates gcc musl-dev make

WORKDIR /go/project-dir/schema

RUN go install github.com/rubenv/sql-migrate/...@latest

CMD ["sql-migrate", "up", "-config=migration/config.yml", "-env=cloudrunjobs"]

project-dir/schema配下にあるmigration/config.ymlを指定してsql−mirate upをかけているだけですね。
sql-migrateのenv指定はcloudrunjobsとしています。

sql-migrateのconfig.ymlは下記となっています。

# Cloud Run Jobsでの実行用
cloudrunjobs:
  dialect: mysql
  datasource: ${SERVER_MYSQL_USER}:${SERVER_MYSQL_PASSWORD}@tcp(${SERVER_MYSQL_HOST}:${SERVER_MYSQL_PORT})/${SERVER_MYSQL_DATABASE}?parseTime=true&charset=utf8mb4
  dir: migration/sql
  table: migration_history

# 開発者ローカルでの実行用
development:
  dialect: mysql
  datasource: ${USER}:${PASSWORD}@tcp(${HOST}:${PORT})/${DB}?parseTime=true&charset=utf8mb4
  dir: migration/sql
  table: migration_history

ローカルとClour Run Jobsでは利用しているENV名が違うためにそれぞれ別の定義としていますが、sql-migrateの便利な点として、実行時に-envオプションを指定しない場合はデフォルトでconfig.yml内のdevelopmentブロックの設定を採用してくれるところです。
これにより、開発者ローカル実行時はきわめて簡素なsql-migrate upだけで済むようにしています。
※ほんとはconfigファイル名もデフォルトから変えているのでsql-migate up -config=migration/config.ymlになります
※make経由で叩いているのでさらに簡素にmake schema.migrateとかになっています

おわりに

いかがだったでしょうか?

弊プロダクトでは月1〜2回のリリースを行っており、リリースのたびにAPIチームがDBマイグレーションをローカルから行うというストレスがかかる作業に追われていましたが、今ではCloud Buildのトリガーをキックするだけになりました。

本当はリリース用のCloud Buildトリガーに組み込むところをゴールとしていたのですが、リリース前にマイグレーションが適用されるリスクや、リリース失敗による切り戻し等を考えて今は別のトリガーとして設定されています。
最終的にはリリース用トリガーと一体化したいなと思いつつ、まずは開発者体験改善の大きな一歩になったかなと思っています。
プロダクトを良くするのは当然として、プロダクトを開発する人たちの体験も、アーキテクチャの改善やCI/CDツールの活用でもっともっとあげていきたいですよね!

最後までお読みいただきありがとうございました😌

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1