Kubernetes Gateway API: Ingress vs Gateway API
CKA prep • GatewayClass / Gateway / HTTPRoute, the role-oriented model, why it supersedes Ingress
Key terms
| Term | Meaning |
|---|---|
| Gateway API | The GA successor to Ingress for L4/L7 routing |
| GatewayClass | Cluster resource naming the controller implementation (like IngressClass) |
| Gateway | A request for an actual load balancer with listeners + ports |
| HTTPRoute | Host/path/header routing rules attached to a Gateway |
| Listener | A port + protocol + hostname on a Gateway |
| Route attachment | How a Route binds to a Gateway (via parentRefs + allowedRoutes) |
| Role-oriented | Separates infra/cluster/app personas into different resources |
Problem & solution
Ingress is simple but cramped: it only does HTTP host/path routing, and every real feature (header routing, traffic splitting, TLS modes, gRPC) lives in vendor-specific annotations that do not port between controllers. There is also no clean split between the platform team that runs the load balancer and the app team that owns routes. Gateway API is the GA, expressive, portable, and role-oriented replacement.
Solution: Model routing as typed resources (GatewayClass, Gateway, HTTPRoute) split by persona, expressing host/path/header/weight routing in the spec instead of annotations, with a standard API that is portable across implementations.
The analogy
A modern port gate is split by role: the port authority installs the gate and owns the hardware and lanes, choosing a gate brand to build it, while each tenant writes only their own routing slips that say which trucks go to which pier through that shared gate. No tenant touches the gate itself, and the slips point at the destination piers. Gateway API splits routing the same way: the GatewayClass names the implementation, the Gateway is the operator-owned load balancer with its listeners, each HTTPRoute is an app team's own routing rules, and backendRefs point at the Services behind them.
The three core resources and their owners
Three personas, three resources — no shared annotation blob.
GatewayClass (the implementation)
Installed by the controller (Envoy Gateway, NGINX Gateway Fabric, Istio, Cilium, cloud providers). Analogous to IngressClass / a StorageClass.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: example-gateway-class
spec:
controllerName: example.net/gateway-controller
Gateway (the load balancer + listeners)
A Gateway asks the chosen implementation for a real load balancer and declares the ports, protocols, and hostnames it listens on. This one opens an HTTP listener and a TLS-terminating HTTPS listener:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: web-gateway
namespace: infra
spec:
gatewayClassName: example-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All # which namespaces may attach routes
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: example-tls
HTTPRoute (the routing rules)
A route attaches to a Gateway via parentRefs and can do path, header, and
weighted backend routing directly in the spec.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
namespace: store
spec:
parentRefs:
- name: web-gateway
namespace: infra
hostnames:
- "shop.example.com"
rules:
- matches:
- path: { type: PathPrefix, value: /cart }
backendRefs:
- name: cart
port: 80
- matches:
- path: { type: PathPrefix, value: /api }
headers:
- name: x-canary
value: "true"
backendRefs:
- name: api-canary
port: 8080
weight: 20
- name: api
port: 8080
weight: 80
Ingress vs Gateway API
Gateway API was designed to fix Ingress's limits, so a direct comparison shows why it supersedes it:
+----------------------+--------------------------+---------------------------+ | | Ingress | Gateway API | +----------------------+--------------------------+---------------------------+ | scope | HTTP host/path only | HTTP, gRPC, TCP, TLS, UDP | | advanced routing | vendor annotations | header/weight in the spec | | portability | low (annotation lock-in) | high (standard API) | | personas | one resource | GatewayClass/Gateway/Route| | cross-namespace | awkward | first-class + ReferenceGrant| | status | stable but frozen | GA, actively extended | +----------------------+--------------------------+---------------------------+
Install and inspect
Gateway API ships as CRDs plus a controller; they are not built into core Kubernetes yet.
# install the standard-channel CRDs, then a controller of your choice
kubectl apply -f \
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
kubectl get gatewayclass
kubectl get gateway -A
kubectl get httproute -A
kubectl describe gateway web-gateway -n infra # check Programmed/Accepted conditions
Cross-namespace routing with ReferenceGrant
Routes in one namespace may target backends in another only if a
ReferenceGrant in the backend's namespace allows it — explicit, auditable
trust instead of annotation guesswork.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-store-to-payments
namespace: payments
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: store
to:
- group: ""
kind: Service
End-to-end: a request through the Gateway API
End-to-end example: one Gateway, two HTTPRoutes, host and path split
A full role-oriented setup: the platform team installs a GatewayClass and a TLS-terminating Gateway; two app teams attach HTTPRoutes that split traffic by host and by path to different backend Services.
- Platform: install the CRDs, then define the GatewayClass and Gateway:
kubectl apply -f \
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
kubectl create namespace infra
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: prod-class
spec:
controllerName: example.net/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: web-gateway
namespace: infra
spec:
gatewayClassName: prod-class
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: example-tls
allowedRoutes:
namespaces:
from: All
- App team A: an HTTPRoute for
shop.example.comthat splits by path:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
namespace: store
spec:
parentRefs:
- name: web-gateway
namespace: infra
hostnames:
- "shop.example.com"
rules:
- matches:
- path: { type: PathPrefix, value: /cart }
backendRefs:
- name: cart
port: 80
- matches:
- path: { type: PathPrefix, value: /checkout }
backendRefs:
- name: checkout
port: 80
- App team B: a second HTTPRoute on a different host,
api.example.com:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
namespace: backend
spec:
parentRefs:
- name: web-gateway
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path: { type: PathPrefix, value: / }
backendRefs:
- name: api
port: 8080
- Apply everything and confirm the Gateway is Programmed and both routes Accepted:
kubectl apply -f gatewayclass.yaml -f gateway.yaml -f shop-route.yaml -f api-route.yaml
kubectl -n infra get gateway web-gateway \
-o jsonpath='{.status.conditions[?(@.type=="Programmed")].status}{"\n"}' # True
kubectl -n store get httproute shop \
-o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}{"\n"}' # True
kubectl -n backend get httproute api \
-o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}{"\n"}' # True
- Test both hosts against the Gateway address without changing DNS:
GW=$(kubectl -n infra get gateway web-gateway -o jsonpath='{.status.addresses[0].value}')
curl -k --resolve shop.example.com:443:$GW https://shop.example.com/cart
# expected: response from the cart Service
curl -k --resolve api.example.com:443:$GW https://api.example.com/v1/health
# expected: response from the api Service
Common pitfalls
These are the most common Gateway API mistakes and what each symptom points to:
- CRDs not installed -> kind Gateway/HTTPRoute unknown to the API
- GatewayClass has no ctrl -> Gateway stays not Programmed (nothing implements it)
- route won't attach -> allowedRoutes.namespaces or parentRefs mismatch
- cross-namespace backend -> missing ReferenceGrant in the backend namespace
- expecting Ingress annot. -> use spec fields (matches/weight), not annotations
- wrong apiVersion -> v1 for Gateway/HTTPRoute, v1beta1 for ReferenceGrant
Key takeaways
- Gateway API is the GA successor to Ingress: richer, portable, role-oriented.
- Three resources: GatewayClass (impl), Gateway (LB+listeners), HTTPRoute (rules).
- Advanced routing (headers, weighted splits, TLS modes) lives in the spec, not annotations.
- It ships as CRDs + a controller, not in core Kubernetes (yet).
- allowedRoutes + ReferenceGrant make cross-namespace routing explicit and safe.
- Check Accepted/Programmed conditions to confirm a Gateway is wired up.
Checklist
- [ ] Installed the Gateway API CRDs and a controller
- [ ] Created a GatewayClass and a Gateway with HTTP + HTTPS listeners
- [ ] Wrote an HTTPRoute with path + header matches and a weighted split
- [ ] Allowed a route from another namespace with a ReferenceGrant
- [ ] Contrasted Ingress annotations vs Gateway API spec fields
- [ ] Verified
Programmed/Acceptedstatus conditions