Global VPC and Regional Subnets
On GCP a VPC is a global resource and its subnets are regional — the exact inverse of what an AWS engineer expects, where the VPC is regional and subnets are pinned to an availability zone. One google_compute_network for Hatch spans every region on Earth; you carve a google_compute_subnetwork in us-central1 out of it, and a VM in any zone of that region attaches to the same subnet with no AZ-to-subnet mapping to design around.
This single inversion is the source of most AWS-brain mistakes on GCP networking. If you replicate the one-subnet-per-AZ habit, you fragment your address space for no redundancy benefit and build a design that fights the platform. Internalize that the network is global and the subnet is regional, and the rest of GCP networking — Shared VPC, firewalls, private connectivity — falls into place.
The Network Is Global
A google_compute_network has no region. It is one routing domain that reaches every GCP region, so a VM in us-central1 and one in europe-west1 attached to the same VPC talk over Google's backbone using internal IPs, with no peering and no transit gateway between them. The VPC is the boundary; the regions are just where you place subnets inside it.
For Hatch, that means hatch-net-host holds exactly one global VPC. You do not create a VPC per region and stitch them together — there is one network object, and you add a regional subnet wherever you want to place workloads. The configuration below declares the host network in custom mode, which the next section explains.
resource "google_compute_network" "hatch" { project = "hatch-net-host" name = "hatch-vpc" auto_create_subnetworks = false # declare every subnet explicitly routing_mode = "GLOBAL" # dynamic routes propagate across regions }
Subnets Are Regional, Not Zonal
A google_compute_subnetwork belongs to exactly one region and carries a primary CIDR — say 10.10.0.0/20 for the Hatch app subnet in us-central1. Every zone in that region draws addresses from that one subnet. There is no per-AZ subnet to replicate, and no "which subnet do I use for zone -a versus -c" decision, because the zones share the regional subnet.
This is exactly where the AWS habit hurts. In AWS you build a subnet per availability zone and spread instances across them for resilience. On GCP that resilience is automatic within a regional resource — a regional managed instance group already spans the zones of us-central1 on the single subnet — so three hand-made "zonal" subnets just chop your /20 into fragments with no benefit.
resource "google_compute_subnetwork" "app" { project = "hatch-net-host" name = "app-uc1" region = "us-central1" # regional, not zonal network = google_compute_network.hatch.id ip_cidr_range = "10.10.0.0/20" # every zone of us-central1 shares this }
Auto Mode vs Custom Mode
Setting auto_create_subnetworks = true creates an auto-mode VPC: Google spawns a /20 subnet in every region automatically, from a fixed default address plan. That is fine for a throwaway sandbox where you never peer or connect to on-prem. It is wrong for anything real, because those default ranges are prone to collide the moment you peer two VPCs or attach a VPN to a corporate network — and you cannot renumber an auto-mode VPC without recreating it.
For hatch-net-host you set auto_create_subnetworks = false and declare each subnet explicitly. Custom mode is the only sane choice for a network you intend to govern: you own the address plan, you place subnets only where you need them, and nothing collides because nothing was auto-generated behind your back.
Secondary IP Ranges
A subnet can carry named secondary_ip_range blocks beyond its primary CIDR. This is how GKE gets alias-IP ranges: a pods range and a services range so containers receive real, routable VPC addresses instead of being hidden behind NAT. The pods get genuine internal IPs, which is what makes GKE networking on GCP feel native rather than bolted on.
Size these up front, because a secondary range is effectively fixed once a cluster consumes it. A too-small /24 pods range caps the cluster's original node pools at a few hundred pods, and lifting that ceiling means adding a second pod range to the cluster and migrating the workloads onto new node pools, because a node pool's range is immutable. Plan a generous /16-class pods range and a smaller services range before the cluster exists, not after it hits the ceiling.
resource "google_compute_subnetwork" "gke" { project = "hatch-net-host" name = "gke-uc1" region = "us-central1" network = google_compute_network.hatch.id ip_cidr_range = "10.20.0.0/20" # nodes secondary_ip_range { range_name = "pods" ip_cidr_range = "10.96.0.0/14" # sized for pod density, fixed once used } secondary_ip_range { range_name = "services" ip_cidr_range = "10.100.0.0/20" } }
Internal Ranges and Routing
The VPC has implied routes between all of its subnets, so subnets in different regions reach each other internally with no configuration. You size the primary range for the VMs you expect and the secondary ranges for the pod and service density you plan, and the routing between them is handled for you.
The discipline that matters is address planning. Overlapping the primary and a secondary range, or two subnets' ranges, breaks alias IPs or peering silently — the VPC accepts the config and the failure only surfaces when GKE or a peer tries to use the colliding range. Lay out non-overlapping CIDRs deliberately and write them down next to the code, because the cost of getting it wrong is a renumber or a rebuild.
GCP VPC — one global object. Its subnets are regional, every zone shares a subnet, and cross-region internal traffic needs no peering. You design one network and place regional subnets inside it.
AWS VPC — regional. Its subnets are pinned to one AZ, you replicate a subnet per AZ, and cross-region traffic needs VPC peering or Transit Gateway. Carrying the AWS one-subnet-per-AZ habit onto GCP produces redundant subnets and a network design that fights the platform.
- Creating a subnet per zone out of AWS habit — GCP subnets are regional, so three "zonal" subnets in
us-central1just fragment your address space for no redundancy benefit. - Leaving
auto_create_subnetworks = trueon a real VPC and getting Google's default/20in every region, which collides the moment you peer or connect to on-prem and cannot be renumbered without recreating. - Sizing a GKE secondary pod range as a
/24and capping the cluster's node pools at a few hundred pods — a node pool's range is immutable, so lifting the ceiling means adding a second pod range and rebuilding every node pool onto it. - Assuming two regions need peering to talk — the same global VPC already routes between regional subnets internally, and adding peering on top is wasted complexity.
- Overlapping the primary and a secondary range, or two subnets' ranges, so alias IPs or peering silently break — the VPC accepts the config and the failure shows up only when GKE or a peer tries to use the range.
- Set
auto_create_subnetworks = falseand declare every subnet explicitly forhatch-net-host, so address space is intentional and peer-safe. - Plan secondary ranges for GKE up front — a generous
/16-class pods range and a smaller services range — because a node pool takes its pod range at creation and can never be moved to another one. - Use one global VPC across regions and rely on its internal routing instead of peering same-org regions together.
- Document the primary and secondary CIDRs in code comments next to each subnet so the next engineer sees the address plan without spelunking.
- Lay out non-overlapping CIDRs deliberately across the whole VPC before creating subnets, so peering and alias IPs never collide later.
Knowledge Check
Why does a single Hatch VPC reach both us-central1 and europe-west1 with no peering?
- A GCP VPC is a global resource and routes internally between its regional subnets
- Terraform creates a hidden peering connection between the two regions automatically
- The two regions share an availability zone that bridges them
- GCP tunnels the traffic over the public internet between regions
An engineer builds three subnets in us-central1, one per zone, the way they would in AWS. What is the result on GCP?
- Wasted, fragmented address space, since a GCP subnet is regional and already serves every zone in that region already
- Improved resilience, because each zone now has a dedicated subnet as its own failure domain, isolated from a routing fault in the others
- A required configuration; GCP rejects a region with fewer than one subnet per zone in the region at creation
- Faster cross-zone traffic, since each zone routes through its own subnet rather than a single shared one
Why do you size a GKE secondary pod range generously before the cluster exists?
- A node pool's pod range is fixed at creation, so an undersized range caps pods and forces new node pools on a second range
- Larger ranges make pod-to-pod traffic faster on Google's backbone, since a roomier CIDR lets the scheduler pack pods onto closer nodes
- GKE bills per unused address in the range, so a generously sized pod range actually costs less
- The pod range must always be exactly the same size as the primary node range, or GKE refuses to create the cluster at all
When is auto_create_subnetworks = true the wrong choice?
- For any VPC you will peer or connect to on-prem, because the default ranges collide and cannot be renumbered without recreating
- For a throwaway sandbox that never connects to another network, where those default ranges will never need to be renumbered at all
- Only when the VPC has more than one secondary range defined on one or more of its regional subnets
- It is never wrong; auto mode is Google's recommended default for production because it lays out non-overlapping ranges in every region for you
You got correct