概要 #
Local Stateのままでは、PCを変えると続きを操作できません。複数人で同じ構成を触ることもできません。
では State Bucket 自体をTerraformで作ればよいのか。今度は、そのStateをどこに置くのかという循環にぶつかります。
解き方は、Bucketを作る構成と、それを使う構成を分けることです。Bootstrap構成はLocal Stateで作ります。demo/側は、そのBucketをRemote Stateとして参照する形にしました。
Terraformの基本操作は前提としています。Local Stateしか使ったことがない方向けです。
State Locking、Workspaceによる環境分離、CI/CDからのBackend利用は扱いません。削除順序を誤るとStateだけが残るため、後片付けの手順も確認します。
検証すること #
- Bootstrap構成でVersioning付きState Bucketを作成できること
- 別のTerraform構成をGCS Backendで初期化できること
- Remote State Objectと過去世代を確認できること
- Consumer、Backendの安全な削除順序を確認できること
前提環境 #
- Google Cloud CLIとApplication Default Credentials(ADC)
- Terraform 1.5以降
- Cloud StorageとBucket IAMを操作できる検証用Google Cloud Project
- 検証時のバージョン:Terraform 1.14.3、google 7.43.0
今回の構成 #
構成を2つに分けます。Bucketを作る側はLocal Stateのまま、それを使う側がRemote Stateを参照します。
flowchart TB
subgraph Local["手元の作業ディレクトリ"]
BootTF["Bootstrap 構成"]
BootState["terraform.tfstate
(Local State)"]
DemoTF["demo/ 構成"]
BootTF --- BootState
end
subgraph GCP["Google Cloud"]
subgraph Project["Project"]
subgraph GCS["Cloud Storage"]
Bucket["State Bucket
Versioning 有効"]
Obj["default.tfstate"]
Demo["demo 用 Bucket"]
Bucket --- Obj
end
end
end
BootTF -->|"作成"| Bucket
DemoTF -->|"backend で読み書き"| Obj
DemoTF -->|"作成"| Demo
Bootstrap 構成のStateだけが手元に残ります。ここを Remote にしようとすると、そのStateをどこへ置くのかという循環に戻ります。
初期化から削除までの流れは次のとおりです。
sequenceDiagram
participant Boot as Bootstrap 構成
participant Bucket as State Bucket
participant Demo as demo/ 構成
participant Res as demo 用 Bucket
Boot->>Bucket: apply(Local State で作成)
Demo->>Bucket: init -backend-config
Note over Demo,Bucket: State の置き場所を登録
Demo->>Res: apply
Demo->>Bucket: State を書き込み
Note over Demo,Res: 削除は逆順
Demo->>Res: destroy
Boot->>Bucket: destroy
削除の順序を誤ると、Bucketが先に消えてStateだけが行方不明になります。
使用するTerraformコード #
Backend用Bucketを先に作る #
resource "google_storage_bucket" "tfstate" {
name = "${var.bucket_name_prefix}-${var.project_id}"
location = var.location
force_destroy = var.force_destroy
uniform_bucket_level_access = true
public_access_prevention = "enforced"
versioning {
enabled = true
}
}
Backendはterraform init時に必要なので、同じState内で同時作成できません。まず親DirectoryでBucketを作成し、その名前をdemo/backend.hclへ設定します。
bucket = "YOUR_TFSTATE_BUCKET"
prefix = "advanced/gcs-remote-backend/demo"
Remote Backendを初期化する #
cd Advanced-Examples/04-gcs-remote-backend
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
cd demo
cp backend.hcl.example backend.hcl
terraform init -backend-config=backend.hcl
terraform plan
terraform apply
親(Bootstrap)は2リソース、demo/は1リソースが作成されます。
# 親
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
state_bucket_name = "tf-adv-tfstate-YOUR_PROJECT_ID"
state_bucket_url = "gs://tf-adv-tfstate-YOUR_PROJECT_ID"
# demo/(GCS Backend経由)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
demo_bucket_name = "tf-adv-remote-demo-YOUR_PROJECT_ID"
GCP側で確認する #
Versioningが有効か確認します。
gcloud storage buckets describe "gs://$(terraform output -raw state_bucket_name)"
versioning_enabled: true
State Objectと世代を確認します。
gcloud storage ls -r "gs://$(terraform output -raw state_bucket_name)/demo/"
terraform state list
gs://tf-adv-tfstate-YOUR_PROJECT_ID/demo/:
gs://tf-adv-tfstate-YOUR_PROJECT_ID/demo/default.tfstate
demo/側でterraform state pullを実行すると、GCS上のStateがそのまま読めます。
Remote Backendにより複数端末から同じStateを参照できます。ただしStateには機密値が含まれる可能性があるため注意が必要です。Bucket IAMをTerraform実行者に限定します。Public Access Prevention、Versioning、監査Logも検討します。
削除順序 #
cd demo
terraform destroy
cd ..
terraform destroy
# demo/
Destroy complete! Resources: 1 destroyed.
# 親
Destroy complete! Resources: 2 destroyed.
必ずRemote Stateを利用するdemo/を先に削除します。Backend Bucketを先に消すと、Terraformが管理対象を追跡できません。force_destroyを使う場合も、State履歴まで削除する影響を理解して設定します。
まとめ #
- Backend用InfrastructureはBootstrapとして分離する
backend.hclで環境固有のBucket名を渡せる- VersioningによりStateの過去世代を保持できる
- 削除はConsumer側、Backend側の順で行う
参考資料 #
次回 #
次回はGitHub Actionsから認証します。Workload Identity FederationでGoogle Cloudへつなぎます。