Skip to content

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 peering

No 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 separately

Mesh 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 VPCs

Solution: 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 conflicts

Setting 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 bidirectional

Verify 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: false

Route 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 peering

Custom 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-prem

Configuration

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 reachability

DNS 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 traffic

Multi-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 policy

Pattern 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 dev

Troubleshooting 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: ACTIVE

Symptom: 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 overlap

Symptom: 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.