TL;DR

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.

hclversions.tf
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.

hclapis.tf
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.

hclnetwork.tf
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.

hclgke.tf
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.

hclnodepool.tf
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.

hclworkload-identity.tf
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.

hclfirewall.tf
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"]
}