Terraform GCP / GKE Templates
GKE Terraform usually provisions APIs, VPC/subnets, secondary IP ranges, GKE cluster, node pools, firewall rules, service accounts, IAM bindings, Cloud NAT, and Workload Identity mappings.
Provider And GCS Backend
This block pins the Google provider and stores Terraform state in a GCS bucket prefix. Use one prefix per environment or root module so GKE, network, and shared services can be managed independently.
terraform {
required_version = ">= 1.6.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
backend "gcs" {
bucket = "client-prod-tfstate"
prefix = "gke/prod"
}
}
provider "google" {
project = var.project_id
region = var.region
}Enable APIs
GCP resources fail to create if the required APIs are disabled. Use this block near the start of a project bootstrap root so Terraform enables the services GKE and networking depend on.
resource "google_project_service" "services" {
for_each = toset([
"container.googleapis.com",
"compute.googleapis.com",
"iam.googleapis.com",
"cloudresourcemanager.googleapis.com"
])
project = var.project_id
service = each.value
}VPC, Subnet, Secondary Ranges
VPC-native GKE needs secondary ranges for Pods and Services. Use this network shape when you want predictable IP planning and private nodes instead of auto-created subnetworks.
resource "google_compute_network" "platform" {
name = "vpc-${var.name}-${var.environment}"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "gke" {
name = "snet-gke-${var.environment}"
ip_cidr_range = "10.60.0.0/20"
region = var.region
network = google_compute_network.platform.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.61.0.0/16"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.62.0.0/20"
}
}Private GKE Cluster
This cluster uses VPC-native networking, private nodes, and Workload Identity. Use it as a production-oriented starting point, then tighten endpoint access based on the client's network model.
resource "google_container_cluster" "this" {
name = "gke-${var.name}-${var.environment}"
location = var.region
network = google_compute_network.platform.id
subnetwork = google_compute_subnetwork.gke.id
remove_default_node_pool = true
initial_node_count = 1
networking_mode = "VPC_NATIVE"
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = "172.16.0.0/28"
}
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
}Node Pool
This node pool runs application workloads with a dedicated node service account. Use separate pools for different machine sizes, labels, taints, or security boundaries.
resource "google_container_node_pool" "apps" {
name = "apps"
location = var.region
cluster = google_container_cluster.this.name
node_count = 3
node_config {
machine_type = "e2-standard-4"
service_account = google_service_account.gke_nodes.email
oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
labels = {
workload = "apps"
}
}
}IAM And Workload Identity
This maps a Kubernetes ServiceAccount to a Google service account. Use it when a pod needs GCP API permissions without mounting long-lived JSON keys.
resource "google_service_account" "external_dns" {
account_id = "external-dns-${var.environment}"
display_name = "ExternalDNS workload identity"
}
resource "google_project_iam_member" "external_dns_dns_admin" {
project = var.project_id
role = "roles/dns.admin"
member = "serviceAccount:${google_service_account.external_dns.email}"
}
resource "google_service_account_iam_member" "external_dns_wi" {
service_account_id = google_service_account.external_dns.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[external-dns/external-dns]"
}Firewall Rule
This firewall rule allows internal cluster and workload traffic across planned ranges. Use specific rules like this instead of broad defaults so reviewers can understand the allowed network paths.
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal-${var.environment}"
network = google_compute_network.platform.name
allow {
protocol = "tcp"
ports = ["0-65535"]
}
source_ranges = ["10.60.0.0/16", "10.61.0.0/16", "10.62.0.0/20"]
}