GKE Networking#

GKE networking centers on VPC-native clusters, where pods and services get IP addresses from VPC subnet ranges. This integrates Kubernetes networking directly into Google Cloud’s VPC, enabling native routing, firewall rules, and load balancing without extra overlays.

VPC-Native Clusters and Alias IP Ranges#

VPC-native clusters use alias IP ranges on the subnet. You allocate two secondary ranges: one for pods, one for services.

# Create subnet with secondary ranges
gcloud compute networks subnets create gke-subnet \
  --network my-vpc \
  --region us-central1 \
  --range 10.0.0.0/20 \
  --secondary-range pods=10.4.0.0/14,services=10.8.0.0/20

# Create cluster using those ranges
gcloud container clusters create my-cluster \
  --region us-central1 \
  --network my-vpc \
  --subnetwork gke-subnet \
  --cluster-secondary-range-name pods \
  --services-secondary-range-name services \
  --enable-ip-alias

The pod range needs to be large. A /14 gives about 262,000 pod IPs. Each node reserves a /24 from the pod range (256 IPs, 110 usable pods per node). If you have 100 nodes, that consumes 100 /24 blocks. Undersizing the pod range is a common cause of IP exhaustion – the cluster cannot add nodes even though VMs are available.

Shared VPC#

In multi-project setups, Shared VPC lets a host project own the VPC while service projects run GKE clusters in it. This centralizes network management (firewall rules, subnets, Cloud NAT) in one project.

# In the host project, share the VPC
gcloud compute shared-vpc enable host-project

# Associate the service project
gcloud compute shared-vpc associated-projects add service-project \
  --host-project host-project

The GKE service account in the service project needs compute.networkUser on the shared subnet and container.hostServiceAgentUser on the host project. Missing IAM bindings are the number one cause of Shared VPC cluster creation failures.

GKE Gateway API#

Gateway API is the successor to Ingress in GKE. It provides multi-cluster routing, traffic splitting, and header-based matching that Ingress cannot do. Google recommends Gateway API for all new deployments.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: external-gateway
  namespace: infra
  annotations:
    networking.gke.io/certmap: my-cert-map
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        options:
          networking.gke.io/cert-manager-certs: wildcard-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
  namespace: production
spec:
  parentRefs:
    - name: external-gateway
      namespace: infra
  hostnames:
    - "app.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-frontend
          port: 80

GKE Gateway classes:

  • gke-l7-global-external-managed – Global external HTTP(S) load balancer (Envoy-based)
  • gke-l7-regional-external-managed – Regional external HTTP(S) load balancer
  • gke-l7-rilb – Internal HTTP(S) load balancer
  • gke-l7-gxlb – Classic global external (legacy)

GKE Ingress (Legacy)#

If you are using the built-in GKE Ingress controller (not nginx), it provisions a Google Cloud Load Balancer:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    kubernetes.io/ingress.class: "gce"
    kubernetes.io/ingress.global-static-ip-name: "my-static-ip"
    networking.gke.io/managed-certificates: "my-managed-cert"
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /*
            pathType: ImplementationSpecific
            backend:
              service:
                name: web-frontend
                port:
                  number: 80

Note the /* path – GKE Ingress uses ImplementationSpecific path matching, not standard Prefix. This is a common source of confusion when migrating from nginx ingress.

Container-Native Load Balancing with NEGs#

By default, GKE load balancers route to node IPs, which then kube-proxy forwards to pods. Network Endpoint Groups (NEGs) bypass this by routing directly to pod IPs, reducing an extra hop and improving latency.

NEGs are enabled automatically when using Gateway API. For Ingress, annotate the Service:

apiVersion: v1
kind: Service
metadata:
  name: web-frontend
  annotations:
    cloud.google.com/neg: '{"ingress": true}'
spec:
  type: ClusterIP
  selector:
    app: web-frontend
  ports:
    - port: 80
      targetPort: 8080

With NEGs, the load balancer health checks pods directly. Your pods must respond to health checks on the serving port. If they do not, the NEG marks all endpoints as unhealthy and the service returns 502s.

Cloud NAT for Private Clusters#

Private cluster nodes have no external IPs, so they cannot reach the internet (for pulling images from Docker Hub, for example) without Cloud NAT:

# Create a Cloud Router
gcloud compute routers create my-router \
  --network my-vpc \
  --region us-central1

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

This allows all subnets (including pod and service ranges) to reach external addresses. For tighter control, use --nat-custom-subnet-ip-ranges to specify only the GKE subnet.

Cloud Armor WAF Integration#

Cloud Armor applies WAF rules at the load balancer level, before traffic reaches your cluster. Create a security policy and attach it via BackendConfig (Ingress) or GCPBackendPolicy (Gateway API):

# For Gateway API
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
  name: app-backend-policy
  namespace: production
spec:
  default:
    securityPolicy: my-armor-policy
  targetRef:
    group: ""
    kind: Service
    name: web-frontend
# Create the Cloud Armor policy
gcloud compute security-policies create my-armor-policy

# Add OWASP ModSecurity rules
gcloud compute security-policies rules create 1000 \
  --security-policy my-armor-policy \
  --expression "evaluatePreconfiguredExpr('sqli-v33-stable')" \
  --action deny-403

# Rate limiting
gcloud compute security-policies rules create 2000 \
  --security-policy my-armor-policy \
  --src-ip-ranges="*" \
  --action throttle \
  --rate-limit-threshold-count 100 \
  --rate-limit-threshold-interval-sec 60 \
  --conform-action allow \
  --exceed-action deny-429

Managed SSL Certificates#

Google-managed certificates automate TLS provisioning and renewal:

apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
  name: my-managed-cert
spec:
  domains:
    - app.example.com
    - api.example.com

Reference it in your Ingress annotation: networking.gke.io/managed-certificates: my-managed-cert. The certificate provisions automatically once DNS points to the load balancer IP. Provisioning takes 15-60 minutes. Check status with kubectl describe managedcertificate my-managed-cert.