GCP Workflowsを利用して、Cloud SQLの開発環境用データベースリストアを自動化する
開発者体験の観点で、本番環境のデータベースからバックアップを取得して開発(検証)環境用にリストアして利用するという事例を目にします。
AWS での実装例が中心ですが、普段 GCP を利用している自分も同様の処理を利用したいと思い、GCP Workflows を利用して検証しました。
- https://blog.studysapuri.jp/entry/2022/05/16/080000
- https://engineer.blog.lancers.jp/sre/auto-masking/
- https://techblog.kayac.com/automate-initializing-databases-for-staging
この記事では、サンプル実装の紹介と Workflows 実装時の Tips や注意点についてまとめます。
(詳細は後述しますが、ワークフローや関連リソースは Terraform で管理しているため、Terraform のサンプルコードも記載しています。)
実装したコードに関しては、下記のレポジトリに置いています。
動作検証した環境は下記の通りです。
❯ sw_vers
ProductName: macOS
ProductVersion: 12.0.1 // mac OS Monterey
BuildVersion: 21A559
❯ terraform -v
Terraform v1.1.3
on darwin_arm64
Your version of Terraform is out of date! The latest version
is 1.2.2. You can update by downloading from https://www.terraform.io/downloads.html
構成
ワークフローで実行する処理は、下記の通りです。
- Cloud SQL のバックアップからデータベースをリストアする
- Cloud Functions を利用して任意の SQL (e.g. データマスキング用の SQL) を実行する
- SQL 実行後の Cloud SQL インスタンスからバックアップを作成する
2 に関して、実装サンプル内では Python
(SQLAlchemy
) を利用して、Cloud Functions からテスト用の SQL を Cloud SQL に対して実行しています。
(実装サンプル内の SQL はマスキング用の SQL ではないので、ご了承ください。)
GCP workflows について
今回は、GCP で利用可能なワークフローサービスの一種である GCP Workflows を利用して実装しました。
同じワークフローサービスとして Cloud Composer もありますが、下記の観点で Workflows を利用することにしました。
(そもそも、 Cloud Composer で同様の処理が実装できるのかどうかも確認していませんが...)
- GCP マネージドサービスとの統合機能が充実
- Cloud Scheduler 経由のワークフロー実行が可能
利用に際しては、下記のワークフローサービスの比較用のドキュメントを参考にしました。
ワークフローのスケジュール実行
Cloud Scheduler を利用して、任意のワークフローをスケジュール実行できます。
実行時のリクエスト経由で任意のデータを JSON 形式で渡してワークフローで利用できますが、今回のケースでは利用していません。
ワークフローに渡したいデータとしては下記のようなリソース関連の設定値が中心なので、Terraform 側で管理するように寄せています。
- バックアップ対象の Cloud SQL のインスタンス名
- 実行対象の Cloud Function 名
こちらに関しても詳細は後述します。
http_target {
http_method = "POST"
uri = "https://workflowexecutions.googleapis.com/v1/${google_workflows_workflow.sample_workflow.id}/executions"
body = base64encode("{\"argument\":\"\"}")
oauth_token {
service_account_email = google_service_account.sample_scheduler_account.email
}
}
ワークフローと関連リソースの管理
先述の通り、ワークフローや関連する GCP のリソースは Terraform で管理しています。
ワークフローの定義自体は YAML ファイルに書き出していますが、ワークフローの作成は Terraform で行うようにしています。
また、ワークフローに渡す設定値 (e.g. Cloud Function の関数名 etc.) の管理も含めて、基本的に Terraform に寄せています。
Cloud SQL のインスタンス名など、必要な設定値を各 Terraform リソースの Attributes として参照できるからです。
# https://www.terraform.io/language/functions/templatefile
source_contents = templatefile("${path.module}/workflows/restore_db.yaml", {
project = "${var.project}",
region = "${var.region}",
tier = "db-n1-standard-1",
function_name = google_cloudfunctions_function.sample_function.name,
instance = "sample-instance-v1",
database_name = "sample-db-v1",
})
ワークフローの定義ファイルの管理
ワークフローの定義自体は YAML ファイルに書き出して、tf ファイルとは別で管理しています。
一方で、Terraform へワークフローの定義を展開するために、templatefile
Function を利用して Terraform のコードに展開しています。
(詳細には、テンプレート用の変数を使用して、指定した YAML をテンプレートとしてレンダリングします。)
main:
steps:
- init:
assign:
- project: ${project}
- instance: ${instance}
- random_id: $${string(int(sys.now()))}
# https://www.terraform.io/language/functions/templatefile
source_contents = templatefile("${path.module}/workflows/restore_db.yaml", {
project = "${var.project}",
region = "${var.region}",
tier = "db-n1-standard-1",
function_name = google_cloudfunctions_function.sample_function.name,
instance = "sample-instance-v1",
database_name = "sample-db-v1",
})
実際に terraform apply
を実行すると、下記のように変数が展開されていることがわかります。
(e.g. init step で変数割り当てされている instance
)
google_workflows_workflow.sample_workflow: Refreshing state... [id=projects/<gcp_project_id>/locations/us-central1/workflows/sample-workflow]
google_cloud_scheduler_job.sample_job: Refreshing state... [id=projects/<gcp_project_id>/locations/us-central1/jobs/sample-scheduler-job]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# google_workflows_workflow.sample_workflow will be updated in-place
~ resource "google_workflows_workflow" "sample_workflow" {
id = "projects/<gcp_project_id>/locations/us-central1/workflows/sample-workflow"
name = "sample-workflow"
~ source_contents = <<-EOT
# https://cloud.google.com/workflows/docs/samples/workflows-connector-sqladmin?hl=ja
main:
steps:
- init:
assign:
- project: <gcp_project_id>
- instance: sample-instance-v1
- random_id: ${string(int(sys.now()))}
...
- return_value:
return: ${resp}
EOT
ワークフロー内の init
のステップで Assign されている変数は後続のステップで利用できるようになるため、templatefile
関数の vars → ワークフロー内の init step → ワークフロー内の任意の処理という形で変数を渡していきます。
この処理は、変数割り当て (assign
) という Workflows の標準構文です。
main:
steps:
- init:
assign:
- project: ${project}
- instance: ${instance}
- random_id: $${string(int(sys.now()))}
# 1. setup database from backup
- create_instance:
call: googleapis.sqladmin.v1beta4.instances.insert
args:
project: $${project}
body:
kind: "sql#database"
settings:
tier: ${tier}
project: $${project}
backendType: "SECOND_GEN"
name: $${instance + "-" + random_id}
rootPassword: thisisroot
また、一点注意点として、通常の Workflow の構文とは異なり、Terraform を利用してワークフロー定義内で変数展開をする際には $$
として定義する必要があります。
tier: ${tier}
のように、Terraform のテンプレートレンダリング時に反映される変数は問題ないですが、project: $${project}
のようにワークフロー内で変数を参照する場合はエスケープしないと terraform apply
の際にエラーになります。
Workflow のTerraform公式ドキュメントにも、下記のような記載があります。
FYI, In terraform you need to escape the $$ or it will cause errors.
実運用に向けて実装が必要なこと
最低限必要な処理を実装した一方で、実環境で運用する上で実装が必要な処理が一部残っているので後々追記できればと思います。
- エラーハンドリング
- ワークフローの処理が途中で失敗した場合に、SQL 実行用のインスタンスを削除したい
- ログ出力
- Cloud SQL の最新バックアップの動的な指定
- 実行したい SQL の別ファイルへの外出し
終わりに
今回、GCP Workflows を利用して Cloud SQL のデータベースリストア処理を実装してみました。
ワークフローの定義ファイルの管理など一部工夫が必要な部分はありますが、それを差し引いてもシンプルに実装できました。
ワークフローサービスを利用するのは今回が初めてなので、指摘・コメントあれば、ぜひお待ちしております。
Discussion