GCP Services Overview
IAM, load balancers, DNS, and compute orchestration — GCP building blocks every backend engineer works with.
IAM (Identity and Access Management)
GCP IAM controls who (member) can do what (role/permission) on which resource. The model is member + role → resource.
Core Concepts
Members (Identities):
user:— Google account (human)serviceAccount:— machine identity for apps/VMs/GKE podsgroup:— Google Group (collection of users)allUsers/allAuthenticatedUsers— public access (use carefully)
Roles (what they can do):
- Primitive —
roles/owner,roles/editor,roles/viewer— broad, avoid in prod - Predefined — e.g.
roles/storage.objectViewer,roles/container.developer— fine-grained, Google-managed - Custom — you define exact permissions, e.g.
compute.instances.getonly
Policy binding: Attaches a member to a role on a resource.
{
"bindings": [
{
"role": "roles/storage.objectViewer",
"members": ["serviceAccount:my-app@my-project.iam.gserviceaccount.com"]
}
]
}
Resource Hierarchy
graph TD
classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef binding fill:#8e44ad,stroke:#6c3483,color:#fff
subgraph L0["Organization node"]
ORG["Organization: domain.com<br/>binding: roles/viewer for group:all-eng"]:::gcp
end
subgraph L1["Folders — inherit every Org-level binding"]
FOLDER_A["Folder: Production"]:::orange
FOLDER_B["Folder: Dev"]:::blue
end
subgraph L2["Projects — inherit Org + Folder bindings"]
PROJ1["Project: prod-backend"]:::green
PROJ2["Project: prod-data"]:::green
PROJ3["Project: dev-backend"]:::blue
end
subgraph L3["Resources — effective policy = union of every level above"]
RES["Resources: GCE, GCS, GKE, etc."]:::gcp
end
ORG -->|"inherits down"| FOLDER_A
ORG -->|"inherits down"| FOLDER_B
FOLDER_A -->|"inherits down"| PROJ1
FOLDER_A -->|"inherits down"| PROJ2
FOLDER_B -->|"inherits down"| PROJ3
PROJ1 -->|"inherits down"| RES
NEWBINDING["New binding added directly<br/>on prod-backend project"]:::binding -.->|"additive only — cannot revoke<br/>what Org/Folder already granted"| PROJ1
IAM policies inherit downward — a binding at the Organization level applies to all folders, projects, and resources within it. Allow bindings are additive; an Allow binding at a lower level can't revoke what's allowed higher up. The dashed edge above shows why: a narrower binding added directly on prod-backend layers on top of everything inherited from Org and Folder — it can only add access, never take away what a higher level already granted. GCP does have a separate override for exactly this gap — IAM Deny policies, which always win over any Allow regardless of hierarchy level — see security-compliance.md for how they work.
AWS parallel: AWS doesn't have a native Organization → Folder → Account hierarchy at the IAM policy level in the same way. GCP's hierarchy maps loosely to AWS Organizations + SCPs.
Service Accounts
Service accounts are both an identity (for IAM bindings) and a credential source (for apps). A GCE VM or GKE pod runs as a service account automatically.
# Create a service account
gcloud iam service-accounts create my-app-sa \
--display-name="My App Service Account"
# Grant it a role on a specific resource
gcloud storage buckets add-iam-policy-binding gs://my-bucket \
--member="serviceAccount:my-app-sa@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
Workload Identity (for GKE)
GKE pods authenticate to GCP APIs as a service account without any key files — using Workload Identity. A Kubernetes Service Account is bound to a GCP Service Account, and the actual token exchange happens transparently underneath whatever client library the app is already using:
sequenceDiagram
participant Pod as App code in Pod, uses ADC
participant Meta as GKE metadata server
participant STS as GCP Security Token Service
participant IAMChk as IAM policy check
participant API as Target GCP API, e.g. Cloud Storage
Pod->>Meta: Request credentials via Application Default Credentials
Meta-->>Pod: Kubernetes-signed OIDC ID token, subject = KSA k8s-sa-name
Pod->>STS: Exchange ID token for a federated access token
STS->>IAMChk: Does this KSA hold roles/iam.workloadIdentityUser on the target GSA
IAMChk-->>STS: Binding found, allow impersonation
STS-->>Pod: Short-lived OAuth2 access token, scoped as the GSA
Pod->>API: Call API with the GSA access token
API-->>Pod: Response, authorized by the GSA's IAM roles
Walking through the same exchange one step at a time:
GOOGLE_APPLICATION_CREDENTIALS env var pointing at a downloaded JSON secret.
namespace/k8s-sa-name), not a long-lived secret.
sts.googleapis.com), asking to trade it for a real GCP access token.
roles/iam.workloadIdentityUser binding that maps exactly this KSA to a target GCP Service Account (project.svc.id.goog[namespace/k8s-sa-name]). No binding, no token — this is the only place trust is actually established.
# Bind K8s SA to GCP SA
gcloud iam service-accounts add-iam-policy-binding my-app-sa@project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:project.svc.id.goog[namespace/k8s-sa-name]"
AWS parallel: AWS IRSA (IAM Roles for Service Accounts) via OIDC token exchange. GCP Workload Identity is GCP's equivalent — both eliminate the need for credential files in pods.
IAM: GCP vs AWS
| GCP IAM | AWS IAM | |
|---|---|---|
| Model | member + role → resource | principal + policy (identity or resource-based) |
| Deny rules | Allow bindings: additive only, no deny. Separate IAM Deny policies always override Allow (see security-compliance.md) | Yes — explicit Deny overrides Allow |
| Resource hierarchy | Org → Folder → Project → Resource | Account → Resource (no Folder concept) |
| Cross-account | Workload Identity Federation | AssumeRole via STS |
| Machine identity | Service Account (vm/pod runs as SA) | IAM Role (EC2 instance profile, IRSA) |
| Policy attachment | Binding on resource | Policy attached to identity or resource |
A team lead argues: "let's add an IAM Deny policy at the prod-backend project level to block the intern's overly broad Editor role, which was granted at the Org level." Does this actually work?
security-compliance.md for the full mechanics.Cloud Load Balancing
GCP load balancers are globally distributed using Google's anycast network — a single IP serves traffic from the nearest PoP worldwide. This is fundamentally different from AWS where ALBs are regional.
graph TD
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
classDef security fill:#e74c3c,stroke:#c0392b,color:#fff
CLIENT["Client, anywhere in the world"]:::blue -->|"1. HTTPS to the single anycast IP"| ANYCAST
subgraph Global["Global External Application Load Balancer (Layer 7)"]
ANYCAST["Global Anycast IP: 34.x.x.x<br/>advertised from every Google PoP"]:::gcp
ARMOR["Cloud Armor<br/>WAF + DDoS rules evaluated first"]:::security
FWD["Forwarding Rule (Frontend)"]:::orange
PROXY["Target HTTPS Proxy<br/>terminates TLS with Google-managed cert"]:::blue
URLMAP["URL Map<br/>routes on host + path"]:::teal
BS1["Backend Service: api-backend<br/>health checks + Cloud CDN cache"]:::green
BS2["Backend Service: web-backend<br/>health checks + Cloud CDN cache"]:::green
NEG1["NEG / Instance Group (us-central1)"]:::orange
NEG2["NEG / Instance Group (europe-west1)"]:::orange
ANYCAST --> ARMOR -->|"2. allow/deny decision"| FWD --> PROXY -->|"3. decrypted request"| URLMAP
URLMAP -->|"4a. host: api.example.com"| BS1
URLMAP -->|"4b. host: app.example.com"| BS2
BS1 -->|"5. route to nearest healthy backend"| NEG1
BS1 -.->|"failover if us-central1 unhealthy"| NEG2
end
Load Balancer Types
| Type | Layer | Scope | SSL | Use case |
|---|---|---|---|---|
| Global Ext App LB | L7 HTTP/HTTPS | Global | Terminates | Web apps, APIs, CDN offload |
| Regional Ext App LB | L7 HTTP/HTTPS | Regional | Terminates | Regional isolation, VPC Service Controls |
| Internal App LB | L7 HTTP/HTTPS | Regional | Terminates | Service mesh, internal microservices |
| Ext Proxy NLB | L4 TCP/SSL | Global | Pass-through/terminate | TCP apps, gaming, global static IPs |
| Internal Passthrough NLB | L4 TCP/UDP | Regional | None | Internal TCP/UDP services |
| Ext Passthrough NLB | L4 TCP/UDP | Regional | None | Legacy regional TCP/UDP |
Backend Types
GCP load balancers support multiple backend types:
| Backend | Description | AWS analog |
|---|---|---|
| Instance Group (Managed/Unmanaged) | GCE VMs in a group | Auto Scaling Group |
| NEG — Zonal | Specific GCE VM IPs/ports | IP target type |
| NEG — Serverless | Cloud Run / App Engine | Lambda target |
| NEG — Internet | External endpoint (outside GCP) | Weighted target group |
| NEG — Private Service Connect | PSC endpoint | PrivateLink endpoint |
GCP ALB vs AWS ALB
| GCP Global App LB | AWS ALB | |
|---|---|---|
| Scope | Global anycast, single IP | Regional (multi-AZ within one region) |
| Routing | Host/path/header | Host/path/header |
| SSL termination | Yes | Yes |
| Backend | Instance Groups, NEGs, Cloud Run | EC2, ECS/EKS (IP target), Lambda |
| WAF | Google Cloud Armor | AWS WAF |
| CDN | Cloud CDN integrated | CloudFront (separate) |
| Cost | Per forwarding rule + data | Per LCU + data |
Cloud Armor (WAF)
# Create a security policy
gcloud compute security-policies create my-waf-policy
# Block a CIDR range
gcloud compute security-policies rules create 1000 \
--security-policy=my-waf-policy \
--src-ip-ranges="1.2.3.0/24" \
--action=deny-403
# Attach to backend service
gcloud compute backend-services update my-backend \
--security-policy=my-waf-policy \
--global
A GCP Global External Application LB and an AWS ALB both need to serve HTTPS traffic to users worldwide with low latency. Why does the GCP setup need only one static IP address, while the AWS setup typically needs a separate ALB (and IP) per region?
Cloud DNS
Cloud DNS is a managed authoritative DNS service — equivalent to AWS Route 53 (private and public zones).
Zone Types
| Zone | Purpose |
|---|---|
| Public zone | Serves DNS queries from the internet |
| Private zone | Serves DNS only from specified VPCs |
| Forwarding zone | Forwards queries for a domain to external nameservers (on-prem DNS) |
| Peering zone | Delegates a zone from one VPC to another VPC's DNS |
# Create a public zone
gcloud dns managed-zones create my-zone \
--dns-name="example.com." \
--visibility=public \
--description="Production zone"
# Add an A record
gcloud dns record-sets create api.example.com. \
--zone=my-zone \
--type=A \
--ttl=300 \
--rrdatas="34.1.2.3"
DNS Record Types
| Type | Purpose |
|---|---|
A |
Domain → IPv4 |
AAAA |
Domain → IPv6 |
CNAME |
Alias to another domain (not at apex) |
MX |
Mail server |
TXT |
SPF, DKIM, verification |
SRV |
Service location |
Note: GCP Cloud DNS does NOT have an ALIAS record type like AWS Route 53. For apex domains pointing to a GCP load balancer, use an A record with the load balancer's static IP. (GCP's Global LB gives you a static anycast IP — so you can use a plain A record at the apex, unlike AWS where the ALB has a dynamic IP requiring ALIAS.)
Private DNS for GKE
# Private zone scoped to your VPC
gcloud dns managed-zones create internal-zone \
--dns-name="internal.example.com." \
--visibility=private \
--networks=my-vpc
GCP Cloud DNS vs AWS Route 53
| GCP Cloud DNS | AWS Route 53 | |
|---|---|---|
| Routing policies | Weighted, Geo, Failover (via routing policies) | Weighted, Latency, Geolocation, Failover, Multi-value |
| Health checks | Via uptime checks integrated separately | Native, tightly coupled to routing |
| Apex domain | A record (LB has static IP) | ALIAS record (LB has dynamic DNS) |
| Private DNS | Private zones per VPC | Private hosted zones per VPC |
| DNS forwarding | Forwarding zones (to on-prem) | Resolver endpoints (Route 53 Resolver) |
| Cost | $0.20/zone/month + queries | $0.50/zone/month + queries |
Why can an apex domain (example.com, no subdomain) point straight at a GCP load balancer with a plain A record, when the equivalent AWS setup needs Route 53's special ALIAS record type instead of a plain A record?
Cloud Run vs GKE
GCP's serverless container service and managed Kubernetes — the GCP parallel to AWS ECS Fargate vs EKS.
graph TD
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
REQ["Incoming HTTPS request / event"]:::gcp
subgraph CloudRun["Cloud Run — Serverless Containers"]
CR_REQ["Handles HTTPS, gRPC,<br/>Pub/Sub push, WebSockets"]:::orange
CR_SCALE["Auto-scales 0 → N → 0<br/>per-request billing"]:::teal
CR_DEPLOY["Deploy container image<br/>no Dockerfile changes needed"]:::gcp
CR_INFRA["No VM or node management<br/>Google owns the control plane"]:::blue
CR_REQ --> CR_SCALE --> CR_DEPLOY --> CR_INFRA
end
subgraph GKE["GKE — Google Kubernetes Engine"]
GKE_CP["Autopilot or Standard mode"]:::gcp
GKE_NODE["Node pools (GCE VMs)<br/>Autopilot: Google-managed<br/>Standard: you manage"]:::orange
GKE_POD["Pods, Deployments, StatefulSets,<br/>DaemonSets (Standard only)"]:::green
GKE_MESH["Anthos/Cloud Service Mesh,<br/>ArgoCD, Istio"]:::blue
GKE_CP --> GKE_NODE --> GKE_POD --> GKE_MESH
end
REQ -->|"stateless HTTP/event workload"| CR_REQ
REQ -.->|"needs Kubernetes API surface"| GKE_CP
Cloud Run Deep Dive
- Deploy a container image, Cloud Run handles infrastructure entirely
- Scales to zero — no requests = no cost (unlike ECS which keeps tasks running)
- Concurrency model: each container instance handles multiple concurrent requests (configurable, default 80)
- Supports HTTP, gRPC, WebSockets, Pub/Sub push subscriptions
- Cloud Run Jobs — for batch/one-shot workloads (parallel to ECS Tasks with
--launch-type=FARGATE)
gcloud run deploy my-service \
--image=gcr.io/my-project/my-app:latest \
--region=us-central1 \
--platform=managed \
--allow-unauthenticated \
--min-instances=0 \
--max-instances=100
GKE Modes
| Mode | Description |
|---|---|
| Autopilot | Google manages nodes, node pools, scaling. You only define Pods. Billed per pod resource request. Recommended default. |
| Standard | You manage node pools. Full control over machine types, taints, GPUs, custom OS images. |
# GKE Autopilot cluster (no node management)
gcloud container clusters create-auto my-cluster \
--region=us-central1
# GKE Standard with a node pool
gcloud container clusters create my-cluster \
--zone=us-central1-a \
--machine-type=n2-standard-4 \
--num-nodes=3
GKE Autopilot vs Standard
| Autopilot | Standard | |
|---|---|---|
| Node management | Google-managed | You manage |
| Billing | Per pod (CPU/memory requested) | Per node (VM cost regardless of usage) |
| DaemonSets | No (Autopilot runs Google-managed DS only) | Yes |
| Node access (SSH) | No | Yes |
| GPU nodes | Limited | Yes |
| Best for | Most workloads, lower ops overhead | Custom hardware, DaemonSets, tuning |
Cloud Run vs GKE vs AWS
| Cloud Run | GKE Autopilot | GKE Standard | AWS ECS Fargate | AWS EKS | |
|---|---|---|---|---|---|
| Scale to zero | Yes | No | No | No | No |
| Kubernetes API | No | Yes | Yes | No | Yes |
| Node management | None | None | Full control | None | Partial (managed node groups) |
| DaemonSets | No | No | Yes | No | Yes |
| Concurrency/request model | Per request, concurrent | Per pod | Per pod | Per task | Per pod |
| Cold start | Yes (~100ms-2s) | No | No | No | No |
| Best for | Stateless HTTP/event workloads | K8s without node ops | Full platform engineering | Simple containers, AWS-native | Microservices, platform eng |
A team runs GKE Standard with 10 n2-standard-4 nodes, but their pods only use about 20% of that capacity most of the day. Does switching to GKE Autopilot fix the waste the same way switching to Cloud Run would?
TLS and mTLS on GCP
TLS Termination at Cloud Load Balancer
The Global External Application LB terminates TLS at Google's edge PoPs worldwide — closer to users than a regional ALB.
sequenceDiagram
participant Client as External client, London
participant GFE as Google Edge PoP, Frankfurt
participant Backend as Backend, GCE/GKE/Cloud Run, us-central1
Client->>GFE: TLS 1.3 handshake to the anycast IP
GFE-->>Client: Google-managed certificate, TLS terminated here
Client->>GFE: HTTPS request over the now-decrypted connection
GFE->>Backend: Forward over HTTP/2, encrypted on Google's private backbone
Backend-->>GFE: Response
GFE-->>Client: HTTPS response, re-encrypted for the last hop
Google-managed certificates auto-renew with no manual action:
gcloud compute ssl-certificates create my-cert \
--domains=api.example.com \
--global
# GCP auto-provisions and renews via Let's Encrypt/Google CA
Anthos Service Mesh / Cloud Service Mesh (mTLS)
GCP's managed Istio service mesh. Provides mTLS between services inside GKE with automatic certificate rotation — equivalent to AWS App Mesh or self-managed Istio on EKS.
graph LR
classDef blue fill:#3498db,stroke:#2980b9,color:#fff
classDef orange fill:#e67e22,stroke:#d35400,color:#fff
classDef teal fill:#1abc9c,stroke:#16a085,color:#fff
classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
classDef purple fill:#8e44ad,stroke:#6c3483,color:#fff
CLIENT["External client"]:::blue -->|"HTTPS — terminated at edge"| GLB["Global LB (GFE)"]:::gcp
subgraph CP["Cloud Service Mesh control plane — Google-managed, no Istiod to run"]
CAS["Certificate Authority Service<br/>issues + auto-rotates SPIFFE certs"]:::purple
end
subgraph CLUSTER["GKE cluster"]
subgraph PODA["payments pod"]
APPA["payments container"]:::orange
SIDECAR_A["Envoy sidecar"]:::orange
end
subgraph PODB["db-proxy pod"]
APPB["db-proxy container"]:::teal
SIDECAR_B["Envoy sidecar"]:::teal
end
end
GLB -->|"HTTP/2 on Google backbone"| SIDECAR_A
SIDECAR_A -->|"local, in-pod"| APPA
SIDECAR_A -->|"mTLS — mutual SPIFFE identity check"| SIDECAR_B
SIDECAR_B -->|"local, in-pod"| APPB
CAS -.->|"issues cert"| SIDECAR_A
CAS -.->|"issues cert"| SIDECAR_B
Key GCP advantage: Cloud Service Mesh manages the Istio control plane — no Istiod to operate. SPIFFE certs are rotated automatically via Google Certificate Authority Service.
Cloud Service Mesh gives you mTLS between GKE services with automatic certificate rotation. What's the concrete operational difference from running self-managed Istio on GKE (or Istio on EKS) to get that same mTLS behavior?