Open9

Cloud Deploy + Cloud Run + RDB(Spanner) における DB スキーママイグレーションの設計

ism.msiism.msi

コンテキスト

いままでは GKE 前提で CI/CD を組んできたが、今回 Cloud Deploy + Cloud Run に挑戦中。

おそらくいままでだと GitHub Actions や initContainer で吸収してきた部分を別途の責務として切り出してデザインする必要あり。さてどうしよう

ism.msiism.msi

ref. 『色々できるぞ! Cloud Deploy!!』

https://zenn.dev/google_cloud_jp/articles/cloud-deploy-updates-2023

デプロイ時、pre-deploy / post-deploy hooks が利用できるようになりました。
従来、デプロイ時にスキーマを変更する必要がある場合などは Cloud Deploy とは別にマイグレーションを設定しておく必要がありましたが、今後はこの機能を使って Cloud Deploy に集約することができます。

これが公式としても推奨されてそうな設計。

  • skaffold.yamlcustomAction を定義
  • clouddeploy.yamlcustomActions を呼び出す設定を追加 [1]

skaffold.yaml の提供してくださっているサンプルを見ると:

https://github.com/iwanariy/sample-cloud-deploy-2023-update/blob/c5cc54ed48be583070c7c424e8ed9d9b213c182f/skaffold.yaml#L23-L29

customActions[].containers[].image でイメージを指定して CLI を動かす。ここに golang-migrate 実行用イメージを作って指定すればよい。

ただ、この際に実行したいマイグレーションの状態は PR マージ時のそれでなくてはいけない。
なので release-please に打ってもらった semver に併せてイメージも作る建て付けの方がよさそう。

現状だと release PR のマージ時に Cloud Build 実行(skaffold build)してアーティファクト生成 → gcloud deploy releases create しているので同タイミングでビルドすればよいはず。

脚注
  1. 私は Terraform の google_clouddeploy_delivery_pipelineclouddeploy.yaml 相当の設定を書いてるのでこちらで対応。 ↩︎

ism.msiism.msi

わかっていない部分

そうなると以下が疑問点になる:

  • Cloud Deploy Hooks で実行されるコンテナの実行主体の GSA は?誰に Spanner の権限を渡せばよい?
ism.msiism.msi

https://cloud.google.com/deploy/docs/hooks?hl=ja

デプロイ前のアクションまたはデプロイ後のアクション、あるいはその両方を対象としたアクションを実行するように、Cloud Deploy と Skaffold を構成できます。これらのプログラムは、「フック」と呼ばれます。 デプロイ前フックとデプロイ後フックは、ロールアウトでデプロイ前とデプロイ後のジョブとして実行されます。

  • ロールアウトとは?
  • ジョブとは?
ism.msiism.msi

ロールアウトとは?

https://cloud.google.com/deploy/docs/deployment-strategies/manage-rollout?hl=ja

ロールアウトは、リリースをターゲットに関連付ける Cloud Deploy リソースです。

さらに細かくすると

デプロイされる変更(コード、構成、またはその両方)を表す Cloud Deploy リソース」を「アプリケーションをデプロイする特定のランタイム環境(Kubernetes クラスタ、Cloud Run サービス、またはその他のサポートされているランタイム)。また、その環境の構成。」に関連付ける Cloud Deploy リソースです。

ジョブとは?

文言を抜粋してまとめ直すと

ロールアウトは、1 つ以上のフェーズで構成されます。標準のデプロイ戦略の場合は、stable という 1 つのフェーズのみです。各ロールアウト フェーズには、1 つ以上のジョブが含まれます。

つまり標準デプロイなら、ロールアウトにつき1つ以上ジョブが生える。少なくとも1つのジョブがリリースをターゲットにデプロイすることでロールアウトの実装を担うはず。prehook, posthook な customActions はここに対して別ジョブとして複数紐づいてくる、と認識した。

ism.msiism.msi

実際に CloudDeploy の実行結果を眺めて確認してわかったことは「Cloud Deploy のジョブ実行は Cloud Build ジョブとして達成される」こと。

なので Cloud Build 上のジョブに対して CloudSQL Proxy をどうするか?を解消すれば認証認可や接続の話は Cloud Build の資料をそのまま流入すればよいことがわかる[1]

まずは雑にサンプルを作ってみて、その上で↓を眺めながらメンタルモデルを作るのがよさそう。
https://cloud.google.com/deploy/docs/architecture?hl=ja

脚注
  1. 今回は Spanner なので Auth proxy の話は気にしなくてよい ↩︎

ism.msiism.msi

Cloud Deploy 実行環境

https://cloud.google.com/deploy/docs/execution-environment?hl=ja

ここに「Cloud Build ワーカープール」って書いてあった...

Cloud Deploy の実行環境は、Cloud Deploy がレンダリング、プリデプロイ、デプロイ、検証、ポストデプロイのオペレーションを実行する環境です。実行環境は、次のコンポーネントで構成されます。

  • Cloud Deploy がレンダリング、プリデプロイ、デプロイ、検証、ポストデプロイのオペレーションを実行する Cloud Build ワーカープール(デフォルトまたはプライベート)
  • これらのアクションを実行するために Cloud Deploy を呼び出すサービス アカウント(デフォルトまたは代替)
  • レンダリングされたマニフェストの Cloud Storage 内の保存場所(デフォルトまたは代替)
  • オペレーションの Cloud Build タイムアウト(デフォルトまたはカスタム)

デフォルト

https://cloud.google.com/deploy/docs/execution-environment?hl=ja#defaults

必要そうな部分を抜粋

デフォルトでは、Cloud Deploy はデフォルトの Cloud Build ワーカープールで実行されます。

デフォルトでは、Cloud Deploy は、デフォルトの Compute Engine サービス アカウントを使用します。

この値は、Cloud Deploy がレンダリングされたマニフェストを保存する Cloud Storage バケットです。デフォルトでは、Cloud Deploy は、Cloud Deploy リソースと同じリージョンに Cloud Storage バケットを作成します。形式は次のとおりです。

<location>.deploy-artifacts.<project ID>.appspot.com

デフォルトでは、Cloud Build が実行するオペレーションのタイムアウトは 1 時間です。


これを踏まえてやりたいことを定義し直すと、CloudBuild のデフォルトワーカープール上で Spanner にアクセスできる GSA(DAC) で golang-migrate を実行するイメージを CloudDeploy の prehook から kick したい、となる。

ism.msiism.msi

各環境で Spanner の接続情報を拾うためには、Target, Stage などの情報から Environment を拾う必要がある。

↓ これは PREDEPLOY とは異なる VERIFY 実行環境で使える環境変数の一覧。

https://cloud.google.com/deploy/docs/verify-deployment?hl=ja#available_environment_variables

ここに

CLOUD_RUN_PROJECT
タイプが RUN のターゲットの場合、Cloud Run サービスが作成されたプロジェクト。

がある。Cloud Run を動かしたいそれぞれのプロジェクトに Spanner は横付けしてあるので、これが使える。 POSTDEPLOY でも CLOUD_RUN_PROJECT が環境変数に入っているのを確認したので PREDEPLOY にも入りそう!

instanceId, databaseName を共通化すればこの情報だけでマイグレーション対象を Target で切り替えられる。