TerraformでCloud Run + Firestore/Cloud SQL環境をコード化する実践ガイド
~実際に検証してみよう!~
- はじめに
- 前提条件
- ディレクトリ構成
- Terraformとは
- Step 1:backend.tf - 基盤設定
- Step 2:variables.tf - 変数の定義
- Step 3:main.tf - リソース定義
- Step 4:*.tfvars - 変数の値
- 実際の検証手順と結果
- 検証で分かった実務的なポイント
- トラブルシューティング
- まとめ
はじめに
皆さん、Google Cloudでサービスの構築や利用を開始する時、どのように行っていますか?私はこれまで、コンソール画面から1つずつ設定していました。しかし、開発・検証・本番と環境が増えたり、チームメンバーが増えたりすると、次のような問題が必ず出てきます。
- 「本番と検証環境で設定が微妙に違う」がいつの間にか発生する
- 誰がいつどの設定を変えたのか分からない
- 環境を作り直すたびに手順書を見ながら時間をかけて設定する
実は、これらの課題を解決する手段として、「Terraformで構成をコード化する」方法が存在します!この記事では、実際に検証した経験を元に、以下の内容について説明します。
- Terraformとはどのようなツールか
- 実際にTerraformコードで動かすにはどうすれば良いのか
※仕様確認:本記事の技術内容は2026年9月時点のGoogle Cloud/Terraform公式情報を確認しています。検証に使用したTerraform/Providerのバージョンは「実際の検証手順と結果」に記載しています。
前提条件
以下の前提条件の元、検証しています。
- GCPプロジェクトが作成済みで、課金が有効になっていること
- 以下のツールがローカルにインストールされていること
- Terraform(検証時:v1.15.8)
- gcloud CLI
gcloud auth application-default loginでTerraformからGoogle CloudへアクセスするためのApplication Default Credentials(ADC)を用意していること
ディレクトリ構成
環境が増えても破綻しないように、最初から次の構成にしておきます。
.
├── environments
│ ├── dev.tfvars
│ └── prod.tfvars
├── backend.tf
├── main.tf
└── variables.tf
※個人検証ではローカルstateを利用できますが、チーム開発では複数人がTerraformを実行するため、GCSバックエンドなどのリモートstateを利用し、アクセス制御やバージョニングを行うことをおすすめします。本記事では、state保存用のGCSバケットは事前に作成する前提とします。現在、Cloud StorageのCLI操作にはgcloud storageの利用が推奨されています。なお、state保存用バケットを別のTerraform構成(bootstrap用)で管理する方法もあります。
Terraformとは
そもそもTerraformとは何か、皆さんご存知でしょうか?
一言で言うと、「クラウドのサーバーやネットワークなどの構成をテキスト(コード)で定義し、コマンドで作成・変更・管理できるツール」です。
具体的には、HashiCorp社が開発・提供する、クラウドや各種ITインフラの構成をコードとして定義・管理するIaC(Infrastructure as Code)ツールです。
AWS、Google Cloud、Microsoft Azureなどの主要クラウドサービスをはじめ、GitHubやDatadogといったSaaS製品まで、さまざまなリソースを同じTerraformのワークフローで管理できます。
Terraformでできる基本的な操作は以下の通りです。
- Init:プロバイダーのプラグインやモジュールをダウンロードしてワークスペースを初期化
- Plan:定義コードと現在のインフラ状態を比較し、変更予定の差分を表示
- Apply:計画された内容を実行し、クラウド上にリソースを自動プロビジョニング
- Destroy:検証環境の撤去時など、Terraformで管理しているリソースの削除を実行
Terraformを利用するメリットは、以下が挙げられます。
- 手作業によるミスを減らせる:同じコードから構築することで、クリックミスや設定漏れを減らしやすくなる。
- 同じ構成を再現しやすい:変数を切り替えることで、開発・検証・本番などの環境差分を管理しながら構成を再現しやすくなる。
- 変更履歴を追跡しやすい:TerraformコードをGitなどで管理することで、「誰が・いつ・何を変更したか」を追跡しやすくなる。なお、Git上のコードを戻すだけでデータやリソースの状態まで必ず元に戻るわけではないため、変更時は
terraform planで差分を確認します。
皆さん、Terraformとは何か分かりましたでしょうか?
次の項目からは、私が実際に試した、Terraformを使ってGoogle Cloudに環境を構築する方法を説明していきます。Terraformのファイルごとに見ていきましょう!
Step 1:backend.tf - 基盤設定
本記事では、Terraform本体やGoogle Cloud Providerのバージョン要件、stateの保存先、Provider設定をbackend.tfにまとめます。Terraformでは同じディレクトリ内の.tfファイルをまとめて1つの構成として評価するため、backend.tfというファイル名自体に特別な意味があるわけではありません。
terraform {
required_version = ">= 1.7.0" # Terraformのバージョン指定
required_providers { # 使うプロバイダー(GCP、AWS等)
google = {
source = "hashicorp/google"
version = "~> 5.30"
}
}
# tfstateの保存先
# 検証用にローカルバックエンドを使用(本番ではGCSを使用)
# backend "gcs"{
# bucket = "YOUR_PROJECT_ID-terraform-state"
# prefix = "app"
# }
}
# GCPへの接続設定
provider "google" {
project = var.project_id
region = var.region
}
本記事では、次の内容をこのファイルにまとめています。
- Terraformのバージョン
- プロバイダー(Google Cloud、AWS等)の指定
- tfstateの保存場所(GCS、S3等)
- プロバイダーへの接続設定
Step 2:variables.tf - 変数の定義
ここでは、コード内で使う変数の「型」「説明」「デフォルト値」などを定義します。
「テンプレートの入力項目を定義する」イメージで、環境ごとに変わる値は別ファイル(tfvars)などから渡します。
<検証で使用した中身>
variable "project_id" {
type = string # 文字列型
description = "GCP Project ID" # 説明
}
variable "region" {
type = string
default = "asia-northeast1" # デフォルト値(省略可能)
description = "GCP region for resources"
}
variable "environment" {
type = string
default = "dev"
description = "Environment name (dev/prod)"
}
variable "container_image" {
type = string
description = "Container image for Cloud Run"
}
variable "db_password" {
type = string
sensitive = true
description = "Database password (store in Secret Manager)"
}
ここの内容に含めること
- 変数名
- データ型(string、number、bool等)
- 説明文
- デフォルト値(あれば)
Step 3:main.tf - リソース定義
本記事では、実際に作成するGoogle Cloudリソース(VPC、Cloud Run等)をmain.tfにまとめます。
言うなれば「設計図の本体」のようなもので、「このVPCを作って、このCloud Runを作って...」という実際の構成を定義します。なお、main.tfというファイル名自体に特別な意味があるわけではありません。
resource "google_compute_network" "vpc" {
name = "app-vpc" # VPCネットワークを作る
}
resource "google_cloud_run_v2_service" "app" {
name = "app" # Cloud Runサービスを作る
location = var.region # 変数を参照
template {
containers {
image = var.container_image
}
}
}
ここの内容に含めること
- 作成したいリソース(VPC、DB、サービス等)
- リソース間の依存関係(depends_on)
- 設定値(変数参照も含む)
main.tfの中身は長いため、項目を分けて、検証結果と共に説明します。
Step 3-1:必要なAPIの有効化
Cloud Run・Firestore・Cloud SQLを使うには、まず事前にAPIを有効化する必要があります。
tosetのリスト型引数の中に、有効化したいAPIを記述します。
resource "google_project_service" "project" {
for_each = toset([
"run.googleapis.com",
"vpcaccess.googleapis.com",
"servicenetworking.googleapis.com",
"sqladmin.googleapis.com",
"firestore.googleapis.com",
"secretmanager.googleapis.com"
])
project = var.project_id
service = each.key
disable_on_destroy = false
}
Tip:disable_on_destroy = falseにしているのは、terraform destroyした際にAPI自体を無効化してしまうと、他のリソースが依存している場合に思わぬ影響が出るためです。
検証結果:実際に6つのAPIが正しく有効化されました。ただし、本検証プロジェクトではCompute Engine APIやIAM APIなど、一部の基盤APIが既に有効になっていました。新規プロジェクトから再現する場合は、compute.googleapis.comやiam.googleapis.comを含め、構成で利用するAPIが有効か事前に確認してください。また、API有効化直後は反映まで時間がかかることがあり、初回applyでエラーになる場合があります。その場合は少し待ってから再度terraform applyを実行します。
Step 3-2:Firestore構成
次に、Firestoreの構成をコード化します。本記事では、Cloud Runから利用するFirestore Nativeモードの(default)データベースを1つ作成します。
resource "google_firestore_database" "database" {
project = var.project_id
name = "(default)"
location_id = var.region
type = "FIRESTORE_NATIVE"
depends_on = [
google_project_service.project
]
}
注意:Firestoreは1つのGoogle Cloudプロジェクトに複数のデータベースを作成でき、Standard/Enterpriseともプロジェクトあたり最大100データベースが基本上限です。本記事では、クライアントライブラリなどで既定の接続先となる(default)データベースを利用します。既にコンソールなどから(default)データベースを作成済みで、Terraform管理へ切り替える場合はterraform importで既存リソースを取り込みます。
Step 3-3:Cloud SQL構成(プライベートIP接続)
Cloud SQLにはパブリックIPを利用する接続方法もありますが、本記事ではインターネットを経由せずVPC内から接続できるプライベートIP構成を採用します。Private Services Accessを利用するため、VPCとService Networking接続を先に用意する必要があり、Firestoreと比べて構成要素が増えます。
# VPC
resource "google_compute_network" "vpc" { ... }
# プライベートIP範囲の予約
resource "google_compute_global_address" "private_ip_address" { ... }
# VPCとGoogleサービスのピアリング
resource "google_service_networking_connection" "private_vpc_connection" { ... }
# Cloud SQL インスタンス
resource "google_sql_database_instance" "instance" {
...
settings {
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.vpc.id
}
}
deletion_protection = var.environment == "prod"
depends_on = [
google_service_networking_connection.private_vpc_connection
]
}
Tip:deletion_protection = var.environment == "prod" のように環境変数で分岐させると、Terraformによる本番インスタンスの誤削除を防ぎやすくなります。ただし、このdeletion_protectionはTerraformからの削除を保護する設定です。Cloud Console、gcloud CLI、APIを含む削除保護が必要な場合は、Cloud SQL側のsettings.deletion_protection_enabledも検討します。
注意:検証時、servicenetworking.services.addPeering権限が不足してエラーになるケースがありました。Private Services Accessの作成にはこの権限を含むネットワーク権限が必要で、Google Cloudの公式ドキュメントではroles/compute.networkAdmin(Compute Network Admin)に必要な権限が含まれると案内されています。Ownerは非常に広い権限を持つため、実運用では必要な権限に絞って付与してください。また、Organization Policyによる制約はOwner権限でも回避できないため、IAM不足と組織ポリシー制約は分けて確認します。
また、データベースパスワードはソースコードや.tfvarsへ直接書き込まず、Secret Managerで管理します。ただし、Terraformのsensitive = trueはCLI出力をマスクする機能であり、通常のsecret_dataに渡した値はTerraform stateに保存されます。本検証では下記の方法を使いましたが、本番ではstateのアクセス制御・暗号化を行うほか、対応する新しいTerraform/Google Providerを利用できる場合はsecret_data_woなどのwrite-only引数、またはシークレット値だけをTerraform外から登録する方法も検討してください。
# Secret Managerにシークレットを作成
# ※検証時のコード。secret_dataに渡した値はTerraform stateにも保存されます。
resource "google_secret_manager_secret" "db_password" { ... }
# シークレットにバージョン(値)を追加
resource "google_secret_manager_secret_version" "db_password_version" {
secret = google_secret_manager_secret.db_password.id
secret_data = var.db_password
}
検証結果:Secret Managerのリソースはスムーズに作成でき、IAM連携も正しく動作しました。
Step 3-4:Cloud RunからCloud SQL/Firestoreにアクセスする
Cloud Runサービス自体の定義と、それに紐づくIAM権限をまとめて作成します。本検証ではServerless VPC Access Connectorを利用しました。現在、Google CloudではCloud RunからVPCへ接続する方法としてDirect VPC egressが推奨されているため、新規設計ではまずDirect VPC egressを検討し、要件に応じてConnector方式を選択してください。
# VPCアクセスコネクタ
resource "google_vpc_access_connector" "connector" { ... }
# Cloud Run サービス
resource "google_cloud_run_v2_service" "default" {
...
template {
...
vpc_access {
connector = google_vpc_access_connector.connector.id
egress = "PRIVATE_RANGES_ONLY"
}
}
}
注意:本検証のConnector方式ではvpc_accessの設定がないと、Cloud RunからプライベートIPのCloud SQLへ到達できません。Direct VPC egressを利用する場合は、Cloud RunサービスにVPCネットワーク/サブネットを設定します。一方、Firestoreは通常GoogleのマネージドAPI経由でアクセスするため、Cloud SQLのようなプライベートIP接続のためのVPC設定は通常不要です。利用するデータサービスによって、Cloud Run側のネットワーク設計が変わる点は押さえておきましょう。
検証結果:VPC Access Connectorは問題なく作成できました。作成に約3〜4分かかるため、terraform apply実行時はしばらく待つ必要があります。また、削除時も同様に3〜4分かかりました。
Step 4:*.tfvars - 変数の値
ここからは、variables.tfで定義した変数に、実際の値を入れる、* tfvarsファイルについて説明していきます。environments/dev.tfvarsとenvironments/prod.tfvarsを用意し、環境差分だけを変数化します。
# environments/dev.tfvars
project_id = "my-gcp-project-dev"
environment = "dev"
...
# environments/prod.tfvars
project_id = "my-gcp-project-prod"
environment = "prod"
...
適用時は以下のように指定します。
terraform init
terraform plan -var-file="environments/dev.tfvars"
terraform apply -var-file="environments/dev.tfvars"
注意:db_passwordのような機密値は.tfvarsへ直接書かず、環境変数やCI/CDのSecret機能などから渡します。ただし、入力経路をSecretにしても、通常のTerraformリソース引数として利用した値はstateへ保存される場合があります。GCSバックエンドのアクセス制御や暗号化を行い、対応バージョンを利用できる場合はwrite-only引数も検討してください。
実際の検証手順と結果
この記事のTerraformコードを実際に検証した手順と結果は以下の通りです。
検証環境
- Terraform v1.15.8
- Google Provider v5.45.2
- 組織管理下のプロジェクトで実施
検証ステップ
コマンドプロンプトで以下のコマンドを打って検証を行います。
1. 文法チェック (terraform validate)
terraform init -backend=false
terraform validate
結果:Success! The configuration is valid.
2. 差分確認(terraform plan)
結果:Plan:22 to add, 0 to change, 0 to destroy.
terraform plan -var-file=verify.tfvars
以下の22リソースが正しく認識されました:
- API有効化 × 6
- VPC、プライベートIP範囲、Service Networking接続
- Cloud SQLインスタンス、データベース、ユーザー
- Firestore Nativeデータベース
- Secret Manager(シークレット + バージョン)
- サービスアカウント、IAM権限 × 4
- VPC Access Connector
- Cloud Runサービス、パブリックアクセス権限
3. 実リソース作成(terraform apply)
terraform apply -var-file=verify.tfvars -auto-approve
結果:15リソースの作成成功、7リソースで権限エラー
作成できたリソース(15個):
- API有効化 × 6
- VPCネットワーク
- プライベートIP範囲予約
- Secret Manager(シークレット + バージョン + IAM)
- サービスアカウント
- IAM権限 × 3
- VPC Access Connector(作成に約3分30秒)
権限不足で作成できなかったリソース(7個):
- Firestoreデータベース(
Error 403:The caller does not have permission) - Service Networking Connection(
servicenetworking.services.addPeering権限不足) - 上記に依存するCloud SQL関連リソース
重要:今回の検証では、IAM権限や組織側の制約が原因で一部リソースを作成できませんでした。IAM権限の不足であれば必要なロール/権限の付与で解消できますが、Organization Policyによる制約はOwnerロールでも回避できません。エラー時は、IAMとOrganization Policyを分けて確認してください。
4. クリーンアップ(terraform destroy)
terraform destroy -var-file=verify.tfvars -auto-approve
結果:Destroy complete! Resources:15 destroyed.
VPC Access Connectorの削除に約3分30秒、全体で約4分で完全にクリーンアップできました。
検証で分かった実務的なポイント
- API有効化の反映時間:
API有効化直後は反映に1〜2分かかり、すぐに依存リソースを作ろうとするとエラーになることがある - VPC Access Connectorの作成/削除時間:
それぞれ約3〜4分かかるため、terraform applyやdestroyは余裕を持って実行する - IAM/Organization Policyの影響:
企業プロジェクトでは権限や組織ポリシーの制約を受けるため、事前に必要権限を確認する。検証用プロジェクトを用意する場合も、本番と同じガードレールを意識して設計する - terraform destroyによる撤去:
今回の検証では、Terraformで作成できた15リソースを約4分で削除でき、検証環境の作り直しを効率化できた
トラブルシューティング
検証を行っている最中に、私が経験したエラーとそのトラブルシューティング方法を以下に記載します。
API有効化エラーが出る
Error:Error creating Connector:googleapi:Error 403:
Serverless VPC Access API has not been used...
対処法:API有効化直後は反映に1〜2分かかります。google_project_serviceにdepends_onを設定していても、初回applyでこのエラーが出ることがあります。2〜3分待ってから再度terraform applyを実行してみてください!
Service Networking Connection の権限エラー
Error:googleapi:Error 403:Permission denied to add peering
for service 'servicenetworking.googleapis.com'
対処法:servicenetworking.services.addPeeringを含む必要権限を確認します。
- Private Services Accessの作成権限を含む
roles/compute.networkAdmin、または必要権限を含むカスタムロールを検討 - 過剰なOwner付与を前提にせず、必要最小限の権限で対応
- 組織ポリシーで
constraints/compute.restrictVpcPeeringなどの制約がないか確認
Firestoreデータベース作成エラー
Error:Error creating Database:googleapi:Error 403:
The caller does not have permission
対処法:
roles/datastore.ownerロールが必要- 既に
(default)データベースが存在する場合はterraform importで取り込む
まとめ
今回の検証を経て、以下のようなことが分かりました。
- FirestoreはCloud RunからマネージドAPI経由で利用できるので、Cloud SQLのプライベートIP接続と比べるとネットワーク構成をシンプルにしやすい
- Cloud SQLをプライベートIPで利用する場合はVPCやPrivate Services Accessなどの構成が必要になる。Cloud RunからVPCへ接続する方法は、現在推奨されているDirect VPC egressをまず検討する
- 環境ごとの
.tfvars分離と、tfstateのGCS管理は最初から入れておくと後から苦労しない - Terraformで作成・削除を自動化すると、Cloud SQLのような継続課金リソースを含む検証環境も管理しやすくなる
初めてTerraformを触ってみたのですが、Google Cloudの環境構築をコードとしてまとめることで、作成・変更・削除の流れを整理しやすいと感じました!基本的な書き方とstateの扱い、権限やネットワークの考え方を押さえることで、より安全に活用できます。本記事が皆さんのTerraform活用のきっかけになれば嬉しいです。
Google Cloud の導入や活用でお困りのことがあれば、お気軽にお問い合わせください!
