Skip to content

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ự:

  1. Local path: source = "./modules/vpc" — tìm trong filesystem relative
  2. Terraform Registry: source = "terraform-google-modules/network/google" — download từ registry.terraform.io
  3. Git repository: source = "git::https://github.com/org/repo.git//modules/vpc?ref=v1.2.0"
  4. 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.main vs google_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
hcl
# 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
hcl
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
hcl
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:

hcl
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 ​

hcl
# 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 ​

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.tf riêng với GCS prefix khác nhau
  • terraform.tfvars với giá trị environment-specific
  • Có thể dùng khác nhau Terraform provider versions

Apply command:

bash
cd environments/prod/networking
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars"

Module Versioning — Tránh Implicit Dependency ​

hcl
# 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:

hcl
# 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ì:

hcl
# 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:

hcl
# 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 ​

hcl
# 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 layer

Module này không nâng abstraction. Dùng resource trực tiếp hoặc build module đủ để justify abstraction.

Hardcode Environment trong Module ​

hcl
# 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 định

Module phải generic. Logic environment-specific nằm ở caller (root module), không trong module.

Output Leakage ​

hcl
# 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 ​

hcl
# 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
  }
}

References ​