Kubernetes Networking & Storage

The Gateway API

18 min Lesson 4 of 31

The Gateway API

Kubernetes Ingress was designed in 2015 for a narrow use case: route HTTP/HTTPS traffic to backend Services. Over the years it accumulated so many vendor-specific annotations — NGINX, AWS ALB, Traefik, Istio — that a single Ingress manifest was rarely portable between clusters. The Kubernetes Gateway API (GA in Kubernetes 1.28, June 2023) is the standards-body answer: a role-aware, expressive, extension-first API for managing traffic at every layer.

Gateway API vs Ingress: Ingress is a single resource controlled by a single team. Gateway API separates concerns across three distinct role-oriented resources: GatewayClass, Gateway, and HTTPRoute (plus TLSRoute, TCPRoute, GRPCRoute). This separation directly maps to how platform, network, and application teams own different layers of infra at big-tech companies.

The Three-Layer Role Model

Gateway API encodes an organisational model that mirrors real infrastructure ownership:

  • GatewayClass — owned by the infrastructure provider (cloud team or cluster admin). Declares which controller implements the Gateway (e.g., Envoy Gateway, Cilium, AWS Load Balancer Controller). Think of it as the driver.
  • Gateway — owned by the platform/network team. Binds to a GatewayClass and defines listeners (ports, protocols, TLS certificates). One Gateway can serve many applications.
  • HTTPRoute / GRPCRoute / TLSRoute — owned by the application team. Attaches to a Gateway, defines fine-grained routing rules (path, header, query, method matching; traffic weighting; request modification).
Gateway API three-layer role model Infra Provider Platform Team App Team GatewayClass controller: envoy-gateway implements Gateway listener: HTTPS :443 tls: my-cert-secret attaches attaches HTTPRoute (api-team) path: /api/* backend: api-svc:8080 HTTPRoute (web-team) path: /* host: app.io backend: web-svc:3000 Internet Client HTTPS Request
Gateway API separates infra-provider, platform-team, and app-team concerns into three distinct resource types.

Installing a Gateway Controller (Envoy Gateway)

Gateway API CRDs are not installed by default — you install the CRDs once, then a controller (implementation) separately. Envoy Gateway is the CNCF-maintained reference implementation backed by Envoy Proxy.

# Install the Gateway API CRDs (Standard channel — includes HTTPRoute, GRPCRoute, TLSRoute) kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml # Install Envoy Gateway controller (v1.1) helm install eg oci://docker.io/envoyproxy/gateway-helm \ --version v1.1.0 \ --namespace envoy-gateway-system \ --create-namespace # Verify CRDs are available kubectl get crd gateways.gateway.networking.k8s.io \ httproutes.gateway.networking.k8s.io \ gatewayclasses.gateway.networking.k8s.io # Verify the controller pod is running kubectl get pods -n envoy-gateway-system

Defining a GatewayClass and Gateway

The platform team creates the GatewayClass and Gateway once. These are cluster-scoped or namespace-scoped resources that application teams attach routes to — they do not touch the Gateway manifest.

# --- GatewayClass (cluster-scoped, created by infra team) --- apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: envoy spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- # --- Gateway (namespace: infra, created by platform team) --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: prod-gateway namespace: infra spec: gatewayClassName: envoy listeners: - name: https port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - kind: Secret name: prod-tls-cert namespace: infra allowedRoutes: namespaces: from: Selector selector: matchLabels: gateway-access: "true" # only namespaces with this label can attach

Writing an HTTPRoute (Application Team)

An HTTPRoute lives in the application namespace and attaches to the shared Gateway. It supports exact, prefix, and regex path matching; header/query matching; traffic weighting for canary; and request/response modification — all without annotations.

# --- HTTPRoute for the api-service (namespace: api-team) --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: api-route namespace: api-team spec: parentRefs: - name: prod-gateway namespace: infra # cross-namespace ref; Gateway must allowedRoutes this NS sectionName: https # binds to the specific listener by name hostnames: - "api.example.com" rules: - matches: - path: type: PathPrefix value: /v2 headers: - name: X-Version value: "2" backendRefs: - name: api-svc-v2 port: 8080 weight: 90 # 90% of matching traffic to v2 - name: api-svc-v1 port: 8080 weight: 10 # 10% canary bleed-back to v1 - matches: - path: type: PathPrefix value: /v1 filters: - type: RequestHeaderModifier requestHeaderModifier: add: - name: X-Deprecated value: "true" backendRefs: - name: api-svc-v1 port: 8080

Traffic Weighting & Canary Without Annotations

The weight field in backendRefs replaces the sprawl of nginx.ingress.kubernetes.io/canary-weight annotations with a first-class, controller-agnostic construct. Big-tech progressive delivery pipelines (Flagger, Argo Rollouts) drive this field programmatically to shift traffic 5% at a time based on metric gates, then promote or roll back automatically.

Production tip: Use ReferenceGrant when your HTTPRoute is in a different namespace from the Gateway. Without it the attachment silently fails and the route stays detached — check kubectl get httproute -n api-team -o yaml and look at .status.parents[].conditions for Accepted: False with reason NotAllowedByParent.

GRPCRoute and TLSRoute

The Gateway API is protocol-aware. For gRPC microservices, use a GRPCRoute which can match on service name and method name — critical for routing at the gRPC level without HTTP path hacks. For raw TLS passthrough (databases, MQTT brokers), TLSRoute routes by SNI without terminating the TLS session.

Production Failure Modes

  • Route not attached: The most common issue. Check kubectl get gateway prod-gateway -n infra -o yaml — the .status.listeners[].attachedRoutes count tells you how many routes are bound. Zero usually means a namespace selector mismatch or a missing ReferenceGrant.
  • TLS certificate not found: If the Secret referenced in certificateRefs is in a different namespace, Envoy Gateway needs a ReferenceGrant in that namespace too — same cross-namespace trust model as routes.
  • Controller not reconciling: Gateway API controllers watch specific GatewayClass.spec.controllerName. If you install two Gateway controllers (e.g., Nginx and Envoy), only the one matching the name reconciles the Gateway. The other ignores it.
  • CRD version skew: Running a v1.1 controller against v1.0 CRDs (or vice versa) causes silent reconciliation failures. Always match controller and CRD versions in CI before promoting to production.
Do not mix Ingress and HTTPRoute for the same hostname. Both Ingress controllers and Gateway API controllers may install separate load balancers. Running both for app.example.com creates two external IPs fighting over the same DNS record, leading to intermittent 502s and unpredictable routing that is very hard to debug at 3am.

Observability: Reading Route Status

Every Gateway API resource exposes structured .status conditions. Automate health checks in your CD pipeline by reading these — a route that deploys without error but never attaches is a silent failure that Ingress would never surface.

# Check Gateway listener status and attached route count kubectl get gateway prod-gateway -n infra -o jsonpath='{.status.listeners[*]}' | jq . # Check HTTPRoute attachment conditions kubectl get httproute api-route -n api-team \ -o jsonpath='{.status.parents[*].conditions}' | jq . # Expected healthy output for a properly attached route: # [ # { "type": "Accepted", "status": "True", "reason": "Accepted" }, # { "type": "ResolvedRefs", "status": "True", "reason": "ResolvedRefs" } # ] # If Accepted=False, check reason: NotAllowedByParent means namespace policy # If ResolvedRefs=False, the backend Service or TLS Secret cannot be resolved

Gateway API is the future of Kubernetes ingress — Ingress will not receive new features. If you are building a new cluster today, start with Gateway API. If you are migrating, most controllers (NGINX, Traefik, Istio) now ship Gateway API support alongside Ingress; you can run both during transition and switch DNS when stable.