Lesson  in  Kubernetes 101

Gateway API

The evolution of Ingress: meet the role separation of the Gateway API and publish your application with an HTTPRoute on a Gateway that the platform team has already left ready.

The Ingress from the previous lesson works, but it has been showing its seams for years. Everything that goes beyond "host and path toward a Service" (timeouts, rewrites, traffic splits) ends up in proprietary annotations of each controller, so an Ingress written for Traefik is no good for nginx. And there is a deeper problem: it is a single object that mixes infrastructure decisions (ports, TLS, domains) with application decisions (my routes), two responsibilities of different teams.

The Gateway API is Kubernetes's official answer, and its big idea is to separate the roles into three resources:

  • The GatewayClass is defined by the provider: which technology implements the Gateways (here, Traefik).
  • The Gateway is managed by the platform team: which ports listen, with what TLS, and who may attach.
  • The HTTPRoute is written by each development team: the routes of its application.

This lesson respects that division: the platform team (the lab's initialization task) has already done its part, and you play the role of the developer publishing their application. Work from the dev-machine tab.

Step 1: Inspect what the platform team left for you

Before touching anything, survey the terrain:

kubectl get gatewayclass
kubectl get gateway -A
kubectl describe gateway gateway -n plataforma

Notice the -A and the -n: the Gateway is not where you work. You are in tienda; the Gateway lives in plataforma, which is the Namespace of the team that operates the traffic entry point. That separation is not a whim of this lab: it is the reason this API exists.

From the describe, two things: the Programmed: True condition (the Gateway is not just an object, there is a real proxy behind it) and the Listeners section. This is the Gateway they have prepared for you:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: plataforma
spec:
  gatewayClassName: traefik
  listeners:
  - name: http
    protocol: HTTP
    port: 8000
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            expose: "true"

The Gateway YAML, explained in questions and answers

You did not write it, but it pays to know how to read it: it is the contract the platform team offers you.

Another new API group?

Yes, gateway.networking.k8s.io/v1, and with a particularity: these resources do not ship with Kubernetes, they are installed as CRDs (custom resource definitions). That is why the initialization of this lesson installed them first. It is the standard mechanism by which Kubernetes is extended.

What role does gatewayClassName play?

The same pattern you already saw with StorageClasses: it picks the implementation. Here traefik, because we have enabled the Gateway API support of the Traefik that k3s ships out of the box.

Why does the listener declare port 8000 if we will later call port 80?

Because the listener is declared against the internal port of Traefik's web entrypoint in k3s (8000), which the cluster publishes to the outside as 80. It is a detail of this particular implementation; what matters is the concept: the Gateway declares listening ports, it does not invent them.

What does allowedRoutes control?

The listener's admission policy: which routes may attach to it, and it is half of the handshake between platform and development. There are three values. Same only admits routes from the same Namespace, which in this setup would admit none because nobody deploys applications in plataforma. All opens the door to the whole cluster. And Selector, the one used here, admits the Namespaces carrying a specific label.

And who sets that label?

The platform team, not you. Check it:

kubectl get ns tienda --show-labels

There is the expose=true. That is the whole contract in one label: the platform team decides which Namespaces may publish traffic, and from there each team manages its own routes without asking permission or touching anything in plataforma. With an Ingress this could not be expressed: anyone who knew the name of the IngressClass could publish whatever they wanted.

Step 2: Your application

The usual, and now with no guidance: create a Deployment web (1 replica of ghcr.io/iximiuz/labs/nginx:alpine, label app: web, port 80) and a ClusterIP Service web that exposes it on port 80. They go in tienda, which is where the context already leaves you: no need to write -n.

Step 3: The HTTPRoute, your side of the contract

Create the file httproute.yaml:

cat << 'EOF' > httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: tienda-route
spec:
  parentRefs:
  - name: gateway
    namespace: plataforma
  hostnames:
  - tienda.local
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web
      port: 80
EOF

The YAML, explained in questions and answers

What is parentRefs?

The hook: this route asks to attach to the Gateway gateway. It is the other half of the handshake: you request, and the Gateway accepts or rejects according to its allowedRoutes. The result is written in the route's status, not left up in the air.

Why does it carry namespace: plataforma?

Because without it, parentRefs looks for the Gateway in the route's own Namespace, that is, in tienda, where there is none. Remove it and apply: the route gets created all the same, but its status will say it has found no parent. It is a very typical silent failure when starting out with the Gateway API, and it is always diagnosed in the same place, the route's status.

A surprising detail: isn't a ReferenceGrant needed to cross Namespaces?

Not for this. The ReferenceGrant is needed when what crosses is a backendRef, that is, when a route wants to send traffic to a Service in another Namespace, and there the owner of the destination does have to authorize it. To attach to someone else's Gateway the authorization is already expressed in allowedRoutes, and that is why the label is enough.

How does a rule differ from the Ingress ones?

In structure and in power. Each rule has matches (conditions) and backendRefs (destinations), both plural. The matches can combine path, headers, method and query params, all typed in the spec, with no proprietary annotations.

Why would I want several backendRefs?

To split traffic by weight: two backends with weight: 90 and weight: 10 are a canary release declared in five lines, something that with Ingress required controller-specific annotations. We do not use it here, but it is the perfect example of why this API exists.

Is PathPrefix the same as the Ingress's pathType: Prefix?

The same idea with the new schema's own name. The familiarity is no accident: the Gateway API was designed so that the mental migration from Ingress would be direct.

Apply it and look at something the Ingress never gave you, a status with an opinion:

kubectl apply -f httproute.yaml
kubectl describe httproute tienda-route
kubectl get httproute tienda-route -o jsonpath='{.status.parents[0].conditions}' | jq

Look for the Accepted: True condition in the Status section. The Gateway has reviewed your request and admitted it.

Step 4: The test

Just like with the Ingress, simulate DNS with the Host header:

curl -H "Host: tienda.local" http://cplane-01/

The nginx page, served this time by the chain Gateway, HTTPRoute, Service, Pod.

The tienda.local tab does the same from a browser: same port 80 of cplane-01, with the Host: tienda.local header set by the platform. Notice that the curl and the tab go through the same chain: the Gateway in the plataforma namespace and your HTTPRoute in the tienda namespace.

Note

💡 Should I abandon the Ingress already? No rush: the Ingress is frozen but supported, and it will stay in production for years. The practical rule: new projects with serious routing needs, on the Gateway API; what exists and works, left alone. Knowing how to read both is what makes you useful today.

Summary

  • The Gateway API separates what the Ingress mixed: GatewayClass (provider), Gateway (platform) and HTTPRoute (development).
  • The advanced capabilities (rich matches, weights, headers) are part of the schema, not proprietary annotations.
  • Routes attach to Gateways with parentRefs, and the result of the handshake is audited in the status.
  • The resources arrive as CRDs: you just saw Kubernetes's extension mechanism in action, and it will show up again as soon as you touch operators.
Previous lesson
Ingress
Next lesson
NetworkPolicy