Terraform Modules & IaC Patterns cho GCP
Tại sao Module Design quan trọng
Module là đơn vị tái sử dụng cơ bản trong Terraform. Nhưng module tệ còn nguy hiểm hơn không có module — nó ẩn complexity, làm khó debug, và tạo coupling ngầm giữa các team.
Module tốt phải làm một việc: nâng mức abstraction. Thay vì wrap từng resource riêng lẻ (thin wrapper anti-pattern), module nên đại diện cho một concept trong kiến trúc: "GKE cluster với networking và IAM chuẩn", "project với billing và APIs", "VPC với subnets và firewall rules theo pattern công ty".
Nếu module của bạn chỉ gói một google_compute_network resource với vài tham số pass-through, nó không thêm giá trị gì. Nhưng nếu nó tạo VPC, subnets, firewall rules, Cloud Router, Cloud NAT theo một pattern nhất quán — đó mới là module đáng có.
Internal Model — Terraform Module System
Module Resolution
Khi Terraform gặp module "name" { source = "..." }, nó resolve source theo thứ tự:
- Local path:
source = "./modules/vpc"— tìm trong filesystem relative - Terraform Registry:
source = "terraform-google-modules/network/google"— download từregistry.terraform.io - Git repository:
source = "git::https://github.com/org/repo.git//modules/vpc?ref=v1.2.0" - GCS bucket:
source = "gcs::https://www.googleapis.com/storage/v1/BUCKET/PREFIX"
Sau khi terraform init, modules được download và cache vào .terraform/modules/. Module code không bao giờ bị thay đổi tự động sau khi đã cache — phải chạy lại terraform init -upgrade để update.
Dependency Graph và Module Scope
Module tạo ra isolated scope — resources trong module không visible với root module trừ khi được expose qua outputs. Điều này có hệ quả quan trọng:
- Tên resource trong module không conflict với root:
module.vpc.google_compute_network.mainvsgoogle_compute_network.main - Variable trong module không tự động inherit từ root — phải explicit pass
- Output từ module phải được declare, không thể access internal state trực tiếp
# Root module
module "vpc" {
source = "terraform-google-modules/network/google"
version = "~> 9.0"
project_id = var.project_id
network_name = "main-vpc"
routing_mode = "GLOBAL"
subnets = [
{
subnet_name = "subnet-prod-us-central1"
subnet_ip = "10.0.0.0/20"
subnet_region = "us-central1"
}
]
}
# Access output từ module
output "vpc_id" {
value = module.vpc.network_id
}terraform-google-modules Ecosystem
Google và cộng đồng duy trì một bộ modules production-ready tại github.com/terraform-google-modules. Đây là reference implementation cho GCP patterns, được Google test và duy trì.
Các modules quan trọng nhất:
terraform-google-network
Module VPC có feature đầy đủ:
- VPC creation với routing mode configuration
- Subnets với secondary ranges cho GKE
- Firewall rules
- VPC peering
- Cloud Router và NAT
module "vpc" {
source = "terraform-google-modules/network/google"
version = "~> 9.0"
project_id = var.project_id
network_name = var.network_name
subnets = [
{
subnet_name = "gke-subnet"
subnet_ip = "10.0.0.0/20"
subnet_region = "us-central1"
subnet_private_access = true
}
]
secondary_ranges = {
"gke-subnet" = [
{ range_name = "pods", ip_cidr_range = "10.4.0.0/14" },
{ range_name = "services", ip_cidr_range = "10.8.0.0/20" },
]
}
}terraform-google-kubernetes-engine
GKE cluster với tất cả production configurations:
- Private cluster setup
- Workload Identity
- Node pools với auto-scaling
- Binary Authorization
- Logging và Monitoring configuration
module "gke" {
source = "terraform-google-modules/kubernetes-engine/google//modules/private-cluster"
version = "~> 33.0"
project_id = var.project_id
name = "prod-cluster"
region = "us-central1"
network = module.vpc.network_name
subnetwork = "gke-subnet"
ip_range_pods = "pods"
ip_range_services = "services"
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = "172.16.0.0/28"
workload_identity_config = var.project_id
node_pools = [
{
name = "default-pool"
machine_type = "n2-standard-4"
min_count = 1
max_count = 10
disk_size_gb = 100
disk_type = "pd-balanced"
spot = false
}
]
}terraform-google-project-factory
Module quan trọng nhất cho organization-scale IaC — tạo GCP projects với cấu hình nhất quán:
module "project" {
source = "terraform-google-modules/project-factory/google"
version = "~> 17.0"
name = "my-service-prod"
project_id = "my-service-prod-a1b2"
org_id = var.org_id
folder_id = var.prod_folder_id
billing_account = var.billing_account
activate_apis = [
"container.googleapis.com",
"monitoring.googleapis.com",
"logging.googleapis.com",
]
labels = {
environment = "prod"
team = "platform"
cost_center = "cc-1234"
}
}Project Factory Pattern
Project factory là pattern tạo GCP projects theo cách factory method — mọi project được tạo từ cùng một "khuôn" với các parameters customize.
Tại sao cần Project Factory
Trong GCP, mỗi microservice/workload nên có project riêng. Điều này đảm bảo:
- IAM isolation rõ ràng
- Billing attribution chính xác
- Blast radius được giới hạn
Nhưng tạo project tay có vấn đề: inconsistency. Project A bật API logging nhưng API kia quên. Project B không có labels billing. Project C dùng default compute SA với quá nhiều quyền.
Project factory giải quyết bằng cách enforce một standard template:
modules/project-factory/
├── main.tf ← project resource + APIs + IAM
├── variables.tf ← project_name, team, env, apis
├── outputs.tf ← project_id, project_number
└── iam.tf ← standard IAM bindings
projects/
├── service-a-prod/
│ ├── main.tf ← gọi module project-factory
│ └── terraform.tfvars
└── service-b-prod/
└── ...Standard Project Configuration
# modules/project-factory/main.tf
resource "google_project" "main" {
name = var.project_name
project_id = var.project_id
folder_id = var.folder_id
billing_account = var.billing_account
labels = merge(var.labels, {
created_by = "terraform"
team = var.team
environment = var.environment
})
auto_create_network = false # Không tự tạo default VPC
}
# APIs mặc định cho tất cả projects
locals {
default_apis = [
"cloudresourcemanager.googleapis.com",
"iam.googleapis.com",
"monitoring.googleapis.com",
"logging.googleapis.com",
"cloudtrace.googleapis.com",
"clouderrorreporting.googleapis.com",
]
}
resource "google_project_service" "apis" {
for_each = toset(concat(local.default_apis, var.additional_apis))
project = google_project.main.project_id
service = each.value
disable_on_destroy = false # Không disable khi xóa resource (tránh outage)
}
# Disable default compute SA
resource "google_project_default_service_accounts" "default" {
project = google_project.main.project_id
action = "DISABLE"
depends_on = [google_project_service.apis]
}Landing Zone Structure
Landing zone là nền tảng tổ chức GCP — hierarchy, networking, IAM, security policies được setup trước khi workloads deploy. Nó được build bằng Terraform với một cấu trúc cụ thể:
infra/
├── bootstrap/ ← Chạy một lần để setup state bucket, seed SA
│ ├── main.tf
│ ├── gcs.tf ← state bucket
│ └── iam.tf ← terraform SA với quyền tạo projects
│
├── org/ ← Organization-level resources
│ ├── folders.tf ← folder hierarchy
│ ├── org-policies.tf ← org policies
│ └── billing.tf ← billing accounts, budgets
│
├── networking/ ← Shared VPC, DNS, NAT (per-environment)
│ ├── prod/
│ │ ├── vpc.tf
│ │ ├── dns.tf
│ │ └── nat.tf
│ └── staging/
│ └── ...
│
├── security/ ← IAM, Secret Manager, KMS
│ ├── kms.tf
│ ├── secret-manager.tf
│ └── iam-audit.tf
│
└── projects/ ← Application projects (project factory)
├── service-a-prod/
├── service-b-prod/
└── ...Mỗi layer có state riêng. Layer trên expose outputs cho layer dưới qua remote state references.
Environment Management — Directory vs Workspace
Directory-Based (Recommended)
environments/
├── dev/
│ ├── networking/
│ │ ├── main.tf ← gọi module vpc
│ │ ├── backend.tf ← bucket: tf-state, prefix: dev/networking
│ │ └── terraform.tfvars
│ └── gke/
│ ├── main.tf
│ ├── backend.tf ← prefix: dev/gke
│ └── terraform.tfvars
├── staging/
│ └── ... (cấu trúc tương tự, tfvars khác)
└── prod/
└── ...Mỗi environment có:
backend.tfriêng với GCS prefix khác nhauterraform.tfvarsvới giá trị environment-specific- Có thể dùng khác nhau Terraform provider versions
Apply command:
cd environments/prod/networking
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars"Module Versioning — Tránh Implicit Dependency
# Sai: không pin version
module "vpc" {
source = "terraform-google-modules/network/google"
# Version không được pin → minor changes sẽ auto apply
}
# Đúng: pin to minor version range
module "vpc" {
source = "terraform-google-modules/network/google"
version = "~> 9.1" # ~> cho phép patch updates, block major
}
# Strict: pin exact version (cho production)
module "vpc" {
source = "terraform-google-modules/network/google"
version = "9.1.0"
}Không bao giờ dùng module source mà không có version pin trong production. Module updates có thể có breaking changes.
Module Composition Patterns
Layered Composition
Đừng nesting modules quá sâu. Flatten composition:
# root/main.tf — Compose các modules ở cùng level
module "vpc" {
source = "..."
# ...
}
module "gke" {
source = "..."
network = module.vpc.network_name
subnetwork = module.vpc.subnets["gke-subnet"].name
# ...
}
module "nat" {
source = "..."
network = module.vpc.network_name
router = module.vpc.cloud_router_name
}Thay vì:
# Anti-pattern: deep nesting
module "infrastructure" {
source = "..."
# module infrastructure gọi module vpc gọi module subnet gọi module...
# khó debug, khó test riêng từng component
}Data Sources cho Cross-State Dependencies
Thay vì hardcode IDs, dùng data sources để lookup:
# Lookup project ID từ name (không cần hardcode project number)
data "google_project" "main" {
project_id = var.project_id
}
# Lookup VPC từ tên (không cần remote state nếu trong cùng project)
data "google_compute_network" "main" {
name = "main-vpc"
project = data.google_project.main.project_id
}Data sources đọc real-time từ GCP API. Khác với remote state (đọc từ state file), data sources luôn reflect current reality.
Anti-Patterns Phổ Biến
Thin Wrapper Anti-Pattern
# Sai: chỉ wrap resource với 2 biến
module "compute_network" {
source = "./modules/compute-network"
name = var.name
project_id = var.project_id
}
# Module này chỉ gọi google_compute_network với 2 params
# Không thêm value gì, chỉ thêm indirection layerModule này không nâng abstraction. Dùng resource trực tiếp hoặc build module đủ để justify abstraction.
Hardcode Environment trong Module
# Sai: module có logic riêng cho environment
resource "google_container_cluster" "main" {
min_master_version = var.environment == "prod" ? "1.29" : "1.28"
}
# Module nên stateless, caller quyết địnhModule phải generic. Logic environment-specific nằm ở caller (root module), không trong module.
Output Leakage
# Sai: expose internal details không cần thiết
output "cluster_internal_ip" {
value = google_container_cluster.main.endpoint # implementation detail
}
# Đúng: expose interface, không implementation
output "cluster_name" {
value = google_container_cluster.main.name
}
output "cluster_ca_certificate" {
value = google_container_cluster.main.master_auth[0].cluster_ca_certificate
sensitive = true
}Không Có prevent_destroy cho Critical Resources
# Thêm lifecycle protection cho resources không nên bao giờ bị destroy tự động
resource "google_sql_database_instance" "main" {
name = "prod-db"
database_version = "POSTGRES_15"
lifecycle {
prevent_destroy = true # Terraform error nếu ai cố xóa
}
}