概要 #
Cloud RunからCloud SQLへ接続する際、Database Passwordをどこへ渡すかで迷います。環境変数へ直接書けば手軽ですが、Terraform StateやCI Logへ残るとSecretが漏れる経路になります。
そこでDatabase PasswordはSecret Managerへ保存します。Cloud RunのRuntime Service Accountには必要な権限だけを付与する設計です。接続自体もIPアドレスではなくUnix Socketを使い、Cloud SQL Auth Proxyの経路に任せます。
Terraformの基本操作とDockerコマンドは前提としています。Cloud Run、Cloud SQL、Secret Managerを個別には触ったことがある方向けです。
Private IP、Connection Pooling、HA構成は扱いません。Password Rotationの自動化も扱いません。
検証すること #
- Cloud Run、Cloud SQL、Secret Manager、Artifact Registryを連携できること
- Write-only ArgumentでDatabase PasswordをStateへ保存せず登録できること
- Runtime Service Accountへ必要最小限の権限を付与できること
- Cloud RunからUnix Socket経由でPostgreSQLへ接続できること
前提環境 #
- Google Cloud CLI、Terraform 1.11以降、Docker
- Billingが有効な検証用Google Cloud Project
- Cloud Run、Cloud SQL、Secret Manager、Artifact Registry、IAMを操作できるGoogleアカウント
- 検証時のバージョン:Terraform 1.14.3、google 7.43.0
今回の構成 #
Cloud RunとCloud SQLはどちらもVPCの外にあります。接続にはUnix Socketを使います。
flowchart TB
Client["Invoker"]
subgraph GCP["Google Cloud"]
subgraph Project["Project"]
SA["Runtime Service Account"]
SM["Secret Manager
DBパスワード"]
subgraph Region["asia-northeast1"]
AR["Artifact Registry"]
Run["Cloud Run v2 Service"]
SQL["Cloud SQL
PostgreSQL 15"]
end
end
end
Client -->|"HTTPS + ID Token"| Run
Run -->|"起動時に pull"| AR
Run -->|"Unix Socket
/cloudsql/CONNECTION_NAME"| SQL
SM -.->|"環境変数へ注入"| Run
SA -.->|"cloudsql.client
secretmanager.secretAccessor"| Run
点線が権限と設定の注入です。Runtime Service Accountにcloudsql.clientがないと接続できません。
リクエストを受けてからDBへ届くまでの流れです。
sequenceDiagram
actor Client as Invoker
participant Run as Cloud Run
participant SM as Secret Manager
participant SQL as Cloud SQL
Note over Run,SM: 起動時にパスワードを取得
SM-->>Run: 環境変数へ注入
Client->>Run: HTTPS リクエスト
Run->>SQL: Unix Socket 経由で接続
SQL-->>Run: クエリ結果
Run-->>Client: HTTP 200
Unix Socketを使うため、IPアドレスやVPC Connectorの設定は要りません。
使用するTerraformコード #
PasswordをStateへ残しにくくする #
Terraform 1.11のWrite-only Argumentを使います。
resource "google_sql_user" "app" {
name = var.db_user
instance = google_sql_database_instance.postgres.name
password_wo = var.db_password
password_wo_version = var.db_password_version
}
resource "google_secret_manager_secret_version" "db_password" {
secret = google_secret_manager_secret.db_password.id
secret_data_wo = var.db_password
secret_data_wo_version = var.db_password_version
}
password_woとsecret_data_woは値そのものをStateへ保存しません。Password変更時はVersion値も増やして更新をTerraformへ伝えます。ただし入力元のtfvarsやCI Logの保護は別途必要です。
Cloud RunからCloud SQLへ接続する #
Cloud Run TemplateにCloud SQL Volumeを定義し、/cloudsqlへMountします。Applicationは/cloudsql/<connection-name>のUnix Socketを使う構成です。Runtime Service Accountには次だけを付与します。
roles/cloudsql.client- 対象Secretの
roles/secretmanager.secretAccessor - Artifact Registry Repositoryの
roles/artifactregistry.reader
実行手順 #
cd Advanced-Examples/06-cloudrun-cloudsql-postgresql
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform fmt -check
terraform validate
terraform apply
最初はHello ImageでInfrastructureを作成します。Artifact Registry、Cloud SQL、Secret Manager、Cloud Runなど合わせて17リソースです。Instance作成には10分ほどかかりました。
Apply complete! Resources: 17 added, 0 changed, 0 destroyed.
Outputs:
app_image_example = "asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/tf-adv-run-sql/cloudrun-sql:latest"
database_name = "appdb"
database_user = "appuser"
instance_connection_name = "YOUR_PROJECT_ID:asia-northeast1:tf-adv-run-sql-pg"
service_url = "https://tf-adv-run-sql-ldzccxoqeq-an.a.run.app"
sql_instance_name = "tf-adv-run-sql-pg"
付属Python ApplicationをBuildしてArtifact RegistryへPushします。container_imageを変更して再度Applyします。
gcloud auth configure-docker REGION-docker.pkg.dev
docker build -t REGION-docker.pkg.dev/PROJECT/REPOSITORY/app:latest app
docker push REGION-docker.pkg.dev/PROJECT/REPOSITORY/app:latest
terraform apply
GCP側で確認する #
Cloud SQL Instanceの状態を確認します。
gcloud sql instances describe "$(terraform output -raw sql_instance_name)"
state: RUNNABLE
databaseInstalledVersion: POSTGRES_15_18
RUNNABLEはInstanceが起動していることを示すだけで、Cloud Runから実際に接続できることは別に確認します。Cloud RunのURLへID Tokenを付けて呼び出します。
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"$(terraform output -raw service_url)"
{"connection":"cloud-sql-unix-socket","database":"appdb","status":"ok","user":"appuser"}
status: okとともに、Unix Socket経由でappdbへappuserとして接続できたことが分かります。
ヘルスチェックのパスにhealthzを使わない。 Cloud Runのデフォルトドメイン(*.run.app)では、パス/healthzがGoogle Frontendの段階で予約扱いになり、コンテナへ到達せず404になります(/healthなど他のパスは到達します)。このサンプルのApplicationも当初/healthzを使っていましたが、実際には到達できないEndpointでした。/healthにリネームして解消しています。
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"$(terraform output -raw service_url)/health"
{"status":"ok"}
後片付けと注意点 #
terraform destroy
Destroy complete! Resources: 17 destroyed.
Cloud SQL Instanceは停止中でも課金が発生し得るため、検証後は必ず削除します。ProductionではDeletion Protection、Backup、PITR、HA、Private IP、Connection Pool、Migration方法を追加で検討してください。Secretを環境変数として渡す場合、Application Logへ出力しないことも重要です。
まとめ #
- Cloud RunとCloud SQLをUnix Socketで接続できる
- Runtime SAのIAMをResourceごとに最小化できる
- Secret ManagerからDatabase Passwordを注入できる
- Write-only ArgumentでPasswordをTerraform Stateへ残しにくくできる
参考資料 #
次回 #
Advanced Examples 01〜06を通して、複数のGoogle Cloud Serviceを安全に連携する構成を確認できました。次はGKEプライベートクラスタと踏み台VMを作成します。