Terraform State với GCS Backend
Tại sao State Management quan trọng ở scale thật
Terraform state là mapping trực tiếp giữa Terraform configuration và real-world resources. Mọi resource Terraform quản lý đều được tracked trong state file với resource ID, attributes, và dependencies. Khi terraform apply chạy, nó so sánh desired state (code) với current state (file) và với real-world resources để tính toán diff.
Điều này tạo ra ba rủi ro chính trong production:
Concurrent modification: Nếu hai pipeline đồng thời chạy terraform apply cùng một state, kết quả là race condition — state file bị ghi đè lên nhau, và một trong hai apply sẽ không biết về thay đổi của cái kia. Resource có thể bị destroy và tạo lại không cần thiết.
State drift: Resource được sửa tay ngoài Terraform (qua console, gcloud) mà state không biết. Lần apply tiếp theo sẽ cố revert về state mong muốn, có thể overwrite thay đổi hợp lệ hoặc destroy resource quan trọng.
Blast radius: Một state file chứa toàn bộ infrastructure có nghĩa là một lỗi Terraform có thể ảnh hưởng mọi thứ cùng lúc. Và plan/apply chậm vì phải refresh tất cả resources.
GCS backend giải quyết vấn đề concurrent modification qua state locking, nhưng vẫn cần hiểu cơ chế để avoid pitfalls.
Internal Model — GCS Backend hoạt động như thế nào
State file format và naming
State được lưu là một GCS object (JSON file) theo naming pattern:
{prefix}/{workspace_name}.tfstateVới default workspace (không dùng workspace feature), name là default:
gs://my-tf-state/prod/network/default.tfstateVới custom workspace staging:
gs://my-tf-state/prod/network/staging.tfstateState file là JSON thuần. Một ví dụ nhỏ:
{
"version": 4,
"terraform_version": "1.7.0",
"serial": 42,
"lineage": "a1b2c3d4-...",
"outputs": { ... },
"resources": [
{
"module": "module.vpc",
"mode": "managed",
"type": "google_compute_network",
"name": "main",
"provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "projects/my-project/global/networks/main",
"name": "main",
"project": "my-project",
...
}
}
]
}
]
}Field serial tăng mỗi lần state được cập nhật. Field lineage là UUID cố định, dùng để detect state file replacement (nếu ai đó swap state file, Terraform sẽ phát hiện lineage mismatch).
State Locking cơ chế thực sự
GCS backend không dùng distributed lock service riêng. Thay vào đó, nó dùng conditional write của GCS — cụ thể là x-goog-if-generation-match: 0 header.
Khi lock được acquire:
- Terraform tạo một lock object riêng tại
{prefix}/{workspace_name}.tflock(hoặc tương tự) - GCS request dùng precondition
If-Generation-Match: 0— tức là "chỉ tạo object này nếu nó chưa tồn tại" - Nếu lock object đã tồn tại (vì Terraform process khác đang giữ lock), GCS trả về
412 Precondition Failed - Terraform process thứ hai nhận lỗi và báo:
Error locking state: Error acquiring the state lock - Lock object chứa thông tin về process đang giữ lock: lock ID, hostname, user, timestamp
Khi lock được release:
- Terraform xóa lock object
- Process tiếp theo có thể acquire lock
Lưu ý quan trọng: Nếu Terraform process bị kill đột ngột (SIGKILL, crash, network disconnect), lock object sẽ không được xóa. Đây là orphaned lock. Cách xử lý:
# Xem thông tin lock hiện tại
terraform force-unlock LOCK_ID
# Hoặc xóa lock object trực tiếp (nguy hiểm, verify trước)
gsutil rm gs://my-tf-state/prefix/default.tflockforce-unlock yêu cầu lock ID, nó xóa lock object chỉ khi ID khớp. Đây là safety mechanism — tránh unlock nhầm lock của process đang chạy hợp lệ.
Object Versioning cho State Recovery
GCS Object Versioning giữ lịch sử tất cả versions của một object. Khi enabled, mỗi terraform apply tạo ra một generation mới của state file — generation cũ vẫn tồn tại và có thể restore.
# Enable versioning trên bucket
gsutil versioning set on gs://my-tf-state
# Xem tất cả versions của state file
gsutil ls -a gs://my-tf-state/prod/network/default.tfstate
# Restore về version cụ thể (dùng generation number)
gsutil cp gs://my-tf-state/prod/network/default.tfstate#1234567890 \
gs://my-tf-state/prod/network/default.tfstateĐây là cơ chế recovery quan trọng nhất khi state bị corrupt hoặc apply sai. Luôn enable Object Versioning trên state bucket.
Tuy nhiên versioning tạo storage cost. Nên đặt Object Lifecycle Management để tự động xóa non-current versions sau N ngày:
resource "google_storage_bucket" "terraform_state" {
name = "my-terraform-state"
location = "US"
versioning {
enabled = true
}
lifecycle_rule {
condition {
num_newer_versions = 10 # giữ 10 versions gần nhất
with_state = "ARCHIVED"
}
action {
type = "Delete"
}
}
public_access_prevention = "enforced"
uniform_bucket_level_access = true
force_destroy = false
}Encryption Options — Ba Tầng Bảo Vệ
GCS backend hỗ trợ ba cấp độ encryption, với trade-offs khác nhau về key management và migration complexity.
Google-Managed Encryption (mặc định)
Mặc định, mọi GCS object được mã hóa bằng AES-256 với keys do Google quản lý. Key được bảo vệ bởi KEK (Key Encryption Key) theo mô hình envelope encryption của Google. Không cần cấu hình thêm.
Đây là lựa chọn đủ mạnh cho hầu hết trường hợp. Google quản lý key rotation tự động.
Customer-Managed Encryption Keys (CMEK với Cloud KMS)
CMEK cho phép bạn kiểm soát key trong Cloud KMS nhưng vẫn để GCS tự xử lý decrypt:
# terraform/backend.tf
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod/network"
kms_encryption_key = "projects/MY_PROJECT/locations/us/keyRings/terraform/cryptoKeys/state"
impersonate_service_account = "terraform@my-project.iam.gserviceaccount.com"
}
}CMEK key được dùng để encrypt DEK (Data Encryption Key) — đây là envelope encryption. Khi cần đọc state, GCS tự động gọi Cloud KMS để decrypt DEK, rồi dùng DEK để decrypt object content. Người dùng không cần gửi key trong mỗi request.
Lợi thế của CMEK:
- Revoke quyền truy cập ngay lập tức bằng cách disable/destroy key → state file trở nên unreadable
- Key rotation tự động qua Cloud KMS rotation schedule
- State migration transparent — Terraform không cần thay đổi gì khi key được rotate
- Audit trail qua Cloud KMS audit logs (ai đọc/ghi state khi nào)
Customer-Supplied Encryption Keys (CSEK)
CSEK là bạn cung cấp 32-byte base64-encoded key trực tiếp:
# Đặt key vào environment variable
export GOOGLE_ENCRYPTION_KEY="$(head -c 32 /dev/urandom | base64)"Terraform đọc key từ env var và gửi kèm mọi GCS request. Google không bao giờ lưu key này — nó dùng key để decrypt trước khi serve data và không có bản copy.
Hệ quả nghiêm trọng: Nếu mất key, mất vĩnh viễn quyền truy cập state file. Không có recovery. Hơn nữa, migration state sang encryption scheme khác yêu cầu manual steps:
# Download với CSEK cũ
gsutil -o GSUtil:encryption_key=OLD_KEY cp gs://bucket/state.tfstate ./state.tfstate
# Re-upload với CMEK mới
gsutil -o GSUtil:kms_key=projects/.../key cp ./state.tfstate gs://bucket/state.tfstateCSEK chỉ phù hợp khi tổ chức có yêu cầu compliance nghiêm ngặt về key không bao giờ rời infrastructure của họ (và kể cả không nằm trong Google KMS). Trong hầu hết trường hợp, CMEK là lựa chọn tốt hơn.
IAM Requirements cho Terraform
Service account chạy Terraform cần quyền tối thiểu để tương tác với GCS state bucket:
# IAM binding cho state bucket
resource "google_storage_bucket_iam_member" "terraform_state" {
bucket = google_storage_bucket.terraform_state.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:terraform@my-project.iam.gserviceaccount.com"
}roles/storage.objectAdmin bao gồm: storage.objects.get, storage.objects.create, storage.objects.delete, storage.objects.list — đủ để read, write, lock state.
Nếu muốn least privilege hơn (không cho xóa versions cũ):
# Custom role cho Terraform state operations
resource "google_project_iam_custom_role" "terraform_state" {
role_id = "terraformStateManager"
title = "Terraform State Manager"
permissions = [
"storage.objects.get",
"storage.objects.create",
"storage.objects.update",
"storage.objects.list",
"storage.buckets.get",
]
}Lưu ý: Terraform cũng cần storage.buckets.get để verify bucket tồn tại khi terraform init.
State Organization Strategy
Monolithic State — Anti-Pattern
Đặt toàn bộ infrastructure vào một state file là sai lầm phổ biến nhất:
gs://my-tf-state/
└── default.tfstate ← chứa mọi thứ: VPC, GKE, IAM, databases...Hệ quả:
terraform planchậm vì phải refresh tất cả resources (có thể 10-20 phút)- Lock contention: mọi thay đổi phải queue sau nhau
- Blast radius: một apply lỗi có thể affect toàn bộ infrastructure
- Không thể delegate: team networking và team application không thể làm việc độc lập
Directory-Based Separation — Recommended Pattern
Tách state theo layer và environment:
gs://my-tf-state/
├── prod/
│ ├── bootstrap/default.tfstate ← IAM, org policies, billing
│ ├── networking/default.tfstate ← VPC, subnets, firewall, DNS
│ ├── gke/default.tfstate ← GKE clusters
│ └── applications/
│ ├── service-a/default.tfstate
│ └── service-b/default.tfstate
├── staging/
│ └── ...
└── dev/
└── ...Mỗi state có scope rõ ràng. Team có thể làm việc độc lập trên layer của mình. Lock contention gần như không xảy ra vì các team thao tác trên states riêng biệt.
Remote State References Giữa Layers
Khi applications cần network outputs từ networking:
# environments/prod/applications/service-a/main.tf
data "terraform_remote_state" "network" {
backend = "gcs"
config = {
bucket = "my-tf-state"
prefix = "prod/networking"
}
}
resource "google_container_cluster" "main" {
network = data.terraform_remote_state.network.outputs.vpc_id
subnetwork = data.terraform_remote_state.network.outputs.subnet_id
}Lưu ý: Consumer state cần read quyền trên producer state bucket. Đây là dependency ngầm — nếu networking state output thay đổi tên, application state sẽ fail plan.
Workspaces — Khi Nào Dùng
Terraform workspaces tạo state file riêng nhưng dùng cùng code và backend:
gs://my-tf-state/prefix/
├── default.tfstate
├── feature-xyz.tfstate
└── staging.tfstateWorkspaces phù hợp cho:
- Feature environment ngắn hạn (tạo và xóa nhanh)
- Test infrastructure variations cùng codebase
Workspaces không phù hợp cho environment management (dev/staging/prod) vì:
- Cùng code cho tất cả environments — không thể khác nhau configuration
- Không thể có Terraform version khác nhau per-environment
- Người dùng dễ nhầm workspace (apply vào prod khi đang ở prod workspace)
Dùng directory separation cho environment management, workspace chỉ cho temp environments.
Migration State — Bootstrap Workflow
Khi mới setup GCS backend, phải có bucket trước khi dùng nó. Đây là chicken-and-egg problem:
Bước 1: Tạo bucket với local backend (hoặc tay)
# bootstrap/main.tf
terraform {
backend "local" {} # local trước
}
resource "google_storage_bucket" "terraform_state" {
name = "my-terraform-state-${var.project_id}"
location = "US"
versioning { enabled = true }
public_access_prevention = "enforced"
uniform_bucket_level_access = true
force_destroy = false
}Bước 2: Apply để tạo bucket
terraform init
terraform applyBước 3: Cập nhật backend config và migrate
# bootstrap/main.tf - thêm GCS backend
terraform {
backend "gcs" {
bucket = "my-terraform-state-my-project"
prefix = "bootstrap"
}
}terraform init -migrate-state
# Terraform sẽ hỏi confirm migrate local state → GCS
# Type 'yes'State giờ đã ở GCS. Local terraform.tfstate file có thể xóa.
Failure Modes & Recovery
State File Corruption
Nếu state file bị corrupt (JSON parse error, incomplete write do network cut):
# List tất cả generations
gsutil ls -a gs://my-tf-state/prod/network/default.tfstate
# Restore generation trước đó
PREV_GENERATION=1234567890
gsutil cp "gs://my-tf-state/prod/network/default.tfstate#${PREV_GENERATION}" \
"gs://my-tf-state/prod/network/default.tfstate"Sau đó verify bằng terraform state list để đảm bảo resources vẫn được track đúng.
State Drift Detection
# Refresh-only: detect drift mà không thay đổi state
terraform plan -refresh-only
# Output sẽ liệt kê các resources nào bị drift
# Không thực hiện apply, chỉ cập nhật state để reflect current reality
terraform apply -refresh-only-refresh-only là cách an toàn để sync state với actual infrastructure sau khi ai đó sửa tay qua console.
State Import
Khi resource tồn tại trong GCP nhưng chưa trong state (VD: tạo tay trước khi có Terraform):
# Import resource vào state
terraform import google_compute_network.main projects/my-project/global/networks/main
# Verify
terraform state show google_compute_network.mainSau import, terraform plan sẽ hiển thị diff giữa current state và desired config. Phải cập nhật code để match reality trước khi apply.