7 min read

Terraform GKE Networking with Secondary Ranges

Build a Google Cloud VPC and GKE Autopilot cluster with Terraform, reserve primary and secondary subnet ranges, and connect Pods and Services to named ranges.

ByAndré Ribeiro· Founder, Obelinf
Terraform GKE Networking with Secondary Ranges
Terraform GKE Networking with Secondary Ranges · October 3, 2026
On this page

A GKE cluster can use a VPC subnet for node addresses and separate ranges for Pod and Service addresses. If those ranges are left implicit, a cluster can claim space that conflicts with an on premises route, a peered VPC, or another cluster.

This tutorial builds a custom Google Cloud VPC, a subnet with named secondary ranges, and an Autopilot GKE cluster that uses those ranges. The sample uses a single region and IPv4 ranges. Replace them with address space reserved for your environment before applying.

GKE uses a primary subnet range for nodes and secondary ranges for Pods and ServicesVPC subnet, us-central1Primary: nodes10.20.0.0/20Secondary: Pods10.24.0.0/14Secondary: Services10.28.0.0/20Keep each range unique across the VPC and every network connected to it.

Prepare a Google Cloud project

Choose a project with billing enabled and permissions to create VPC networks, subnets, Cloud NAT, and GKE clusters. Enable the required APIs and authenticate the Google Cloud CLI:

gcloud services enable compute.googleapis.com container.googleapis.com \
  --project YOUR_PROJECT_ID
gcloud auth login
gcloud auth application-default login

Create a directory named gke-network and commit .terraform.lock.hcl after initialization. Use a dedicated project or sandbox while learning because this tutorial creates billable cloud resources.

Configure the Google provider

Create main.tf and set the project and region as input variables. The Google provider uses Application Default Credentials from the login step.

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 6.0"
    }
  }
}

variable "project_id" {
  type = string
}

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

provider "google" {
  project = var.project_id
  region  = var.region
}

Pass your project ID at the command line or through an untracked terraform.tfvars file. Do not commit credentials or local variable files containing secrets.

Create a custom VPC and subnet ranges

Disable automatic subnet creation so the configuration controls the network layout. Give each range a clear name because the GKE cluster refers to the names, not only the CIDR values.

resource "google_compute_network" "gke" {
  name                    = "gke-network"
  auto_create_subnetworks = false
  routing_mode            = "GLOBAL"
}

resource "google_compute_subnetwork" "gke" {
  name                     = "gke-us-central1"
  region                   = var.region
  network                  = google_compute_network.gke.id
  ip_cidr_range            = "10.20.0.0/20"
  private_ip_google_access = true

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

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

The primary range is for node addresses. The Pod and Service ranges must not overlap each other, the primary range, or any routes visible to the VPC. Size the Pod range for your maximum node count and per node Pod capacity, not just today’s workload.

Estimate capacity from the cluster settings before choosing those ranges. The /20 node range contains 4,096 addresses, and Google Cloud reserves four addresses in a primary subnet range, leaving 4,092 for node interfaces. On GKE Standard, the default maximum of 110 Pods per node normally assigns a /24 block to each node from the Pod range. A /14 contains 262,144 addresses, enough for 1,024 such /24 node blocks before considering other cluster limits. The required block changes when the maximum Pods per node changes, and Autopilot chooses its own node density based on its workload model.

The /20 Service range contains 4,096 addresses. Check the expected number of Kubernetes Services and confirm whether your cluster version uses a user managed range or a GKE managed Service range. This example explicitly names a user managed range, which keeps the address plan visible. If you use a GKE managed Service range, remove the services secondary block and configure the cluster according to the chosen GKE behavior instead.

Treat range size as an immutable cluster design choice. GKE cannot simply stretch an existing Pod secondary range after it runs out of space. You may be able to add another Pod range with discontiguous multi Pod CIDR, but that requires a deliberate migration and supported cluster configuration. Reserve enough space at creation for expected node growth, Pod density, and future clusters that share the VPC.

Add outbound networking for cluster nodes

Autopilot nodes do not need public IP addresses. Add Cloud Router and NAT so nodes can reach external endpoints when the workload needs internet egress. Cloud NAT is a billable service, so review regional pricing and traffic expectations.

resource "google_compute_router" "gke" {
  name    = "gke-router"
  region  = var.region
  network = google_compute_network.gke.id
}

resource "google_compute_router_nat" "gke" {
  name                               = "gke-nat"
  router                             = google_compute_router.gke.name
  region                             = var.region
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

  subnetwork {
    name                    = google_compute_subnetwork.gke.id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }
}

This NAT rule covers the subnet primary and secondary ranges. If your security policy restricts egress, use Cloud NAT logging and firewall rules that describe the permitted destinations instead of treating NAT as a filter.

Private Google Access and Cloud NAT serve different traffic. private_ip_google_access = true lets eligible resources without external IP addresses reach Google APIs using Google’s network. Cloud NAT provides outbound translation to other internet destinations. A workload that can pull from a Google artifact registry may still need NAT to reach a public package mirror, and a NAT gateway does not automatically grant permission to call Google APIs.

Cloud NAT does not create an ingress path to the cluster. For inbound application traffic, choose a load balancer or another approved exposure mechanism and configure firewall policy for its health checks and backends. Keep the tutorial’s network boundary separate from application exposure so that opening an ingress path is an explicit design decision.

Create the Autopilot cluster

Reference the subnet and its secondary range names in ip_allocation_policy. Terraform creates the network resources first because the cluster references their IDs.

resource "google_container_cluster" "main" {
  name                = "gke-network-lab"
  location            = var.region
  network             = google_compute_network.gke.id
  subnetwork          = google_compute_subnetwork.gke.id
  networking_mode     = "VPC_NATIVE"
  enable_autopilot    = true
  deletion_protection = false

  ip_allocation_policy {
    cluster_secondary_range_name  = "pods"
    services_secondary_range_name = "services"
  }
}

Google may add cluster metadata to the subnet as part of range management. Let the provider manage the resource and avoid out of band edits to GKE generated metadata. Inspect the plan after cluster creation and upgrades.

Terraform creates network ranges before the GKE cluster consumes themVPCrouting boundarySubnetprimary and secondary rangesCloud NAToptional outbound pathGKEnamed range useSubnets are regional resources, while a VPC network is global.

Initialize and review the plan

Run the Terraform checks, then inspect the plan before creating the cluster:

terraform init
terraform fmt -recursive
terraform validate
terraform plan -var="project_id=YOUR_PROJECT_ID"

Confirm the region, primary CIDR, Pod range, Service range, NAT configuration, and cluster settings. The Google provider subnet reference documents named secondary ranges. GKE’s VPC native networking guide describes how node, Pod, and Service addresses are assigned, and its Pod range sizing guide explains how Pod density affects range capacity.

Apply and inspect the cluster

Apply the reviewed configuration, then fetch cluster credentials and inspect the nodes:

terraform apply -var="project_id=YOUR_PROJECT_ID"
gcloud container clusters get-credentials gke-network-lab \
  --region us-central1 --project YOUR_PROJECT_ID
kubectl get nodes
kubectl get pods --all-namespaces

In the Google Cloud console, confirm that the subnet has the expected primary CIDR and both named secondary ranges. The cluster should reference pods for Pod addresses and services for Service addresses. Deploy a small test workload if you need to observe actual Pod IP allocation.

After deploying a workload, compare its Pod addresses with the Pod secondary range and check the Kubernetes Service cluster IP against the Service range. For example, kubectl get pods --all-namespaces -o wide shows Pod addresses and kubectl get services --all-namespaces shows service IPs. The node’s internal address should come from the primary range. If one address appears outside the expected range, confirm that the cluster is VPC native and that the intended subnetwork and range names were selected.

When a range is missing or the cluster create call fails, verify the following before editing Terraform: the secondary range names are unique within the subnet, the ranges are nonoverlapping, the cluster location matches the subnet region, and the project APIs are enabled. Also confirm that the account can create Autopilot clusters and Cloud NAT. GKE create errors often identify a range or permission problem directly, while a Terraform diff may only show that the API rejected the overall cluster request.

For multiple clusters in one VPC, allocate a distinct Pod range for each cluster when the network design requires independent ownership or growth. Do not point two cluster configurations at the same user managed Pod range unless the selected GKE mode explicitly supports that arrangement. Keep a record of the cluster name, range names, CIDRs, region, and expected capacity so future teams do not reuse a block that is still allocated.

Clean up and protect address space

Delete the cluster and supporting resources when the lab is complete:

terraform destroy -var="project_id=YOUR_PROJECT_ID"

The cluster, Cloud NAT, router, subnet, and VPC are billable or consume shared network capacity. Review the destroy plan carefully if you reused a project or VPC. For production, keep the range plan in version control and coordinate it with peered VPCs, VPNs, and on premises routes before reserving blocks.

The Terraform state should own each network resource only once. If a platform team owns the VPC and a workload team owns the cluster, pass the network and subnet identifiers into the cluster configuration rather than declaring duplicate network resources in both states. Keep the secondary range names as documented inputs so a cluster module cannot silently choose a different range during a refactor.

Before adding another cluster, reserve nonoverlapping Pod and node ranges for it and record which subnet names it will consume. A clear allocation plan makes cluster growth safer than relying on automatically selected ranges that may conflict with routes added later.

Frequently Asked Questions

Why does GKE need secondary subnet ranges?
In a VPC native cluster, the subnet primary range supplies node addresses. Secondary ranges can supply Pod addresses and, when configured, Service addresses.
How do I connect Terraform GKE to subnet secondary ranges?
Create the subnet with named `secondary_ip_range` blocks, then set `cluster_secondary_range_name` and `services_secondary_range_name` in the cluster's `ip_allocation_policy`.
Can GKE choose its own Pod and Service ranges?
Yes. GKE can manage range assignment, but user managed secondary ranges make the address plan explicit and easier to coordinate with existing routes. Choose the method that fits your network ownership model.

Stop reaching for a spreadsheet

Obelinf keeps every subnet, device, circuit, and rack in one live source of truth, with audit logs and a topology view. Free for personal use.

Related Articles