Kubernetes Security & Hardening›08 · Gateway API

Lesson 08 of 15 · Traffic Security

Gateway API

The successor to Ingress: GatewayClass, Gateway and HTTPRoute with a clean split between platform and app teams, TLS on listeners, safe cross-namespace attachment, and weighted traffic splitting.

Advanced
Key wordsGateway APIGatewayClassGatewayHTTPRouteReferenceGrantallowedRoutesrole separationtraffic splitting

Why a new API

Ingress mixed everyone's concerns into one object, and pushed anything beyond basic HTTP routing into controller-specific annotations. Gateway API splits responsibilities across objects and standardises the features that used to be annotations: traffic splitting, header matching, TLS options, TCP/UDP/gRPC routes.

Think of an airport. The airport authority decides which terminal building exists (GatewayClass). The terminal manager opens gates, sets up security checks, and decides which airlines may use which gates (Gateway). Each airline decides its own flights and destinations (HTTPRoute). Nobody needs to edit anyone else's paperwork.

The roles and objects

Object Owner Contains
GatewayClass Infrastructure provider Which controller implements it (Envoy Gateway, Cilium, Istio, Traefik, a cloud LB…)
Gateway Platform / cluster operators Listeners: port, protocol, hostname, TLS certificates, which namespaces may attach
HTTPRoute (and GRPCRoute, TLSRoute…) App teams Matches (host, path, headers) → backends, with weights and filters
ReferenceGrant Owner of the referenced namespace Opt-in for cross-namespace references

Install the CRDs from the Gateway API releases (standard channel), then an implementation. Most implementations document the exact version they support:

$ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/<version>/standard-install.yaml

A shared Gateway, owned by the platform

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: infra
spec:
  gatewayClassName: envoy-gateway          # from your implementation
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-com-tls # a cert-manager Certificate in 'infra'
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: public       # only namespaces with this label may attach

A Route, owned by the app team

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
  namespace: shop                          # labelled gateway-access=public
spec:
  parentRefs:
    - name: public
      namespace: infra
      sectionName: https
  hostnames: [ "shop.example.com" ]
  rules:
    - matches:
        - path: { type: PathPrefix, value: /api }
      backendRefs:
        - name: api
          port: 8080
    - backendRefs:
        - name: web
          port: 80

The app team never touches the certificate or the listener; the platform team never touches the app's paths.

Traffic splitting (canary releases)

  rules:
    - backendRefs:
        - name: web-v1
          port: 80
          weight: 90
        - name: web-v2
          port: 80
          weight: 10

Shift weights gradually (manually, or with a progressive delivery tool) and roll back instantly by moving the weight back.

Debug with status

$ kubectl -n shop describe httproute shop
Status:
  Parents:
    Conditions:
      Type:    Accepted
      Status:  False
      Reason:  NotAllowedByListeners

NotAllowedByListeners → the shop namespace lacks the gateway-access=public label the Gateway requires. Gateway API resources report why in their status, which is much friendlier than silent annotations.

Try it: Gateway API on kind

  1. Install the Gateway API CRDs and an implementation (for example Envoy Gateway, following its quickstart; it also creates a GatewayClass).
  2. Create the infra namespace, a cert-manager Certificate for *.example.com from your private CA, and the public Gateway.
  3. Deploy web in shop, apply the HTTPRoute, and read its status. Fix the NotAllowedByListeners condition by labelling the namespace.
  4. Port-forward to the gateway's Service and test with curl -k --resolve shop.example.com:8443:127.0.0.1 https://shop.example.com:8443/.
  5. Deploy web-v2 and split traffic 90/10; count responses over 100 requests.

Going deeper: Gateway API in platforms

  • Implementations differ in supported features; each publishes conformance results. Check them before relying on a feature.
  • Policy attachment (timeouts, retries, backend TLS, rate limits) is evolving; some policies are standard, others implementation-specific CRDs.
  • Service meshes (Istio, Linkerd, Cilium) use Gateway API for east-west traffic as well (the GAMMA initiative), so one API covers both edge and in-cluster routing.
  • Migrating from Ingress: tools such as ingress2gateway translate Ingress objects to Gateway API resources as a starting point.

Recap

  • Gateway API = GatewayClass (infra) → Gateway (platform: listeners, TLS, allowed namespaces) → Routes (apps).
  • allowedRoutes and ReferenceGrant make cross-namespace access explicit and safe.
  • Weighted backendRefs give standard traffic splitting.
  • Read status conditions (Accepted, ResolvedRefs) to debug.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.