Google Cloud VPC Peering - Hub and Spoke Setup Step by Step with gcloud and Terraform
In this blog post we are going to look at Google Cloud VPC Network Peering - what it is, when you should use it instead of Shared VPC or a VPN, and then we are gonna build a complete hub-and-spoke lab step by step. Two VPC networks, two VMs with no public IP at all, and by the end of the post the hub VM pings the spoke VM over an internal IP, privately over Google's backbone.
I have been running this exact lab on my YouTube channel, so every command and every Terraform resource you see here is the one from the video. I have also added the step-by-step diagrams which I used for recording - click on any of them to zoom, and use the arrow keys to walk through the steps.
This is the first post of my Google Cloud networking series. Here is what we are going to cover -
Table of Content
- What is Google Cloud VPC Peering?
- VPC Peering vs Shared VPC vs Cloud VPN - when to use which
- What we are going to build - hub and spoke
- Build it step by step (diagrams, gcloud and Terraform for every step)
- How VPC peering works - concept animation
- gcloud CLI walkthrough - the complete command list
- The same hub and spoke in Terraform
- The transitive peering trap
- Conclusion and what to read next
1. What is Google Cloud VPC Peering?
VPC Network Peering gives you private connectivity between two VPC networks, so that the resources in both networks can reach each other using their internal IP addresses. The traffic stays on Google's backbone and never touches the public internet. No VPN tunnels, no gateways, no public IPs.
Think of it like this - you have two separate offices, each with its own private network. Peering is like running a private cable between the two buildings. Both offices keep their own address range, their own admins and their own firewall, but a machine in office A can now talk to a machine in office B as if they were on the same LAN.
A few properties of VPC peering which you must know before you start -
- It is bilateral - Both networks have to declare the peering. One side alone stays in
INACTIVEstate, it becomesACTIVEonly when the second side is created. - No overlapping IP ranges - If
hub-vpcuses10.10.1.0/24, the spoke cannot use the same range. Google will reject the peering. - Routes are exchanged, firewall rules are not - Peering makes the other network routable, but the firewall rules of each network still decide what is allowed. You need both. This is the number one reason why "my peering is ACTIVE but ping does not work".
- It is not transitive - If A is peered with B, and A is peered with C, then B still cannot talk to C. We will prove this in Section 8.
- It works across projects and even across organisations - as long as you have permission on your own side of the peering.
2. VPC Peering vs Shared VPC vs Cloud VPN - when to use which
This is the question I get asked the most in the comments, so let me put it in one table -
| VPC Peering | Shared VPC | Cloud VPN / Interconnect | |
|---|---|---|---|
| What it connects | Two separate VPC networks | Many projects onto one network | A VPC to on-premises (or another cloud) |
| Who owns the network | Each side owns its own | One host project owns it, service projects borrow subnets | Each side owns its own |
| Needs an Organization? | No | Yes | No |
| Transitive? | No | Not applicable (one network) | Depends on your routing |
| Typical use | Two teams, two independent networks, inside Google Cloud | Enterprise landing zone with a central network team | Hybrid connectivity |
So the rule of thumb is -
- Two independently managed networks inside Google Cloud - VPC Peering (this post).
- One governed network that many teams should share - Shared VPC, which needs an Organization.
- Expose just one service, not the whole network - Private Service Connect.
- Connect to your data centre - Cloud VPN or Interconnect.
3. What we are going to build - hub and spoke
Here is the lab. One project cl-demo-sandbox, region europe-north2 (Stockholm) -
- hub-vpc (
10.10.1.0/24) with hub-vm - spoke1-vpc (
10.20.1.0/24) with spoke1-vm - Both VMs are
e2-microwith no external IP. We will SSH into them through IAP (Identity-Aware Proxy), so nothing is exposed to the internet. - Peering in both directions, the two firewall rules which make the traffic actually flow, and a ping from hub to spoke to prove it.
- As a bonus, a spoke2-vpc (
10.30.1.0/24) in a second project to show cross-project peering and the transitive trap.
Prerequisites -
- A Google Cloud project with billing enabled. If you are completely new to Google Cloud, start with my post on how to set up a Virtual Machine on Google Cloud Platform, it covers creating the project and installing the
gcloudCLI. - The
gcloudCLI authenticated with an Owner or Network Admin on the project. - For the Terraform part - Terraform installed and the Google provider configured. My Terraform getting started guide has everything if you are new to it.
4. Build it step by step (diagrams, gcloud and Terraform for every step)
This is the part I am most excited about. Each step below has three things - the diagram of what exists after the step, the gcloud command for the step, and the Terraform for the same step. Click a step in the rail or use the Prev / Next buttons (or the ← → arrow keys). Click the diagram to see it in full screen.
gcloud compute networks create hub-vpc --subnet-mode=custom
gcloud compute networks subnets create hub-subnet \
--network=hub-vpc --region=europe-north2 --range=10.10.1.0/24
gcloud compute networks create spoke1-vpc --subnet-mode=custom
gcloud compute networks subnets create spoke1-subnet \
--network=spoke1-vpc --region=europe-north2 --range=10.20.1.0/24resource "google_compute_network" "hub" {
name = "hub-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "hub" {
name = "hub-subnet"
network = google_compute_network.hub.id
region = "europe-north2"
ip_cidr_range = "10.10.1.0/24"
}
resource "google_compute_network" "spoke1" {
name = "spoke1-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "spoke1" {
name = "spoke1-subnet"
network = google_compute_network.spoke1.id
region = "europe-north2"
ip_cidr_range = "10.20.1.0/24"
}gcloud compute instances create hub-vm \
--zone=europe-north2-a --subnet=hub-subnet --no-address \
--machine-type=e2-micro --image-family=debian-13 --image-project=debian-cloud
gcloud compute instances create spoke1-vm \
--zone=europe-north2-a --subnet=spoke1-subnet --no-address \
--machine-type=e2-micro --image-family=debian-13 --image-project=debian-cloudresource "google_compute_instance" "hub" {
name = "hub-vm"
zone = "europe-north2-a"
machine_type = "e2-micro"
boot_disk {
initialize_params { image = "debian-cloud/debian-13" }
}
network_interface {
subnetwork = google_compute_subnetwork.hub.id
# no access_config → no external IP
}
}
resource "google_compute_instance" "spoke1" {
name = "spoke1-vm"
zone = "europe-north2-a"
machine_type = "e2-micro"
boot_disk {
initialize_params { image = "debian-cloud/debian-13" }
}
network_interface {
subnetwork = google_compute_subnetwork.spoke1.id
}
}# enable the IAP API (one-time, project-wide)
gcloud services enable iap.googleapis.com# the Terraform equivalent of `gcloud services enable`
resource "google_project_service" "iap" {
service = "iap.googleapis.com"
disable_on_destroy = false
}# let IAP's range SSH into each network (no public IP needed)
gcloud compute firewall-rules create hub-fw-allow-iap-ssh \
--network=hub-vpc --direction=INGRESS --action=ALLOW \
--rules=tcp:22 --source-ranges=35.235.240.0/20
gcloud compute firewall-rules create spoke1-fw-allow-iap-ssh \
--network=spoke1-vpc --direction=INGRESS --action=ALLOW \
--rules=tcp:22 --source-ranges=35.235.240.0/20resource "google_compute_firewall" "hub_fw_allow_iap_ssh" {
name = "hub-fw-allow-iap-ssh"
network = google_compute_network.hub.id
direction = "INGRESS"
source_ranges = ["35.235.240.0/20"]
allow {
protocol = "tcp"
ports = ["22"]
}
}
resource "google_compute_firewall" "spoke1_fw_allow_iap_ssh" {
name = "spoke1-fw-allow-iap-ssh"
network = google_compute_network.spoke1.id
direction = "INGRESS"
source_ranges = ["35.235.240.0/20"]
allow {
protocol = "tcp"
ports = ["22"]
}
}# both sides — ACTIVE only after the second
gcloud compute networks peerings create hub-to-spoke1 \
--network=hub-vpc --peer-network=spoke1-vpc
gcloud compute networks peerings create spoke1-to-hub \
--network=spoke1-vpc --peer-network=hub-vpcresource "google_compute_network_peering" "hub_to_spoke1" {
name = "hub-to-spoke1"
network = google_compute_network.hub.self_link
peer_network = google_compute_network.spoke1.self_link
}
resource "google_compute_network_peering" "spoke1_to_hub" {
name = "spoke1-to-hub"
network = google_compute_network.spoke1.self_link
peer_network = google_compute_network.hub.self_link
}# each rule lives on the network that RECEIVES the traffic
gcloud compute firewall-rules create spoke1-fw-allow-from-hub \
--network=spoke1-vpc --direction=INGRESS --action=ALLOW \
--rules=icmp,tcp:22 --source-ranges=10.10.1.0/24
gcloud compute firewall-rules create hub-fw-allow-from-spokes \
--network=hub-vpc --direction=INGRESS --action=ALLOW \
--rules=icmp,tcp:22 --source-ranges=10.20.1.0/24# lives on spoke1 — lets the hub in
resource "google_compute_firewall" "spoke1_fw_allow_from_hub" {
name = "spoke1-fw-allow-from-hub"
network = google_compute_network.spoke1.id
direction = "INGRESS"
source_ranges = ["10.10.1.0/24"]
allow { protocol = "icmp" }
allow {
protocol = "tcp"
ports = ["22"]
}
}
# lives on the hub — lets spoke1 in
resource "google_compute_firewall" "hub_fw_allow_from_spokes" {
name = "hub-fw-allow-from-spokes"
network = google_compute_network.hub.id
direction = "INGRESS"
source_ranges = ["10.20.1.0/24"]
allow { protocol = "icmp" }
allow {
protocol = "tcp"
ports = ["22"]
}
}# spoke2 in a second project (replace your-second-project)
gcloud compute networks create spoke2-vpc --subnet-mode=custom --project=your-second-project
gcloud compute networks subnets create spoke2-subnet --project=your-second-project \
--network=spoke2-vpc --region=europe-north2 --range=10.30.1.0/24
# peer hub <-> spoke2 (one side in each project)
gcloud compute networks peerings create hub-to-spoke2 --project=cl-demo-sandbox \
--network=hub-vpc --peer-network=spoke2-vpc --peer-project=your-second-project
gcloud compute networks peerings create spoke2-to-hub --project=your-second-project \
--network=spoke2-vpc --peer-network=hub-vpc --peer-project=cl-demo-sandbox# second project needs its own aliased provider
provider "google" {
alias = "project2"
project = "your-second-project"
region = "europe-north2"
}
# hub side (default project)
resource "google_compute_network_peering" "hub_to_spoke2" {
name = "hub-to-spoke2"
network = google_compute_network.hub.self_link
peer_network = "projects/your-second-project/global/networks/spoke2-vpc"
}
# ...mirror "spoke2_to_hub" + spoke2's subnet/VM/firewall use provider = google.project2The steps are cumulative - step 5 shows everything from steps 1 to 4 plus the peering. If you only want the full command list to copy and paste, jump to Section 6.
5. How VPC peering works - concept animation
Before you run the commands, here is a one-picture explanation of what Google does behind the scenes when you create a peering - the routes of each network get exported to the other, while the firewall of each network stays exactly as it was.
6. gcloud CLI walkthrough - the complete command list
Here is the complete walkthrough with gcloud. Copy these top to bottom and the hub-and-spoke works - every command matches the live demo in the video.
Copy these top to bottom and the hub-and-spoke works — every command matches the live
demo. Project cl-demo-sandbox, region europe-north2 (Stockholm).
Set your project once and make sure the Compute Engine API is on:
1gcloud config set project cl-demo-sandbox
2gcloud services enable compute.googleapis.com
1 · Create the hub and spoke VPCs
1gcloud compute networks create hub-vpc --subnet-mode=custom
2gcloud compute networks subnets create hub-subnet \
3 --network=hub-vpc --region=europe-north2 --range=10.10.1.0/24
4
5gcloud compute networks create spoke1-vpc --subnet-mode=custom
6gcloud compute networks subnets create spoke1-subnet \
7 --network=spoke1-vpc --region=europe-north2 --range=10.20.1.0/24
2 · Create test VMs (no external IPs)
e2-micro is the cheapest machine type; --no-address means no public IP.
1gcloud compute instances create hub-vm \
2 --zone=europe-north2-a --subnet=hub-subnet --no-address \
3 --machine-type=e2-micro --image-family=debian-13 --image-project=debian-cloud
4gcloud compute instances create spoke1-vm \
5 --zone=europe-north2-a --subnet=spoke1-subnet --no-address \
6 --machine-type=e2-micro --image-family=debian-13 --image-project=debian-cloud
3 · Reach the VMs with no public IP (IAP SSH)
These VMs have --no-address, so there's no public IP to SSH to. The console's SSH
button tunnels through IAP (Identity-Aware Proxy) instead: your browser → Google's
IAP relay → the VM's internal IP. IAP always connects from Google's fixed range
35.235.240.0/20, so that's the source the firewall must allow (not your laptop's IP).
1# 1. enable the IAP API (one-time, project-wide)
2gcloud services enable iap.googleapis.com
3
4# 2. let IAP's range SSH into the hub
5gcloud compute firewall-rules create hub-fw-allow-iap-ssh \
6 --network=hub-vpc --direction=INGRESS --action=ALLOW \
7 --rules=tcp:22 --source-ranges=35.235.240.0/20
8
9# 3. ...and into spoke1 (one rule per network whose VMs you SSH into)
10gcloud compute firewall-rules create spoke1-fw-allow-iap-ssh \
11 --network=spoke1-vpc --direction=INGRESS --action=ALLOW \
12 --rules=tcp:22 --source-ranges=35.235.240.0/20
You also need the IAP-secured Tunnel User role (roles/iap.tunnelResourceAccessor) —
Owners and Editors already have it.
Now open hub-vm from the console's SSH button and ping the spoke by its internal IP
(peer VM names don't resolve across peering, so always use the IP):
1ping -c 3 10.20.1.2 # hub-vm → spoke1-vm — fails for now (no peering yet). Good.
4 · Peer both sides — ACTIVE only after the second
Peering is bilateral: each side declares it, and it flips to ACTIVE only once both halves exist.
1# side 1 — on hub-vpc
2gcloud compute networks peerings create hub-to-spoke1 \
3 --network=hub-vpc --peer-network=spoke1-vpc
4
5# side 2 — on spoke1-vpc
6gcloud compute networks peerings create spoke1-to-hub \
7 --network=spoke1-vpc --peer-network=hub-vpc
8
9# both entries must say ACTIVE
10gcloud compute networks peerings list --network=hub-vpc
5 · Firewall — routes make it routable, firewall makes it allowed
Naming that reads correctly: <network-it-lives-on>-fw-allow-from-<source>. A firewall
rule always lives on the network that receives the traffic — you stand at your own
door and decide who may knock. (Egress is implied-allow; ingress is implied-deny, so
inbound rules are the only ones you need here.)
1# standing on spoke1 — let the hub in
2gcloud compute firewall-rules create spoke1-fw-allow-from-hub \
3 --network=spoke1-vpc --direction=INGRESS --action=ALLOW \
4 --rules=icmp,tcp:22 --source-ranges=10.10.1.0/24
5
6# standing on the hub — let spoke1 in
7gcloud compute firewall-rules create hub-fw-allow-from-spokes \
8 --network=hub-vpc --direction=INGRESS --action=ALLOW \
9 --rules=icmp,tcp:22 --source-ranges=10.20.1.0/24
Back on hub-vm, ping the spoke again — now it works:
1ping -c 3 10.20.1.2 # hub-vm → spoke1-vm — SUCCESS 🎉
Peering made it routable, the firewall made it allowed — you need both.
6 · Cross-project: spoke2 in a second project
This part needs a second GCP project (with billing). Replace your-second-project with
its real ID everywhere below — everything above ran in cl-demo-sandbox only.
1# build spoke2 in the second project
2gcloud compute networks create spoke2-vpc --subnet-mode=custom --project=your-second-project
3gcloud compute networks subnets create spoke2-subnet --project=your-second-project \
4 --network=spoke2-vpc --region=europe-north2 --range=10.30.1.0/24
5gcloud compute instances create spoke2-vm --project=your-second-project \
6 --zone=europe-north2-a --subnet=spoke2-subnet --no-address \
7 --machine-type=e2-micro --image-family=debian-13 --image-project=debian-cloud
8
9# peer hub <-> spoke2 — one side in each project
10gcloud compute networks peerings create hub-to-spoke2 --project=cl-demo-sandbox \
11 --network=hub-vpc --peer-network=spoke2-vpc --peer-project=your-second-project
12gcloud compute networks peerings create spoke2-to-hub --project=your-second-project \
13 --network=spoke2-vpc --peer-network=hub-vpc --peer-project=cl-demo-sandbox
14
15# the hub rule already allows spoke1 — ADD spoke2's range to the same rule
16gcloud compute firewall-rules update hub-fw-allow-from-spokes --project=cl-demo-sandbox \
17 --source-ranges=10.20.1.0/24,10.30.1.0/24
18
19# standing on spoke2 — let the hub in, and let IAP SSH you in to test
20gcloud compute firewall-rules create spoke2-fw-allow-from-hub --project=your-second-project \
21 --network=spoke2-vpc --direction=INGRESS --action=ALLOW \
22 --rules=icmp,tcp:22 --source-ranges=10.10.1.0/24
23gcloud compute firewall-rules create spoke2-fw-allow-iap-ssh --project=your-second-project \
24 --network=spoke2-vpc --direction=INGRESS --action=ALLOW \
25 --rules=tcp:22 --source-ranges=35.235.240.0/20
Now the two tests that prove the point:
hub-vm→spoke2-vm(ping 10.30.1.2) → works — the hub peers with spoke2.spoke1-vm→spoke2-vm(ping 10.30.1.2) → fails, even though both peer with the hub.
The trap: peering is never transitive. A↔B and A↔C never give you B↔C.
7. The same hub and spoke in Terraform
Everything we did by hand in Section 6 as Terraform code. One terraform apply builds the networks, the subnets, the VMs, the peering in both directions and the firewall rules.
If you have not worked with the Google provider before, the only thing you need is a provider "google" block with your project and region, and credentials - either gcloud auth application-default login on your laptop or a service account key. Watch out for the invalid function argument credential file error if you go the service account route, I have a separate post on it.
The same hub-and-spoke as code — one terraform apply builds everything the CLI steps do.
Region europe-north2, project cl-demo-sandbox. terraform sorts out the order itself,
so these files can live side by side.
providers.tf
1provider "google" {
2 project = "cl-demo-sandbox"
3 region = "europe-north2"
4}
networks.tf
1resource "google_compute_network" "hub" {
2 name = "hub-vpc"
3 auto_create_subnetworks = false
4}
5
6resource "google_compute_subnetwork" "hub" {
7 name = "hub-subnet"
8 network = google_compute_network.hub.id
9 region = "europe-north2"
10 ip_cidr_range = "10.10.1.0/24"
11}
12
13resource "google_compute_network" "spoke1" {
14 name = "spoke1-vpc"
15 auto_create_subnetworks = false
16}
17
18resource "google_compute_subnetwork" "spoke1" {
19 name = "spoke1-subnet"
20 network = google_compute_network.spoke1.id
21 region = "europe-north2"
22 ip_cidr_range = "10.20.1.0/24"
23}
instances.tf — no external IP
An empty access_config would give the VM a public IP; omitting the block entirely is
what makes it internal-only.
1resource "google_compute_instance" "hub" {
2 name = "hub-vm"
3 zone = "europe-north2-a"
4 machine_type = "e2-micro"
5
6 boot_disk {
7 initialize_params {
8 image = "debian-cloud/debian-13"
9 }
10 }
11
12 network_interface {
13 subnetwork = google_compute_subnetwork.hub.id
14 # no access_config → no external IP
15 }
16}
17
18resource "google_compute_instance" "spoke1" {
19 name = "spoke1-vm"
20 zone = "europe-north2-a"
21 machine_type = "e2-micro"
22
23 boot_disk {
24 initialize_params {
25 image = "debian-cloud/debian-13"
26 }
27 }
28
29 network_interface {
30 subnetwork = google_compute_subnetwork.spoke1.id
31 }
32}
peering.tf — both directions
1resource "google_compute_network_peering" "hub_to_spoke1" {
2 name = "hub-to-spoke1"
3 network = google_compute_network.hub.self_link
4 peer_network = google_compute_network.spoke1.self_link
5}
6
7resource "google_compute_network_peering" "spoke1_to_hub" {
8 name = "spoke1-to-hub"
9 network = google_compute_network.spoke1.self_link
10 peer_network = google_compute_network.hub.self_link
11}
firewall.tf
Two kinds of rule: IAP SSH (so you can reach the VMs with no public IP, from Google's
35.235.240.0/20 range) and the hub↔spoke rules (so the VMs can reach each other).
1# --- IAP SSH: reach each VM from the console with no public IP ---
2resource "google_compute_firewall" "hub_fw_allow_iap_ssh" {
3 name = "hub-fw-allow-iap-ssh"
4 network = google_compute_network.hub.id
5 direction = "INGRESS"
6 source_ranges = ["35.235.240.0/20"]
7 allow {
8 protocol = "tcp"
9 ports = ["22"]
10 }
11}
12
13resource "google_compute_firewall" "spoke1_fw_allow_iap_ssh" {
14 name = "spoke1-fw-allow-iap-ssh"
15 network = google_compute_network.spoke1.id
16 direction = "INGRESS"
17 source_ranges = ["35.235.240.0/20"]
18 allow {
19 protocol = "tcp"
20 ports = ["22"]
21 }
22}
23
24# --- hub <-> spoke: lives on the network that RECEIVES the traffic ---
25# lives on spoke1 — lets the hub in
26resource "google_compute_firewall" "spoke1_fw_allow_from_hub" {
27 name = "spoke1-fw-allow-from-hub"
28 network = google_compute_network.spoke1.id
29 direction = "INGRESS"
30 source_ranges = ["10.10.1.0/24"]
31 allow {
32 protocol = "icmp"
33 }
34 allow {
35 protocol = "tcp"
36 ports = ["22"]
37 }
38}
39
40# lives on the hub — lets spoke1 in (add "10.30.1.0/24" when spoke2 exists)
41resource "google_compute_firewall" "hub_fw_allow_from_spokes" {
42 name = "hub-fw-allow-from-spokes"
43 network = google_compute_network.hub.id
44 direction = "INGRESS"
45 source_ranges = ["10.20.1.0/24"]
46 allow {
47 protocol = "icmp"
48 }
49 allow {
50 protocol = "tcp"
51 ports = ["22"]
52 }
53}
terraform apply, then SSH into hub-vm from the console and ping -c 3 10.20.1.2 —
it reaches spoke1-vm over internal IPs. (Ping the IP, not the name: peer VM names
don't resolve across peering.)
Cross-project (spoke2)
spoke2 lives in a second project, so you add a second google provider aliased to it and
scope spoke2's resources with provider = google.project2. Peering still needs a resource
on each side, and each side needs permissions on its own project.
1provider "google" {
2 alias = "project2"
3 project = "your-second-project"
4 region = "europe-north2"
5}
6
7# hub side (default project)
8resource "google_compute_network_peering" "hub_to_spoke2" {
9 name = "hub-to-spoke2"
10 network = google_compute_network.hub.self_link
11 peer_network = "projects/your-second-project/global/networks/spoke2-vpc"
12}
13# ...and the mirror "spoke2_to_hub" created with provider = google.project2,
14# plus spoke2's own subnet, VM, and firewall rules under that provider.
Then widen the hub rule to include spoke2's range — change hub_fw_allow_from_spokes to
source_ranges = ["10.20.1.0/24", "10.30.1.0/24"] and re-apply.
Tip: one terraform apply creates both peering directions at once — but each
google_compute_network_peering still needs permissions on its own network's project.
Tip - Keep this project in its own Terraform state. The spoke projects of a real hub-and-spoke are usually managed by different teams, and they only need the hub's network
self_linkas an input, which you can share through a terraform_remote_state data source or a plain Terraform variable.
8. The transitive peering trap
Here is the thing which catches everyone the first time. After step 7 we have -
hub-vpcpeered withspoke1-vpchub-vpcpeered withspoke2-vpc
So you would expect spoke1-vm to reach spoke2-vm through the hub. It does not.
1# from hub-vm -> spoke2-vm : works (hub peers with spoke2)
2ping -c 3 10.30.1.2
3
4# from spoke1-vm -> spoke2-vm : FAILS (both peer with the hub, but not with each other)
5ping -c 3 10.30.1.2
VPC peering is never transitive. A ↔ B and A ↔ C never give you B ↔ C, and no firewall rule can fix it, because there is simply no route from spoke1 to spoke2. If you really need spoke-to-spoke traffic you have three options -
- Peer the spokes directly with each other (a full mesh - fine for 3 networks, a nightmare for 30).
- Use Network Connectivity Center with the hub as a transit hub.
- Put the workloads on one network instead - which is exactly what Shared VPC is for.
I have written the cross-project part of this lab up as its own post, with the two-provider Terraform pattern - Google Cloud cross-project VPC peering.
9. Conclusion and what to read next
VPC Network Peering is the simplest way to connect two VPC networks on Google Cloud, and now you have seen the whole thing - the concept, the hub-and-spoke build with diagrams for each step, the gcloud commands, the Terraform, and the transitive trap.
The three things to remember -
- Peering is bilateral - create it on both sides, and check for
ACTIVE. - Routes are exchanged, firewall rules are not - a firewall rule always lives on the network that receives the traffic.
- Peering is not transitive - for many networks, look at Shared VPC or Network Connectivity Center.
Here is the rest of the Google Cloud networking series -
- Google Cloud cross-project VPC peering - two projects, two providers
- Google Cloud Organization setup with Cloud Identity (free) - needed before Shared VPC
- Google Cloud Shared VPC - one network, many projects
- Google Cloud Private Service Connect - publish one service privately
If you have any questions or you are stuck somewhere, put them in the comments and I will try to help.
Posts in this series
- Google Cloud Private Service Connect (PSC) Explained - Publish a Service with a Service Attachment and Consume It Through a PSC Endpoint
- Google Cloud Shared VPC Explained - Host Project, Service Project, networkUser and Cloud NAT (gcloud and Terraform)
- Google Cloud Organization Setup with Cloud Identity Free - Create an Org and Move Your Projects Under It
- Google Cloud Cross-Project VPC Peering - Peer Two VPCs in Different Projects (gcloud and Terraform)
- Google Cloud VPC Peering - Hub and Spoke Setup Step by Step with gcloud and Terraform