The Gateway API
Why the Gateway API splits ingress into GatewayClass, Gateway and HTTPRoute, how listeners and routes attach, and how to write matches, filters and weighted traffic splits.
What you will be able to do
- Explain which team owns GatewayClass, Gateway and HTTPRoute, and how a route attaches to a listener.
- Write an HTTPRoute with hostnames, path matches, weighted backendRefs and filters, and check it with kubectl.
- Translate an Ingress into a Gateway plus HTTPRoute, including TLS termination.
Use the Gateway API to manage Ingress traffic joined the curriculum in 2025, and it now appears as a task in most sittings: create a Gateway and an HTTPRoute, split traffic, or migrate an existing Ingress. It is all YAML. There is no kubectl create gateway, so what you need is a clear mental model of the three objects and a fast way to find their fields.
Why it exists
Ingress puts everything in one object: the entry point, TLS and every route. Anything beyond host and path matching (header matches, redirects, traffic splitting) ended up in controller-specific annotations that did not port from nginx to Traefik to a cloud load balancer. The Gateway API (gateway.networking.k8s.io/v1, a set of CRDs maintained by SIG Network) splits that object along team lines and puts the advanced features into the spec itself.
Other route kinds exist (GRPCRoute, TLSRoute, TCPRoute, UDPRoute), but HTTPRoute is what the exam uses. Like Ingress, none of this routes a single packet without a controller (Envoy Gateway, Istio, Cilium, NGINX Gateway Fabric, Traefik, a cloud provider). This lab installs the standard CRDs but no controller: your objects are validated and stored, their status stays at Waiting for controller, and no traffic flows. On the exam, a controller is installed and the grader can send real requests.
kubectl get crd | grep gateway.networking.k8s.io
kubectl api-resources --api-group=gateway.networking.k8s.io
kubectl get gatewayclass # which controllers exist (exam: usually one)
kubectl get gateway,httproute -A
# The CRDs carry full schemas, so kubectl explain works offline and is faster than the docs
kubectl explain gateway.spec.listeners
kubectl explain httproute.spec.rules.backendRefs
kubectl explain httproute.spec.rules.filters --recursive | lessGatewayClass and Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nw-edge
spec:
controllerName: example.com/gateway-controller # must match the installed controller; immutable
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: edge
namespace: nw-gw
spec:
gatewayClassName: nw-edge
listeners:
- name: http # routes can target one listener with parentRefs[].sectionName
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Same # the default; All, or Selector with a label selector
- name: https
protocol: HTTPS
port: 443
hostname: "store.nw.local" # optional; routes must share a hostname with the listener
tls:
mode: Terminate # the default for HTTPS
certificateRefs:
- kind: Secret
name: store-tls # kubernetes.io/tls Secret in the Gateway's namespaceA listener is one port, protocol and (optionally) hostname combination. allowedRoutes is the operator's guard rail: with the default from: Same, only routes in the Gateway's namespace can attach. To let app teams in other namespaces attach, use from: All or from: Selector with a namespace label.
HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: store
namespace: nw-gw
spec:
parentRefs:
- name: edge # the Gateway; add sectionName: http to pin one listener
hostnames:
- store.nw.local
rules:
- matches:
- path:
type: PathPrefix # Exact, PathPrefix, or RegularExpression (controller-dependent)
value: /store
backendRefs:
- name: store-v1 # a Service in the route's namespace
port: 80 # Service port; required for Services
weight: 80
- name: store-v2
port: 80
weight: 20 # weights are relative: 4 and 1 would split the same way- matches: entries in the list are ORed; the fields inside one entry (
path,headers,queryParams,method) are ANDed. A rule with nomatchesgetsPathPrefix /. - backendRefs:
weightdefaults to 1. A weight of 0 sends nothing to that backend. A Service in another namespace needs aReferenceGrantin that namespace. - Precedence: across rules, the most specific match wins: an Exact path before a prefix, a longer prefix before a shorter one, then a method match, then more header matches, then more query parameter matches.
- Defaults you will see when you read the object back:
group: ""andkind: Serviceon each backendRef, andgroup/kindon each parentRef.
Filters
rules:
- matches:
- path: { type: PathPrefix, value: /old-shop }
filters:
- type: RequestRedirect # answers the client itself; no backendRefs allowed in this rule
requestRedirect:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /store
statusCode: 301
- matches:
- path: { type: PathPrefix, value: /api }
filters:
- type: URLRewrite # /api/orders reaches the backend as /orders
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: X-Env
value: lab
remove: ["X-Debug"]
- type: ResponseHeaderModifier
responseHeaderModifier:
add:
- name: Cache-Control
value: no-store
backendRefs:
- name: store-api
port: 80RequestRedirect can also change scheme (the usual HTTP-to-HTTPS redirect, on a route attached to the port-80 listener), hostname and port. RequestMirror copies traffic to a second backend and ignores its responses. Filters can sit on a rule or on a single backendRef.
Ingress or Gateway API?
In the official docs· open on exam day too
- Gateway API (kubernetes.io concept page)search: gateway api
- HTTPRoute guidesearch: (study only) http routing
- Traffic splittingsearch: (study only) traffic splitting
- Migrating from Ingresssearch: (study only) migrating from ingress
A Gateway with a weighted HTTPRoute
nw-edge whose controller name is example.com/gateway-controller. In namespace nw-gw, create a Gateway named edge of that class with one listener named http for protocol HTTP on port 80 that accepts routes from its own namespace only. Then create an HTTPRoute named store attached to edge for hostname store.nw.local. Requests whose path starts with /store must be split so that 80% go to Service store-v1 and 20% to Service store-v2, both on port 80. (The Services exist; their Pods and a Gateway controller are not needed for this task.)Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/cleanup | bash
Migrate an Ingress to the Gateway API
legacy in namespace nw-gw2 must be replaced by Gateway API objects. Using the existing GatewayClass nw-lab, create a Gateway named legacy-gw in nw-gw2 with a listener named https on port 443 using protocol HTTPS for hostname legacy.nw.local, terminating TLS with the Secret the Ingress uses. Then create an HTTPRoute named legacy attached to that Gateway that reproduces the Ingress's host and path routing exactly. Leave the Ingress in place.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-2/cleanup | bash
Finished this lesson?
0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.