VPC Peering Deep Dive — Direct VPC-to-VPC Connectivity
Executive Summary
VPC Peering = Direct connectivity giữa 2 VPCs (có thể khác projects).
Key points:
- ✅ No transitivity (peering không propagate)
- ✅ Subnet overlap không allowed (CIDR conflict)
- ✅ Bi-directional (both VPCs must accept)
- ✅ Free data transfer (vs internet egress charge)
- ❌ Limited to 25 peering connections per VPC (soft limit)
What is VPC Peering?
Peering vs Other Connectivity
yaml
VPC Peering:
- Scope: VPC ↔ VPC (same/different project, same region)
- Data path: Google backbone
- Latency: ~100 µs
- Cost: Free for data transfer (normal charges apply)
- Throughput: Per-project limits
Shared VPC:
- Scope: Host project ↔ Service projects
- Centralized network
- Network admin controls all projects
Cloud Interconnect:
- Scope: On-premises ↔ GCP
- Dedicated connection
- High throughput, low latency
VPN:
- Scope: Anywhere ↔ GCP
- Encrypted tunnel
- Lower throughput, more latency than peeringNo Transitivity: Critical Constraint
The Problem
Scenario: 3 VPCs (A, B, C)
VPC A (10.0.0.0/16) ←→ Peering ←→ VPC B (10.1.0.0/16)
VPC B (10.1.0.0/16) ←→ Peering ←→ VPC C (10.2.0.0/16)
Question: Can A reach C?
Answer: NO ✗ (no transitivity)
VPC A → VPC B: Direct peering, YES ✓
VPC B → VPC C: Direct peering, YES ✓
VPC A → VPC C: NO peering connection, NO ✗
Implication: Traffic A → B → C NOT supported
Must peer A ↔ C separatelyMesh Topology Required
3 VPCs need full connectivity:
Wrong (transitive expectation):
A ←→ B ←→ C
Result: C cannot reach A
Right (full mesh):
A ↔ B
A ↔ C
B ↔ C
Result: Full connectivity
Formula: N VPCs → N×(N-1)/2 peering connections
3 VPCs: 3 peering connections
5 VPCs: 10 peering connections
10 VPCs: 45 peering connections
Scaling problem: Mesh explodes with VPCsSolution: Hub-and-Spoke + Routing
Instead of mesh peering, use routing:
VPC A (10.0.0.0/16)
VPC Hub (10.254.0.0/16)
VPC C (10.2.0.0/16)
Setup:
A ↔ Hub (peering)
C ↔ Hub (peering)
Routing:
VPC A: route 10.2.0.0/16 → Hub
Hub: route 10.0.0.0/16 → A, route 10.2.0.0/16 → C
VPC C: route 10.0.0.0/16 → Hub
Traffic A → C: A → Hub → C (via routing)
Requirements:
- Hub instances have IP forwarding enabled
- Hub instances act as routers
- Firewall rules allow forwarding
Advantage over mesh: O(N) peering instead of O(N²)Subnet Overlap Constraints
CIDR Conflict Detection
Scenario: Attempting to peer
VPC A subnets:
├── prod-app (10.0.1.0/24)
└── prod-db (10.0.2.0/24)
VPC B subnets:
├── staging-app (10.0.3.0/24)
├── staging-db (10.0.4.0/24)
└── backup (10.0.0.0/24) ← Overlaps with VPC A 10.0.0.0/16?
Peering request: VPC A ←→ VPC B
Validation:
Is 10.0.1.0/24 ∩ 10.0.3.0/24? NO ✓
Is 10.0.1.0/24 ∩ 10.0.4.0/24? NO ✓
Is 10.0.1.0/24 ∩ 10.0.0.0/24? NO ✓
Is 10.0.2.0/24 ∩ 10.0.0.0/24? NO ✓
All subnets check: PASS ✓
Peering: ALLOWED
But:
Is 10.0.0.0/16 (implicit VPC range) ∩ 10.0.0.0/24? YES ✗
Result: Peering FAILED (overlap)Handling Overlap: Workarounds
Problem: VPC A (10.0.0.0/8) and VPC B (10.0.0.0/8) both start with 10.0
Solution 1: Recreate VPC B with different CIDR
(Requires replacing all subnets, VMs)
Solution 2: Use VPC Peering + Manual Routing (advanced)
(Not recommended - complex)
Solution 3: Use Shared VPC instead
(Requires common control, host project)
Solution 4: Use Cloud Interconnect + overlapping CIDR avoidance
(On-premises side uses non-overlapping CIDR)
Best practice: Plan CIDR layout upfront
Document allocation to avoid conflictsSetting Up Peering
Create Peering Connection
bash
gcloud compute networks peerings create peering-a-to-b \
--network=vpc-a \
--peer-network=vpc-b \
--peer-project=project-b
# Creates unidirectional peering: vpc-a → vpc-b
gcloud compute networks peerings accept peering-a-to-b \
--network=vpc-b \
--project=project-b
# Accepts peering: vpc-b → vpc-a
# Now bidirectionalVerify Peering State
bash
gcloud compute networks peerings describe peering-a-to-b \
--network=vpc-a
Output:
state: ACTIVE
stateDetails: null (healthy)
autoCreateRoutes: true
importCustomRoutes: false
exportCustomRoutes: false
importSubnetRoutesWithPublicIp: false
exportSubnetRoutesWithPublicIp: falseRoute Exchange Options
Automatic Routes (Default)
Setup:
autoCreateRoutes: true (default)
Effect:
VPC A creates route for VPC B subnets automatically
VPC B creates route for VPC A subnets automatically
Example:
VPC A: subnet 10.0.1.0/24
VPC B: subnet 10.1.1.0/24
After peering:
VPC A routing table:
10.1.1.0/24 → peering-a-to-b (auto-created)
VPC B routing table:
10.0.1.0/24 → peering-a-to-b (auto-created)
Traffic:
VM in A (10.0.1.5) → VM in B (10.1.1.10)
Route match: 10.1.1.0/24 via peering
→ Packet delivered via peeringCustom Routes Exchange
Setup:
importCustomRoutes: true (VPC B imports A's custom routes)
exportCustomRoutes: true (VPC A exports custom routes)
Effect:
VPC A advertises static/dynamic routes to VPC B
VPC B imports these routes
Example:
VPC A custom route: 192.168.0.0/16 → vpn-gateway (on-prem)
With importCustomRoutes=true:
VPC B adds route: 192.168.0.0/16 → peering-a-to-b
→ VPC B VMs can reach on-prem via VPC A gateway
Without importCustomRoutes:
VPC B doesn't learn route
→ VPC B VMs cannot reach on-premConfiguration
bash
gcloud compute networks peerings update peering-a-to-b \
--network=vpc-a \
--import-custom-routes \
--export-custom-routes
# Now VPC B learns routes from VPC A (custom static/dynamic)DNS Resolution Across Peering
Private DNS Zones with Peering
Setup:
VPC A:
- Private DNS zone "internal.example.com"
- Bound to VPC A only
VPC B:
- No DNS zone defined
Query from VPC B VM: nslookup api.internal.example.com
Result: NXDOMAIN (not found)
Reason: Zone only bound to VPC A, not VPC B
Solution: Bind same zone to both VPCs
gcloud dns managed-zones create internal-zone \
--dns-name=internal.example.com. \
--networks=vpc-a,vpc-b \
--visibility=private
Result:
Both VPC A and VPC B can resolve internal.example.com
Peering necessary for IP reachabilityDNS Records Across Peering
VPC A:
A record: api.internal.example.com → 10.0.1.10 (VM in VPC A)
VPC B:
A record: cache.internal.example.com → 10.1.2.20 (VM in VPC B)
Query from VPC A VM: nslookup cache.internal.example.com
→ 10.1.2.20 returned
Connectivity:
VPC A VM (10.0.1.5) → 10.1.2.20 (VPC B)
Requires: Peering + firewall rule
Result: DNS works, then peering routes trafficMulti-Project Patterns
Pattern 1: Hub-and-Spoke (Shared VPC Alternative)
Project A (Hub):
VPC: hub-network (10.254.0.0/16)
Project B (Spoke):
VPC: project-b-network (10.0.0.0/16)
Project C (Spoke):
VPC: project-c-network (10.1.0.0/16)
Peering:
Hub ↔ Spoke B
Hub ↔ Spoke C
(No direct Spoke B ↔ Spoke C peering)
Routing:
Spoke B: route 10.1.0.0/16 → hub
Hub: route 10.0.0.0/16 → Spoke B, route 10.1.0.0/16 → Spoke C
Spoke C: route 10.0.0.0/16 → hub
Result: Centralized connectivity without Shared VPC
Each project retains network autonomy
Hub project owns routing policyPattern 2: Cross-Environment Peering (Prod → Dev)
Project Production:
VPC: prod-vpc (10.0.0.0/16)
Subnets: prod-app, prod-db
Project Development:
VPC: dev-vpc (10.100.0.0/16)
Subnets: dev-app, dev-db
Peering: prod-vpc ↔ dev-vpc
Use case:
Dev team needs to test against prod database
→ Peer dev-vpc to prod-vpc
→ Set firewall rule: allow dev-app → prod-db only
→ Prod remains isolated (no dev VMs can reach prod-app)
Security:
Peering one-way traffic (firewall enforces)
Prod database accessible from dev
Prod application isolated from devTroubleshooting Peering
Symptom: Peering INACTIVE
bash
Diagnosis:
1. Check peering state:
gcloud compute networks peerings describe peering-a-to-b \
--network=vpc-a
Output: state: INACTIVE
2. Check if peering accepted:
gcloud compute networks peerings describe peering-a-to-b \
--network=vpc-b \
--project=project-b
If ERROR: peering not found
→ Peering not accepted on peer side
3. Accept peering:
gcloud compute networks peerings accept peering-a-to-b \
--network=vpc-b \
--project=project-b
4. Verify bidirectional:
Both VPC-A and VPC-B should show state: ACTIVESymptom: CIDR Overlap Error
bash
Error: (gcloud.compute.networks.peerings.create)
Subnet CIDR block 10.0.0.0/16 overlaps...
Diagnosis:
1. List VPC A subnets:
gcloud compute networks subnets list --network=vpc-a \
--format="value(ipCidrRange)"
2. List VPC B subnets:
gcloud compute networks subnets list --network=vpc-b \
--format="value(ipCidrRange)"
3. Check for overlaps (manually)
4. If overlap exists:
Option A: Delete/recreate conflicting subnet
Option B: Use Shared VPC instead
Option C: Use Cloud NAT to avoid overlapSymptom: Routes Not Appearing
bash
Diagnosis:
1. Check autoCreateRoutes setting:
gcloud compute networks peerings describe peering-a-to-b \
--network=vpc-a \
--format="value(autoCreateRoutes)"
If false: routes not auto-created
2. Manually add routes:
gcloud compute routes create manual-route-to-b \
--destination-range=10.1.0.0/16 \
--network=vpc-a \
--next-hop-vpn-gateway=peering-a-to-b
(Note: peering doesn't work as next-hop type directly,
must use route table entry)
3. Check firewall rules:
May block traffic even if routes exist
gcloud compute firewall-rules list \
--filter="network:vpc-a AND direction:INGRESS"Best Practices
✅ Do:
- Plan CIDR layout upfront (avoid overlap later)
- Use peering for same-region VPC connectivity
- Enable autoCreateRoutes (simpler management)
- Use hub-and-spoke for 3+ VPCs (avoid mesh explosion)
- Monitor peering state (alerting)
- Document peering topology
❌ Don't:
- Assume transitive routing (breaks at 3 VPCs)
- Create mesh peering (O(N²) connections)
- Peer VPCs with overlapping CIDRs (won't work)
- Mix peering + on-premises access without routing (confusing)
- Disable autoCreateRoutes unless necessary
Peering Limits
Per VPC:
- Max peering connections: 25 (soft limit, increasable)
Per project:
- No limit on peering connections across VPCs
If hitting limits:
- Contact Google Cloud support to increase
- Or: Use Shared VPC instead (unlimited VPCs in same org)Conclusion
VPC Peering provides direct, free connectivity between VPCs:
- No transitivity: Design topology carefully (hub-and-spoke)
- CIDR planning: Essential (overlap = no peering)
- Route exchange: Automatic or manual (flexible)
- Multi-project: Useful for cross-org/cross-team connectivity
Best for: Same-region, multi-VPC architectures where Shared VPC not feasible.
Not needed: Single VPC or Shared VPC scenarios.