GCP

#8 20 pages

GCP Networking

Global VPC, regional subnets, firewall rules, Cloud NAT, Cloud Router, VPC Peering, Shared VPC, Private Google Access, and Private Service Connect — the primitives that differ most from the region-scoped, subnet-boundary model AWS engineers already carry in their heads.

0/0 checks

VPC Architecture

GCP VPC is global — unlike AWS where a VPC is region-scoped, a single GCP VPC spans all regions. Subnets are regional (one subnet = one region), but they all belong to the same global VPC. This means a VM in us-central1 and a VM in europe-west1 can communicate privately within the same VPC without peering.

graph TD
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff
    classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef warn fill:#e74c3c,stroke:#c0392b,color:#fff

    subgraph FRONT["Global Front Door"]
        GLB["Cloud Load Balancing<br/>one global anycast IP<br/>backends span regions"]:::blue
    end

    subgraph GCP["GCP Global VPC: my-vpc — one VPC, every region"]
        subgraph US["Region: us-central1"]
            subgraph SUB_US["Subnet 10.0.1.0/24 (regional)"]
                VM_US["GCE VM<br/>10.0.1.10"]:::orange
                GKE_US["GKE Node<br/>10.0.1.20"]:::gcp
            end
            ROUTER_US["Cloud Router (us-central1)"]:::blue
            NAT_US["Cloud NAT (us-central1)"]:::teal
        end

        subgraph EU["Region: europe-west1"]
            subgraph SUB_EU["Subnet 10.1.1.0/24 (regional)"]
                VM_EU["GCE VM<br/>10.1.1.10"]:::orange
                GKE_EU["GKE Node<br/>10.1.1.20"]:::gcp
            end
            ROUTER_EU["Cloud Router (europe-west1)"]:::blue
            NAT_EU["Cloud NAT (europe-west1)"]:::teal
        end

        NOPEER["No VPC Peering, no Transit Gateway —<br/>this is ONE VPC, not two"]:::warn
    end

    INTERNET["Internet"]:::blue
    INTERNET --> GLB
    GLB -->|routes to nearest healthy backend| SUB_US
    GLB -->|routes to nearest healthy backend| SUB_EU
    NAT_US -->|outbound only| INTERNET
    NAT_EU -->|outbound only| INTERNET
    VM_US -->|"private, same VPC, crosses regions<br/>over Google's private backbone"| VM_EU
    VM_US -.-> NOPEER

Key difference from AWS: In AWS, cross-region communication between VPCs requires VPC Peering or Transit Gateway. In GCP, it's native — same VPC, different regional subnets, traffic never leaves Google's private backbone.

A VM in us-central1 needs to talk privately to a VM in europe-west1. Both are in the same GCP VPC. What do you need to set up to make that work?


Subnets

GCP subnets are regional — you pick a region and a CIDR. Unlike AWS, there are no Availability Zone-level subnets. GCP manages zone distribution of compute resources within the region automatically.

Subnet Modes

Mode Behavior
Auto mode One subnet per region created automatically (10.128.0.0/9 range). Quick start, but limited control.
Custom mode You define CIDRs per region. Required for production — gives full CIDR control and avoids overlap with on-prem.
GCP pre-creates one subnet per region, all carved out of the reserved 10.128.0.0/9 block. Fast to start with — zero planning needed — but you inherit whatever CIDR GCP picked for every region, including ones you may never use, and you have no control over overlap with an on-prem range you might VPN or Interconnect to later. Fine for a scratch project, risky for anything headed toward production or hybrid connectivity.
You explicitly create each regional subnet with your own CIDR — nothing exists until you define it. This is the only mode that lets you size ranges deliberately (including secondary ranges for GKE) and guarantee no collision with on-prem or another peered VPC. Anthos and most production Terraform modules default to custom mode for exactly this reason.
# Create custom mode VPC
gcloud compute networks create my-vpc --subnet-mode=custom

# Create a regional subnet
gcloud compute networks subnets create app-subnet \
  --network=my-vpc \
  --region=us-central1 \
  --range=10.0.1.0/24

Secondary Ranges (for GKE)

GCP subnets support secondary IP ranges — extra CIDR blocks on the same subnet used by GKE for Pod IPs and Service IPs (VPC-native clusters). This avoids IP exhaustion on the primary range.

graph TD
    classDef primary fill:#3498db,stroke:#2471a3,color:#fff
    classDef pods fill:#2ecc71,stroke:#27ae60,color:#fff
    classDef svc fill:#9b59b6,stroke:#8e44ad,color:#fff

    subgraph SUBNET["Subnet: gke-subnet (one CIDR block, three ranges)"]
        PRIMARY["Primary range: 10.0.2.0/24<br/>used by GKE Node IPs"]:::primary
        PODS["Secondary range 'pods': 10.1.0.0/16<br/>Alias IPs, one block per Node"]:::pods
        SVC["Secondary range 'services': 10.2.0.0/20<br/>ClusterIP Service VIPs"]:::svc
    end

    NODE["GKE Node"]:::primary -->|owns an IP from| PRIMARY
    NODE -->|gets a /24 slice of| PODS
    POD["Pod on that Node"]:::pods -->|Alias IP routed natively, no overlay| PODS
    SERVICE["k8s Service"]:::svc -->|ClusterIP allocated from| SVC

AWS parallel: AWS secondary CIDRs are added at the VPC level; GCP secondary ranges are per-subnet. GKE automatically uses these for pod networking (Alias IPs) without needing an overlay network.

gcloud compute networks subnets create gke-subnet \
  --network=my-vpc \
  --region=us-central1 \
  --range=10.0.2.0/24 \
  --secondary-range pods=10.1.0.0/16,services=10.2.0.0/20

A GKE cluster is running in VPC-native mode with a secondary range for Pods. Why doesn't it need an overlay network the way many on-prem Kubernetes clusters do?


Firewall Rules

GCP firewall rules are network-scoped, not resource-scoped. They're conceptually closest to AWS Security Groups but evaluated at the network level, not the ENI. There are no NACLs in GCP.

Key Properties

  • Direction: INGRESS (inbound) or EGRESS (outbound)
  • Priority: 0–65535 — lower number wins, evaluated first
  • Action: ALLOW or DENY
  • Targets: apply to all VMs, or by target tag or target service account
  • Sources/Destinations: CIDR, source tag, or source service account
  • Stateful: GCP firewall rules ARE stateful (like AWS SGs) — return traffic is auto-allowed
Firewall Rule: allow-internal-http
  Direction:   INGRESS
  Priority:    1000
  Action:      ALLOW
  Target:      tag: backend
  Source:      tag: frontend
  Ports:       TCP 8080
# Allow inbound HTTP to all VMs tagged "backend" from VMs tagged "frontend"
gcloud compute firewall-rules create allow-internal-http \
  --network=my-vpc \
  --direction=INGRESS \
  --priority=1000 \
  --action=ALLOW \
  --target-tags=backend \
  --source-tags=frontend \
  --rules=tcp:8080

Tags vs Service Accounts as Selectors

Selector How it works Security
Network tag String applied to VM (--tags=frontend). Any user with VM edit access can add/remove. Lower — tag is just metadata
Service account VM's identity (what IAM SA it runs as). Can't be spoofed. Higher — tied to IAM identity
A plain string attached to a VM (--tags=frontend). Cheap and readable in rule names, but it's just instance metadata — anyone with compute.instances.setTags can add or remove it from any VM, which silently changes what firewall rules apply to that VM. Convenient for quick iteration, weak as a security boundary.
The IAM service account identity the VM actually runs as. It can't be reassigned by editing instance metadata — changing it requires iam.serviceAccounts.actAs permission on that specific SA, a much narrower grant. This is what "identity-aware" firewalling means in GCP: the rule trusts who the VM authenticates as, not a label someone attached to it.

Best practice: Use service accounts as firewall selectors in production. Tags are convenient but any engineer with compute.instances.setTags can reassign them.

Default Rules

Every GCP VPC has two implied rules (lowest priority, 65535):

  • default-allow-internal — allow all traffic between instances in the same network (often removed for stricter setups)
  • default-deny-ingress — deny all ingress
  • default-allow-egress — allow all egress
graph TD
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff
    classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef red fill:#e74c3c,stroke:#c0392b,color:#fff
    classDef green fill:#2ecc71,stroke:#27ae60,color:#fff

    INTERNET["Internet / other VM"]:::blue

    subgraph EVAL["Ingress evaluation (priority-ordered, lowest number first)"]
        R1["Priority 1000: allow-internal-http<br/>ALLOW tcp:8080 from tag:frontend"]:::green
        R2["Priority 65535: default-deny-ingress<br/>DENY everything else (implied, always last)"]:::red
    end

    VM["Target VM (tag: backend)"]:::orange
    CONNTRACK["Connection tracking table<br/>(stateful — remembers this flow)"]:::teal
    EGRESS_RULE["default-allow-egress<br/>(implied, priority 65535)"]:::green
    DEST["External destination"]:::blue

    INTERNET -->|"packet arrives"| R1
    R1 -->|matched, ALLOW| VM
    R1 -.->|"no match falls through to"| R2
    R2 -->|matched, DENY| BLOCKED["dropped, no response sent"]:::red
    VM -->|allowed inbound connection registered in| CONNTRACK
    VM -->|"outbound reply on same flow"| CONNTRACK
    CONNTRACK -->|"return traffic auto-allowed,<br/>no matching EGRESS rule needed"| INTERNET
    VM -->|"new outbound connection<br/>(different flow)"| EGRESS_RULE
    EGRESS_RULE --> DEST

Two firewall rules could both match the same packet: one at priority 1000 (ALLOW) and the implied default-deny-ingress at priority 65535 (DENY). Which one wins, and why?

GCP Firewall vs AWS Security Groups

GCP Firewall Rule AWS Security Group
Scope Network-wide, filtered by tag/SA Attached to specific ENI
Stateful Yes Yes
Allow/Deny Both Allow only
Priority Explicit numeric priority No priority, union of allows
Subnet-level filter No (no NACLs in GCP) NACLs exist at subnet boundary
Reference by identity Tag or Service Account SG ID

Cloud NAT

Cloud NAT provides outbound internet access for VMs without external IP addresses. Conceptually mirrors AWS NAT Gateway.

sequenceDiagram
    participant VM as GCE VM (private IP 10.0.1.10, no external IP)
    participant ROUTER as Cloud Router
    participant NAT as Cloud NAT (distributed, no single VM)
    participant EXT as External API (8.8.8.8)

    Note over VM,ROUTER: Outbound connection
    VM->>ROUTER: packet to 8.8.8.8, src 10.0.1.10:54321
    ROUTER->>NAT: matched default route 0.0.0.0/0, handed to NAT
    NAT->>NAT: allocate NAT IP:port pair from configured pool
    NAT->>EXT: packet forwarded, src rewritten to NAT external IP:port
    Note over NAT: mapping 10.0.1.10:54321 to NAT IP:port<br/>held in NAT's connection table

    Note over EXT,NAT: Return path
    EXT-->>NAT: response addressed to NAT external IP:port
    NAT->>NAT: look up connection table, find original private IP:port
    NAT-->>VM: response translated back to 10.0.1.10:54321

To walk through the same flow one step at a time:

1. VM sends an outbound packet. A GCE VM with no external IP addresses a packet to 8.8.8.8. Its source is its private IP and an ephemeral source port, e.g. 10.0.1.10:54321.
2. Cloud Router matches the default route. The VPC's 0.0.0.0/0 route points at Cloud NAT (via the Cloud Router it's attached to), so the packet is handed off instead of being dropped for lack of a public IP.
3. Cloud NAT allocates and rewrites. NAT picks a NAT IP:port pair from its configured pool, records the mapping 10.0.1.10:54321 → NAT-IP:NAT-port in its connection table, and rewrites the packet's source before it leaves Google's network.
4. Response comes back to the NAT IP. The external server has no idea a private VM exists — it replies to the NAT IP:port it saw as the source.
5. Cloud NAT reverses the translation. It looks up the NAT IP:port in its connection table, finds the original 10.0.1.10:54321, rewrites the destination, and delivers the response to the VM — which never sees or needs an external IP at any point.

How It Works

Cloud NAT is a distributed software service — it's not a VM or a single machine. It runs on Google's infrastructure, attached to a Cloud Router, and automatically scales with traffic.

# Cloud NAT requires a Cloud Router first
gcloud compute routers create my-router \
  --network=my-vpc \
  --region=us-central1

# Create Cloud NAT on the router
gcloud compute routers nats create my-nat \
  --router=my-router \
  --region=us-central1 \
  --nat-all-subnet-ip-ranges \
  --auto-allocate-nat-external-ips

GCP Cloud NAT vs AWS NAT Gateway

GCP Cloud NAT AWS NAT Gateway
Architecture Distributed, no single VM Managed service, single AZ
Placement Regional (configured on Cloud Router) Per-subnet (must be in public subnet)
Public subnet needed No — GCP has no concept of "public subnet" Yes — NAT GW lives in public subnet
AZ resilience Built-in, fully distributed One per AZ recommended
Cost ~$0.044/hr + $0.045/GB ~$0.045/hr + $0.045/GB
Scale Auto-scales Auto-scales

GCP has no "public/private subnet" distinction. A subnet is "private" by not assigning external IPs to VMs. Outbound internet access for those VMs is then provided by Cloud NAT.

A VM with no external IP has an active outbound connection through Cloud NAT. Does that connection let anything on the internet initiate a new inbound connection to the VM?


Cloud Router

Cloud Router is a BGP routing service that dynamically exchanges routes between your VPC and external networks (on-prem via Cloud VPN or Cloud Interconnect, or other VPCs via VPN).

  • Required for Cloud NAT
  • Advertises your VPC subnets via BGP to on-prem routers
  • Learns on-prem routes and injects them into VPC route tables dynamically
graph TD
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff
    classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff

    subgraph VPCSIDE["GCP VPC"]
        VPC["my-vpc<br/>(subnet CIDRs to advertise)"]:::gcp
        ROUTER["Cloud Router<br/>BGP speaker, ASN configured per region"]:::blue
        NAT["Cloud NAT<br/>(depends on this Cloud Router)"]:::teal
        ROUTETABLE["VPC route table<br/>dynamic routes injected here"]:::gcp
    end

    subgraph HYBRID["Hybrid connectivity"]
        VPN["Cloud VPN / Cloud Interconnect"]:::orange
        ONPREM["On-Premises Network<br/>(peer BGP router, own ASN)"]:::blue
    end

    VPC -->|"advertises subnet CIDRs"| ROUTER
    ROUTER -->|"powers"| NAT
    ROUTER -->|"BGP session: exchange routes"| VPN
    VPN -->|"encrypted tunnel / dedicated circuit"| ONPREM
    ROUTER -->|"learned on-prem routes injected into"| ROUTETABLE

AWS parallel: AWS Transit Gateway + VPN Gateway + BGP achieves similar hybrid connectivity. Cloud Router is GCP's managed BGP speaker that plugs into these services.

You want to enable Cloud NAT in a region where no hybrid connectivity (VPN/Interconnect) exists at all. Do you still need a Cloud Router?


VPC Peering vs Shared VPC

GCP offers two models for multi-VPC connectivity — different from AWS's Transit Gateway model.

Connects two otherwise-independent VPCs. Each side keeps its own subnets, firewall rules, and admin team; peering just makes their internal IPs routable to each other. No transitivity — if A peers with B and A peers with C, B still can't reach C through A. Good for connecting a handful of VPCs that are otherwise separately owned.
One VPC, centrally owned, used by many projects. A Host Project owns the actual VPC and subnets; Service Projects don't get their own VPC at all — they deploy workloads directly into the host's subnets. Every service project automatically shares full route transitivity with every other one, because there was only ever one VPC. Good for an org-wide platform team that wants one network to secure and monitor.

VPC Peering

Direct L3 peering between two GCP VPCs. Internal IPs are routable across the peering.

graph TD
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff
    classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef red fill:#e74c3c,stroke:#c0392b,color:#fff

    subgraph A["VPC A: Production (10.0.0.0/16)"]
        A1["App tier"]:::gcp
    end
    subgraph B["VPC B: Data (10.1.0.0/16)"]
        B1["Database tier"]:::blue
    end
    subgraph C["VPC C: Shared Services (10.2.0.0/16)"]
        C1["Logging / monitoring agents"]:::teal
    end

    A -->|"VPC Peering — direct, bidirectional"| B
    A -->|"VPC Peering — direct, bidirectional"| C
    B -.->|"NOT reachable: peering isn't transitive"| C
    NOTE["B cannot reach C through A,<br/>even though A peers with both"]:::red
    B -.-> NOTE

Same limitation as AWS: no transitive routing. N*(N-1)/2 peering connections for full mesh.

Shared VPC (Host/Service Project)

GCP-specific: a Host Project owns the VPC and subnets. Service Projects attach to it and deploy workloads into the host's subnets. Centralized network control across many projects.

Owns the VPC, the subnets, the firewall rules, and Cloud Router/NAT config. Exactly one project per Shared VPC. Whoever administers this project controls the network for every attached service project — IAM roles like Network Admin and Subnet User here are how the platform team grants (or withholds) subnet access to individual teams without giving them the network itself.
Owns no VPC of its own — it's "attached" to the host and its workloads (GCE, GKE) deploy directly into the host project's subnets, getting IPs from the host's CIDR ranges. A service project team can launch VMs and manage their own IAM/billing/APIs independently, but they never see or edit the host's firewall rules or routing — that stays with the host's network admins.
graph TD
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef teal fill:#1abc9c,stroke:#16a085,color:#fff

    subgraph HOSTPROJ["Host Project — owns the network"]
        VPC["Shared VPC: platform-vpc"]:::gcp
        SUB1["Subnet: team-a (10.0.1.0/24)"]:::gcp
        SUB2["Subnet: team-b (10.0.2.0/24)"]:::gcp
        FW["Firewall rules, Cloud Router, Cloud NAT<br/>(all owned here, only here)"]:::gcp
        VPC --> SUB1
        VPC --> SUB2
        VPC --> FW
    end

    subgraph SP1["Service Project: Team A"]
        WL1["GCE / GKE workloads<br/>own IAM, billing, APIs"]:::blue
    end
    subgraph SP2["Service Project: Team B"]
        WL2["GCE / GKE workloads<br/>own IAM, billing, APIs"]:::orange
    end
    subgraph SP3["Service Project: Team C"]
        WL3["No subnet attached yet"]:::teal
    end

    SUB1 -.->|"Subnet User role grants IP allocation"| WL1
    SUB2 -.->|"Subnet User role grants IP allocation"| WL2
    HOSTPROJ -.->|"not yet attached"| SP3
VPC Peering Shared VPC
Use case Connect separate VPCs Central network for multiple teams/projects
Route transitivity No Yes — all service projects share the host VPC
Admin model Each VPC team manages their own Centralized network team owns the host
AWS analog VPC Peering Centralized VPC + Transit Gateway (roughly)
Best for Connecting a few VPCs Org-wide multi-project platform

VPC A is peered with both VPC B and VPC C. A workload in VPC B needs to reach a workload in VPC C. Does the existing peering already make that possible?


Private Google Access & Private Service Connect

Private Google Access

Allows VMs without external IPs to reach Google APIs (GCS, BigQuery, Cloud SQL APIs) using internal routes — traffic stays on Google's backbone.

When a VM has no external IP, Private Google Access is what makes gsutil or a BigQuery client library call actually resolve and route successfully instead of timing out trying to reach the public internet:

graph TD
    classDef vm fill:#e67e22,stroke:#d35400,color:#fff
    classDef dns fill:#9b59b6,stroke:#8e44ad,color:#fff
    classDef route fill:#3498db,stroke:#2980b9,color:#fff
    classDef api fill:#4285f4,stroke:#2a56c6,color:#fff

    VM["GCE VM, no external IP<br/>subnet flag: enable-private-ip-google-access"]:::vm
    DNS["DNS resolves *.googleapis.com<br/>to restricted/private range<br/>199.36.153.4/30 or 199.36.153.8/30"]:::dns
    ROUTE["VPC default route 0.0.0.0/0<br/>matches that range, stays internal<br/>(never egresses to internet)"]:::route
    API["Google API front end<br/>(GCS, BigQuery, Cloud SQL Admin API, ...)"]:::api

    VM -->|"1. DNS query"| DNS
    DNS -->|"2. returns internal-only IP"| VM
    VM -->|"3. connects to that internal IP"| ROUTE
    ROUTE -->|"4. delivered over Google's backbone"| API
1. Subnet flag is enabled. --enable-private-ip-google-access is set on the subnet. Without it, a VM with no external IP simply cannot reach Google APIs at all — the traffic has nowhere to go.
2. VM resolves a Google API hostname. A call to storage.googleapis.com (or another Google API domain) triggers a DNS lookup, same as any other API call in application code.
3. DNS returns an internal-only address range. Instead of a public internet-routable IP, the resolver returns an address in the restricted or private Google range (199.36.153.4/30 or 199.36.153.8/30) — these ranges are never reachable from the public internet, only from inside a VPC with Private Google Access enabled.
4. The VPC route table already covers it. That internal range falls inside the VPC's normal route table, so the packet is routed like any internal destination — no NAT, no external IP, no internet egress involved.
5. Google's backbone completes the connection. The request lands on the Google API front end entirely over Google's private network, never touching the public internet at any hop.
gcloud compute networks subnets update app-subnet \
  --region=us-central1 \
  --enable-private-ip-google-access

AWS parallel: VPC Endpoints (Gateway for S3/DynamoDB, Interface for everything else). GCP's Private Google Access is simpler — a subnet flag, no individual endpoint to provision per service.

A subnet does NOT have Private Google Access enabled. A VM in that subnet has no external IP. What happens when it tries to call the GCS API?

Private Service Connect (PSC)

More granular than Private Google Access. Create a PSC endpoint (a forwarding rule with an internal IP) to access specific Google managed services or partner services entirely privately.

graph TD
    classDef consumer fill:#e67e22,stroke:#d35400,color:#fff
    classDef psc fill:#9b59b6,stroke:#8e44ad,color:#fff
    classDef producer fill:#4285f4,stroke:#2a56c6,color:#fff

    subgraph CONSUMER["Your VPC (consumer)"]
        VM["VM or GKE workload"]:::consumer
        EP["PSC Endpoint<br/>internal forwarding rule, IP you choose<br/>e.g. 10.0.5.100"]:::psc
    end

    subgraph PRODUCER["Producer VPC (Google-managed or partner)"]
        SVC["Specific service<br/>(e.g. a managed database, or a partner SaaS)"]:::producer
    end

    VM -->|"connects to internal IP, looks local"| EP
    EP -->|"privately routed, never touches internet<br/>producer never sees your VPC's topology"| SVC
Private Google Access Private Service Connect
Scope All Google APIs broadly Specific service or producer VPC
IP Uses private.googleapis.com DNS Assigns internal IP of your choosing
Use case General Google API access Multi-tenant, specific endpoint, partner services
AWS analog VPC Gateway/Interface Endpoint Interface VPC Endpoint (PrivateLink)
A subnet-wide switch that covers Google APIs broadly (GCS, BigQuery, Cloud SQL Admin API, and more) through a shared restricted IP range. Nothing to provision per service — flip the flag once and every Google API is reachable privately from that subnet.
A named endpoint you create for one specific service or producer, with an internal IP you pick yourself. More setup per service, but far more granular — you can expose exactly one managed service (or a specific partner's service) without opening up "all Google APIs," and it's also how you privately consume services published by another team's VPC, not just Google's own APIs.

You want to privately connect to just one specific partner SaaS service — not "all Google APIs" — and you want to control the exact internal IP your VMs use to reach it. Which feature fits, and why not the other one?

Publishing Your Own Service via PSC (Producer Side)

The diagrams above show the consumer connecting to someone else's service. PSC also works the other way: your team publishes a service, and other VPCs connect to it privately — without VPC Peering, which would expose your entire network.

Why not just use VPC Peering?

VPC Peering opens full L3 routing between two VPCs — every VM in VPC A can potentially reach every VM in VPC B. PSC is surgical: consumers get one internal IP that forwards to exactly one service. The producer's VPC topology stays hidden.

graph TD
    classDef consumer fill:#e67e22,stroke:#d35400,color:#fff
    classDef psc fill:#9b59b6,stroke:#8e44ad,color:#fff
    classDef producer fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef blocked fill:#e74c3c,stroke:#c0392b,color:#fff

    subgraph PROD["Producer VPC — your team"]
        ILB["Internal Load Balancer<br/>(forwards to your service)"]:::producer
        SA["Service Attachment<br/>(published via PSC)"]:::psc
        SVC["Your backend VMs / GKE pods"]:::producer
        ILB --> SVC
        ILB --> SA
    end

    subgraph CON_A["Consumer VPC A"]
        EP_A["PSC Endpoint<br/>internal IP: 10.1.0.50"]:::psc
        WL_A["Workload"]:::consumer
        WL_A -->|"connects to 10.1.0.50"| EP_A
        EP_A -->|"privately routed to Service Attachment"| SA
    end

    subgraph CON_B["Consumer VPC B (rejected)"]
        EP_B["PSC Endpoint<br/>(not approved)"]:::blocked
        EP_B -.->|"connection rejected — not in allowlist"| SA
    end

    NOTE["Producer never sees consumer VPC topology.<br/>Consumers never see producer VPC topology.<br/>No transitive routing — only the one service is reachable."]

How it works — three resources on the producer side:

1. Internal Load Balancer (ILB). Your service is fronted by an Internal TCP/UDP or Internal HTTP(S) LB. This is the actual network endpoint PSC will forward traffic to — PSC doesn't directly attach to VMs.
2. Service Attachment. You create a Service Attachment that references the ILB's forwarding rule. This is the PSC "publish" handle — it has a URI like projects/my-proj/regions/us-central1/serviceAttachments/my-svc that consumers reference.
3. Consumer accept list. The Service Attachment has an --consumer-accept-list (project IDs or service accounts) and a --connection-preference. Connections from unlisted projects are rejected — your VPC topology is never exposed even to the rejected project.
4. Consumer creates a PSC endpoint. An approved consumer creates a forwarding rule (PSC endpoint) with an internal IP of their choosing, pointed at your Service Attachment URI. From their VMs, the service looks like a local internal IP.
# --- PRODUCER SIDE ---

# Step 1: Note your ILB forwarding rule name
# (create the ILB first if you haven't)
FORWARDING_RULE="projects/my-proj/regions/us-central1/forwardingRules/my-ilb-fr"

# Step 2: Create a Service Attachment
gcloud compute service-attachments create my-service-attachment \
  --region=us-central1 \
  --producer-forwarding-rule=$FORWARDING_RULE \
  --connection-preference=ACCEPT_MANUAL \          # you explicitly approve each consumer
  --consumer-accept-list=consumer-project-id=10    # max 10 connections from this project

# Step 3: Share the Service Attachment URI with the consumer team
gcloud compute service-attachments describe my-service-attachment \
  --region=us-central1 \
  --format="value(selfLink)"
# → projects/my-proj/regions/us-central1/serviceAttachments/my-service-attachment

# Step 4: Approve a pending consumer connection
gcloud compute service-attachments update my-service-attachment \
  --region=us-central1 \
  --consumer-accept-list=consumer-project-id=10

# --- CONSUMER SIDE ---

# Step 5: Consumer creates a PSC endpoint pointed at the Service Attachment
gcloud compute forwarding-rules create my-psc-endpoint \
  --region=us-central1 \
  --network=consumer-vpc \
  --subnet=consumer-subnet \
  --address=10.1.0.50 \                            # internal IP consumer picks
  --target-service-attachment=projects/my-proj/regions/us-central1/serviceAttachments/my-service-attachment
VPC Peering PSC (producer publish)
What's exposed Full VPC — all IPs routable One service endpoint only
Consumer can see All VMs in the producer VPC Only the PSC internal IP
Transitivity No (non-transitive) No — and no need to be
Setup Mutual peering on both VPCs Service Attachment + consumer endpoint
Who controls access Firewall rules on both sides Producer's accept list is the gate
AWS analog VPC Peering PrivateLink (exactly equivalent)

Your team owns a payments service in its own VPC. Three other teams' VPCs need to call it privately. A colleague suggests VPC Peering with all three. What's the security argument for PSC instead?


All Files

Networking (this file)

VPC, subnets, firewall rules, Cloud NAT, VPC Peering, Shared VPC, Private Google Access, Private Service Connect

AWS Engineers Start Here

File Topics
from-aws.md Mental model shifts, resource hierarchy vs AWS Orgs, IAM additive model, global VPC, pricing quirks, CLI cheatsheet

Compute

File Topics
compute.md GCE vs EC2, machine families, custom machine types, disk types, Preemptible/Spot VMs, Managed vs Unmanaged Instance Groups (MIG/UIG), auto-healing, auto-scaling, rolling updates, serial ports 1–4, live migration, IAP SSH
gke.md GKE Standard vs Autopilot, Workload Identity, VPC-native networking, container-native LB (NEG), GPU node pools, upgrade strategy

Storage

File Topics
storage.md GCS vs S3 (storage classes, Autoclass, lifecycle, versioning), Persistent Disk, Local SSD, Filestore (NFS), Storage Transfer Service

Databases

File Topics
databases.md Cloud SQL (Postgres/MySQL), AlloyDB, Cloud Spanner (TrueTime), Firestore, Memorystore (Redis), database selection guide
bigquery.md Columnar storage, partitioning, clustering, slots, streaming vs batch load, external tables, time travel, cost optimization
bigtable.md Wide-column data model, row key design, LSM tree, replication, HBase API, monitoring

Application Services

File Topics
serverless.md Cloud Run (concurrency, traffic splitting, VPC, triggers), Cloud Functions Gen2, Cloud Run Jobs, Cloud Scheduler, Secret Manager
messaging.md Pub/Sub (topic/subscription, DLQ, Lite), Cloud Tasks (rate-limited queues), Eventarc (event routing), service selection guide

Operations

File Topics
observability.md Cloud Monitoring (metrics, alerting, uptime), Cloud Logging (LQL, sinks, retention), Cloud Trace, Cloud Audit Logs, Error Reporting, Profiler
cicd.md Cloud Build (cloudbuild.yaml, triggers, caching), Artifact Registry (Docker/Helm, scanning), Cloud Deploy (canary, approval gates), GHA integration

Solutions-Architect Deep Dives

File Topics
security-compliance.md VPC Service Controls, Cloud KMS/CMEK, Secret Manager, Binary Authorization, Security Command Center, IAM Deny policies, IAM Conditions, Org Policy constraints
reliability-dr.md RTO/RPO-driven design, Google's 4 DR patterns (backup-restore, pilot light, warm standby, multi-site active/active), multi-zone vs multi-region, multi-region failover walkthrough
cost-optimization.md CUD vs SUD, Recommender/Active Assist, BigQuery pricing (on-demand vs Editions), FinOps toolchain (budgets, billing export, labels), hidden network-egress costs
data-pipelines.md Dataflow (Apache Beam), Dataproc (managed Spark/Hadoop), Cloud Composer vs Workflows, Data Fusion, pipeline-tool decision guide
migration-methodology.md Migration strategy taxonomy, Migrate to Virtual Machines, Database Migration Service, BigQuery Migration Service, Storage Transfer Service/Transfer Appliance, Anthos hybrid migration
request-flow-glb-to-pod.md End-to-end request walkthrough: Cloud DNS → Global External LB → Cloud Armor → NEG → GKE pod, LB-type tradeoffs, traffic-not-reaching-pod debugging flowchart

Comparison & Scenarios

File Topics
gcp-vs-aws.md Service-by-service mapping, global VPC vs regional, BigQuery vs Redshift, GKE vs EKS, when to choose which
scenarios.md 10 debugging scenarios: Workload Identity 403, autoscaler not scaling, BigQuery cost spike, cold starts, Spanner hotspot, Pub/Sub backlog, GCS access denied, Cloud SQL connection exhaustion, AlloyDB/Cloud SQL failover DNS caching, Bigtable hot row key

Recommended Read Order

Coming from AWS:
  from-aws.md → README.md → services-overview.md → security-compliance.md
  → compute.md → gke.md → request-flow-glb-to-pod.md → storage.md
  → databases.md → bigquery.md → bigtable.md → data-pipelines.md
  → serverless.md → messaging.md → reliability-dr.md → cost-optimization.md
  → migration-methodology.md → observability.md → cicd.md
  → gcp-vs-aws.md → scenarios.md

GCP-first learner:
  README.md → services-overview.md → security-compliance.md → compute.md
  → gke.md → request-flow-glb-to-pod.md → storage.md → databases.md
  → data-pipelines.md → serverless.md → messaging.md → reliability-dr.md
  → cost-optimization.md → migration-methodology.md → observability.md
  → cicd.md → scenarios.md

Pages in this section