Gateway API
The successor to Ingress, and why it exists.
Ingress isn't dead. It's frozen. The Kubernetes community stopped adding features to it years ago, and that one fact is the whole reason Gateway API exists. Here's what Ingress does, in plain terms. Your app runs inside Pods (a Pod is the smallest unit Kubernetes runs, basically a wrapper around one running container), and because Pods come and go, Kubernetes puts a stable name in front of a group of them called a Service. Ingress takes a web address, a hostname and path like app.example.com/checkout, and points it at that Service over HTTP, the everyday protocol behind web pages. It does that one job cleanly. The trouble starts the moment you want anything more. Match on a request header. Send 10% of traffic to a new build as a test. Carry raw TCP or UDP (the low-level protocols underneath most network traffic) instead of web requests. Let two teams share one entry point without stepping on each other's config. For any of those, Ingress makes you paste in vendor-specific annotations, scribbled margin-notes on the config that only one company's software understands and that stop working the day you move to another cluster.
The cracks Ingress couldn't patch
Three problems, and they stack up. First, Ingress is built HTTP-shaped, so anything that isn't a web request always felt bolted on: raw TCP, UDP, or gRPC (a fast way for small back-end services to call each other, the plumbing inside modern apps). Second, every interesting feature hides inside an annotation like nginx.ingress.kubernetes.io/canary-weight. An annotation is just a labeled note stapled to the object, and that particular note means nothing to a different vendor's software, so your config gets welded to one product. Move clusters, and you rewrite everything. Third, and this one bites big teams hardest: a single Ingress object smashes together two jobs that belong to two different people. The person who owns the load balancer (the LB, the internet-facing box that holds the public IP address and the TLS certificates, TLS being the lock behind HTTPS) and the person who just wants /checkout sent to their Pod are both editing the same file. One fat-fingered TLS block, and routing breaks for every app sitting behind that shared front door.
Think of an office building. Old way: any team that wanted visitors sent up to their floor had to walk down and reconfigure the front door themselves. New way: the building owner installs and owns the front door, and each team just drops a labeled note in the mailroom, "my visitors go to floor 3." Nobody touches the door except the owner. That clean split is the whole point of Gateway API.
Three objects, three owners
Gateway API takes that one overloaded Ingress object and splits it into a chain, and each link has an obvious owner. GatewayClass is the template for a kind of gateway, published by whoever wrote the software that powers it (a cloud provider, or a project like Cilium, NGINX, or Istio). You almost never create a GatewayClass. You pick one off the shelf. Gateway is the live listener, the actual front door, with an IP address, ports, and certificates, and the cluster operators (the platform team) own it. Routes are where an app team says "send this traffic to that Service": an HTTPRoute for web traffic, a GRPCRoute for service-to-service calls, a TCPRoute for raw connections. App teams own their Routes and attach each one to a Gateway by naming it. None of this works until someone installs the objects that teach Kubernetes these new kinds. Those are CRDs, Custom Resource Definitions, and installing them extends the API server (the cluster's single front desk that every kubectl command talks to) with vocabulary it didn't ship with. Apply the standard-channel set with kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml, then look at what controller your cluster actually offers.
kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGEnginx gateway.nginx.org/nginx-gateway-controller True 6m
The Gateway: the front door
The cluster operator creates the Gateway inside an infrastructure namespace (a namespace is just a folder that walls off one team's objects from everyone else's). Now the part that trips people up: creating the Gateway object does nothing by itself. Writing it down is like filing a building permit. The paper sitting in the drawer doesn't pour any concrete. Somewhere in the cluster a controller is watching, and a controller is a program that runs in a loop, notices new requests, and does the real work to make the world match what you asked for. It's the contractor who reads your permit and actually builds. This one sees the new Gateway, provisions a real load balancer or configures a proxy, then writes the result back onto the object as status conditions, small stamped verdicts like "done" or "blocked." The condition you watch is Programmed. When Programmed flips to True and an ADDRESS shows up, the data plane (the part that actually carries your users' traffic) is genuinely live. Look at the allowedRoutes block below. It decides which namespaces are allowed to attach Routes to this listener. The field is tiny and it quietly decides whether the whole thing works at all.
apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata:name: prod-gwnamespace: infraspec:gatewayClassName: nginxlisteners:- name: webprotocol: HTTPport: 80allowedRoutes:namespaces:from: Selectorselector:matchLabels:gateway-access: "true"
$ kubectl get gateway prod-gw -n infraNAME CLASS ADDRESS PROGRAMMED AGEprod-gw nginx 203.0.113.10 True 90s
The Route: app teams attach their own
Now the app team, over in their own namespace, writes an HTTPRoute and points its parentRefs at the Gateway. The word parentRef just means "the Gateway I want to hang off of." The controller runs its loop again and asks two plain questions. Will a listener on that Gateway actually accept this Route? And does the backend Service this Route names really exist? It writes both answers back as conditions, which work like the tracking on a parcel. Accepted: True is "the depot took your package." ResolvedRefs: True is "the delivery address is real." Get both greens and traffic flows. Those two conditions are almost your entire debugging surface, so get comfortable reading them with kubectl describe.
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata:name: webnamespace: team-webspec:parentRefs:- name: prod-gwnamespace: infrahostnames:- app.example.comrules:- matches:- path:type: PathPrefixvalue: /backendRefs:- name: webport: 80
$ kubectl describe httproute web -n team-web...Status:Parents:Controller Name: gateway.nginx.org/nginx-gateway-controllerParent Ref:Group: gateway.networking.k8s.ioKind: GatewayName: prod-gwNamespace: infraConditions:Type: AcceptedStatus: TrueReason: AcceptedMessage: The route is acceptedType: ResolvedRefsStatus: TrueReason: ResolvedRefsMessage: All references are resolved
When a Route misbehaves, don't guess. Read those two conditions. Accepted: False with reason NotAllowedByListeners means the Gateway listener refused to attach it, usually a namespace permission problem. Accepted: False with NoMatchingListenerHostname means your hostname doesn't line up with the listener. ResolvedRefs: False with BackendNotFound means you typed the Service name or port wrong. ResolvedRefs: False with RefNotPermitted means the backend lives in another namespace and you never granted permission (that's what a ReferenceGrant object is for, an explicit "yes, this namespace may send traffic to my Service").
Without a GatewayClass implementation, CRDs are inert. Same lesson as Ingress controllers.
Migration is gradual. Many clusters run Ingress and Gateway side by side during the move.
Policy attachment and timeouts become first-class instead of vendor annotations. Read the route status conditions.
Try this
List GatewayClass and Gateway objects if the API is installed. Compare the separation of Gateway (infra) and HTTPRoute (app) to a classic Ingress annotation pile.
$ kubectl get gatewayclass$ kubectl get gateway prod-gw -n infraNAME CLASS ADDRESS PROGRAMMED AGEprod-gw nginx 203.0.113.10 True 90s$ kubectl describe httproute web -n team-web...Status:Parents:Controller Name: gateway.nginx.org/nginx-gateway-controllerParent Ref:Group: gateway.networking.k8s.ioKind: GatewayName: prod-gwNamespace: infraConditions:Type: AcceptedStatus: TrueReason: AcceptedMessage: The route is acceptedType: ResolvedRefsStatus: TrueReason: ResolvedRefsMessage: All references are resolved
Takeaway
Gateway API splits infrastructure and application routing roles. It is the successor direction to Ingress for portable, expressive HTTP and more.