Gateway API
The Ingress API was designed in 2015 for one use case: routing HTTP traffic. Ten years later, clusters handle gRPC, TCP, canary deployments, and multi-team traffic management — all through annotation hacks. Gateway API (GA in Kubernetes 1.31) is the official replacement: typed resources, role separation, and native support for all protocols.
Why Gateway API
The Ingress API has three structural problems that can't be fixed by adding fields:
- Only HTTP/HTTPS — no TCP, UDP, or gRPC support in the core spec
- Annotations escape hatch — every advanced feature (timeout, rate limit, rewrite, redirect) ends up as a controller-specific annotation string with no validation and no portability
- No role separation — both the infrastructure team (who manages the load balancer) and the app team (who writes routing rules) modify the same Ingress object
Gateway API solves all three by splitting responsibilities across three separate resource types, each owned by a different team.
The Three Resources
GatewayClass
A GatewayClass names a controller that knows how to provision Gateways. It's cluster-scoped — one instance per controller type. The controller implementation watches for Gateways that reference its class and provisions the underlying infrastructure.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: k8s.nginx.org/nginx-gateway-controller
Most cloud providers and popular controller projects (nginx, Istio, Envoy, Traefik, Cilium) already ship GatewayClass definitions. After installing the controller, the GatewayClass should appear in your cluster automatically.
Gateway
A Gateway declares one or more listeners — combinations of port, protocol, and optional hostname. The controller provisions a load balancer or configures an existing one to match these listeners.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
namespace: infra
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: example-tls # Secret in the same namespace
# restrict which namespaces can attach HTTPRoutes
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: allowed
The allowedRoutes field is a key multi-tenancy feature: the infra team controls which namespaces can attach routes to this Gateway. App teams can't "steal" capacity from a Gateway they aren't allowed to use.
HTTPRoute
HTTPRoute defines the routing rules that would previously live in an Ingress spec (host matching, path matching) plus features that previously required annotations (header matching, response modification, redirects).
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: production # app team's namespace
spec:
parentRefs:
- name: prod-gateway
namespace: infra # attach to the Gateway
sectionName: https # attach to the "https" listener only
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v1
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Version
value: "1"
backendRefs:
- name: api-v1
port: 8080
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: api-v2
port: 8080
Traffic Splitting
Traffic splitting with weights is a first-class feature — no annotations, no controller-specific YAML. Useful for canary deployments and A/B testing.
spec:
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api-stable # current production version
port: 8080
weight: 90 # 90% of traffic
- name: api-canary # new version under test
port: 8080
weight: 10 # 10% of traffic
Adjust the weights without redeploying the app — just update the HTTPRoute. Gradually shift from 10% to 50% to 100% as confidence builds, then remove the stable backend.
Other Route Types
Gateway API supports multiple route types for different protocols:
| Route type | Protocol | Status | Use case |
|---|---|---|---|
HTTPRoute | HTTP/HTTPS | GA (v1) | Web apps, REST APIs, redirects, header rewrites |
GRPCRoute | gRPC | GA (v1) | gRPC services, method-level routing |
TLSRoute | TLS (passthrough) | Experimental | Non-HTTP TLS traffic forwarded by SNI |
TCPRoute | Raw TCP | Experimental | Databases, custom protocols (no SNI) |
UDPRoute | UDP | Experimental | DNS, QUIC, game servers |
TLS with Gateway API
TLS configuration moves from Ingress annotations into the Gateway's listener spec. The Gateway references a TLS Secret for the certificate. Cert-manager already supports Gateway API — add the annotation to auto-provision certificates:
# Gateway listener (infra team)
listeners:
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: example-tls # Secret with tls.crt and tls.key
# cert-manager auto-provisions this Secret when you annotate the Gateway:
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
Migrating from Ingress
Ingress and Gateway API can coexist in the same cluster during migration. Most major controllers support both. The migration path:
- Install a controller that supports Gateway API (e.g. nginx-gateway-fabric, Istio, Envoy Gateway)
- Create a GatewayClass and Gateway to mirror your Ingress configuration
- Convert Ingress rules to HTTPRoute resources one namespace at a time
- Update DNS to point to the Gateway's IP once routes are verified
- Remove the old Ingress resources
Ingress is not deprecated and has no planned removal date. Migrate when you need features Gateway API provides natively (traffic splitting, gRPC routing, header manipulation, multi-team separation) — not just to be on the latest API.
kubectl Commands
# Install Gateway API CRDs (required before using any Gateway resources)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
# List GatewayClasses (cluster-scoped)
kubectl get gatewayclass
# List Gateways
kubectl get gateway -A
# Describe a Gateway (shows listeners, assigned addresses, conditions)
kubectl describe gateway prod-gateway -n infra
# List HTTPRoutes
kubectl get httproute -A
# Describe an HTTPRoute (shows rules, parent refs, backend status)
kubectl describe httproute api-route -n production
# Check if a route was accepted by the Gateway
kubectl get httproute api-route -n production \
-o jsonpath='{.status.parents[*].conditions}'