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