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.

40 min 2 hands-on tasksNetworking · 20% of the exam

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.

KindScopeOwned bySays
GatewayClassClusterInfrastructure provider"Gateways of this class are implemented by controller X" (spec.controllerName)
GatewayNamespaceCluster operator"Listen on these ports, protocols and hostnames, with these certificates, and accept routes from these namespaces"
HTTPRouteNamespaceApplication team"For these hostnames and paths, send traffic to these Services" (parentRefs picks the Gateway)
ReferenceGrantNamespaceOwner of the target namespace"Routes or Gateways in namespace A may reference my Services or Secrets"

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.

Terminal
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 | less

GatewayClass and Gateway

A class and a Gateway with two listeners
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 namespace

A 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

Hostname, path match and an 80/20 split
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 no matches gets PathPrefix /.
  • backendRefs: weight defaults to 1. A weight of 0 sends nothing to that backend. A Service in another namespace needs a ReferenceGrant in 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: "" and kind: Service on each backendRef, and group/kind on each parentRef.

Filters

Redirect, rewrite and header changes
  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: 80

RequestRedirect 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?

IngressGateway API equivalent
ingressClassName + IngressClassgatewayClassName + GatewayClass
The controller's Service and portsGateway.spec.listeners
spec.tls[].secretNamelisteners[].tls.certificateRefs on the Gateway
rules[].hostHTTPRoute.spec.hostnames
pathType: Prefix / Exactpath.type: PathPrefix / Exact
backend.service.port.numberbackendRefs[].port
Annotations (rewrite, redirect, canary)filters and weight in the spec

In the official docs· open on exam day too

Hands-on task 1 of 2nw-gw-1
~8 min6% weightNetworking

A Gateway with a weighted HTTPRoute

Create a GatewayClass named 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. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-1/cleanup | bash
Hands-on task 2 of 2nw-gw-2
~10 min7% weightNetworking

Migrate an Ingress to the Gateway API

The Ingress 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. 1

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-2/setup | bash
  2. 2

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/nw-gw-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 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.