GCP for DevOps — Terraform Modules

2 minute read

Terraform Modules

Modules are reusable Terraform packages. They encapsulate a set of resources behind a clean interface of variables and outputs, promoting DRY (Don’t Repeat Yourself) infrastructure.

Module Structure

modules/
├── gke-cluster/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── README.md
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── cloud-run-service/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

Create a VPC Module

# modules/vpc/variables.tf
variable "project_id" {
  type = string
}

variable "environment" {
  type = string
}

variable "region" {
  type    = string
  default = "us-central1"
}

variable "subnet_cidr" {
  type    = string
  default = "10.0.0.0/20"
}
# modules/vpc/main.tf
resource "google_compute_network" "vpc" {
  name                    = "${var.environment}-vpc"
  project                 = var.project_id
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "subnet" {
  name          = "${var.environment}-subnet"
  project       = var.project_id
  ip_cidr_range = var.subnet_cidr
  region        = var.region
  network       = google_compute_network.vpc.id

  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = "10.4.0.0/14"
  }

  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = "10.8.0.0/20"
  }

  private_ip_google_access = true
}

resource "google_compute_router" "router" {
  name    = "${var.environment}-router"
  project = var.project_id
  region  = var.region
  network = google_compute_network.vpc.id
}

resource "google_compute_router_nat" "nat" {
  name                               = "${var.environment}-nat"
  project                            = var.project_id
  router                             = google_compute_router.router.name
  region                             = var.region
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
# modules/vpc/outputs.tf
output "network_name" {
  value = google_compute_network.vpc.name
}

output "network_id" {
  value = google_compute_network.vpc.id
}

output "subnet_name" {
  value = google_compute_subnetwork.subnet.name
}

output "subnet_id" {
  value = google_compute_subnetwork.subnet.id
}

Use the Module

# environments/dev/main.tf
module "vpc" {
  source = "../../modules/vpc"

  project_id  = var.project_id
  environment = "dev"
  region      = "us-central1"
  subnet_cidr = "10.0.0.0/20"
}

module "gke" {
  source = "../../modules/gke-cluster"

  project_id   = var.project_id
  environment  = "dev"
  region       = "us-central1"
  network_name = module.vpc.network_name
  subnet_name  = module.vpc.subnet_name
}

Terraform Registry Modules

Use pre-built modules from the Terraform Registry.

# Google Cloud VPC module from registry
module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = var.project_id
  network_name = "${var.environment}-vpc"
  routing_mode = "GLOBAL"

  subnets = [
    {
      subnet_name   = "${var.environment}-subnet"
      subnet_ip     = "10.0.0.0/20"
      subnet_region = var.region
    }
  ]

  secondary_ranges = {
    "${var.environment}-subnet" = [
      { range_name = "pods",     ip_cidr_range = "10.4.0.0/14" },
      { range_name = "services", ip_cidr_range = "10.8.0.0/20" },
    ]
  }
}

# GKE module from registry
module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google//modules/autopilot"
  version = "~> 30.0"

  project_id     = var.project_id
  name           = "${var.environment}-cluster"
  region         = var.region
  network        = module.vpc.network_name
  subnetwork     = "${var.environment}-subnet"
  ip_range_pods  = "pods"
  ip_range_services = "services"
}

Module Versioning

# Pin to a specific version (recommended)
module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "9.1.0"
  # ...
}

# Pin to a Git tag
module "custom_module" {
  source = "git::https://github.com/myorg/terraform-modules.git//vpc?ref=v1.2.0"
}

Common Mistakes

Mistake Why It’s Wrong Fix
No module versioning Breaking changes Pin module versions
Monolithic modules Too rigid, hard to compose Keep modules focused and small
Hardcoding values in modules Not reusable Use variables for all configurable values
No outputs Can’t compose modules Export all IDs, names, and URLs
Copy-pasting instead of modules Drift between environments Extract into modules

Quick Reference

Concept Example
Local module source = "../../modules/vpc"
Registry module source = "terraform-google-modules/network/google"
Git module source = "git::https://github.com/org/repo.git//path?ref=v1.0"
Version pin version = "~> 9.0"
Module output module.vpc.network_id

Next → Cloud Foundation Toolkit

Updated: