Migrating from Kubernetes Ingress to Gateway API

7 min read
KubernetesGateway APINetworkingIngressGCP

The Kubernetes documentation for Ingress now carries a note that changes the long-term plan for every cluster terminating HTTP traffic: the project recommends using Gateway instead of Ingress, and states that the Ingress API has been frozen. Nothing stops working today. Ingress is still a stable API and the controllers in your clusters keep serving it. But the direction of travel is settled, and platform teams that wired their routing strategy to Ingress annotations now own a migration they never scheduled.

This guide is for the platform engineers and leads who have to plan that work: what the freeze does and does not mean, how the Gateway API model splits responsibility across teams, how a typical Ingress translates into a Gateway plus HTTPRoute, and a cutover sequence that keeps traffic flowing. The security baseline does not change; the TLS posture in our ingress and TLS hardening guide still applies, only the resource enforcing it does.

What a frozen Ingress API actually means

Frozen is narrower than deprecated. Ingress remains a documented, stable API, listed as stable since Kubernetes v1.19, and every major controller still reconciles it. What a freeze removes is the option to fix the real limitation: the Ingress specification has run out of room for the routing features teams keep asking for. Instead of extending it, the project points to Gateway API, an official Kubernetes project focused on L4 and L7 routing and described by its maintainers as the next generation of the Ingress, load balancing, and service mesh APIs.

The annotation tax

Because Ingress covers only the basics, everything else lives in controller-specific annotations. Header-based matching, traffic weighting, and CORS are the canonical example: capabilities that Gateway API resources expose as typed fields and that Ingress can only express through custom annotations. That works in one cluster with one controller; then it becomes the expensive part of your platform. Annotations cannot be validated against a schema, they are invisible to a reviewer who does not know the controller by heart, and they do not survive a controller replacement.

Portability is the cost teams underestimate. Google Cloud's documentation notes that Gateway API was designed to be portable across many implementations, with a portable core API and implementation-specific extensions layered on top. Annotation-driven Ingress config is the opposite: the deeper you go into advanced behavior, the more your manifests describe one vendor's proxy rather than Kubernetes routing intent.

The resource model splits ownership

Gateway API is not one object. It is three layers, and the split is the point. A GatewayClass is a cluster-scoped template naming the controller that implements it. A Gateway is an instance of traffic-handling infrastructure, such as a load balancer, whose listeners declare protocol, port, and hostname. Routes, HTTPRoute and GRPCRoute, hold the protocol-specific rules mapping requests from a listener to backend Services. In GKE, the Gateway resource is what causes a load balancer to be created.

Attachment is a two-way agreement rather than a reference. A route names its parent Gateway, and the Gateway's listeners decide which namespaces may attach through allowedRoutes; the Kubernetes documentation describes this as a bidirectional trust model. That design choice is what makes safe sharing possible: the platform team owns the Gateway, its listeners, certificates, and allowed namespaces, while product teams own HTTPRoutes in their own namespaces and change routing for their services without touching shared infrastructure.

Gateway API frames this as a role-oriented model built around the infrastructure provider, cluster operator, and application developer. In practice it turns one shared Ingress object, which every team edits and nobody owns, into a boundary that matches your access model: if a route breaks, the blast radius is one namespace; if a listener or certificate breaks, it is a platform incident with a platform owner.

Translating an Ingress into Gateway API resources

The conversion is mechanical once you see the mapping. The Kubernetes documentation is explicit that Gateway API does not include the Ingress kind, so this is a one-time conversion rather than an in-place upgrade. For a typical host-based Ingress with TLS, the first three pieces map directly.

  • ingressClassName becomes the GatewayClass referenced by your Gateway, which the platform team pins to a specific implementation.
  • The host and TLS secret become a listener on the Gateway: protocol HTTPS, port 443, hostname, and a certificate reference.
  • Path rules and their Service backends become an HTTPRoute attached to that Gateway, matching on host and path with backendRefs pointing at the same Services.

The part nobody budgets for

  • The default backend becomes an explicit catch-all HTTPRoute, or is dropped so unmatched traffic returns a 404 instead of silently landing somewhere.
  • Rewrite, redirect, timeout, and retry annotations become typed route filters, or an implementation policy resource where the behavior is vendor-specific.
  • Several teams sharing one load balancer become several HTTPRoutes in their own namespaces attaching to one shared Gateway.

The YAML translation takes an afternoon. Auditing annotations takes longer, because each one is either a standard capability that now has a typed field, an implementation-specific behavior that needs a policy resource, or an accident nobody wants to admit to. GKE's model, for example, includes policy resources describing implementation-specific characteristics attached to a Gateway, a route, or a Service. Classify every annotation against your implementation's documentation before scheduling the cutover; anything you cannot classify is a reason to slow down.

A staged cutover plan

Run the migration as a series of reversible steps, one hostname at a time. The sequence below has held up on estates with dozens of Ingress objects behind a handful of load balancers.

  1. Inventory every Ingress and group by controller, hostname, TLS secret, and annotation family. This is the scope of the project, not a formality.
  2. Choose one low-risk hostname. Create the Gateway with a listener for that hostname only, so the blast radius is a single route set.
  3. Author the HTTPRoute alongside the existing Ingress. Apply both manifests; only one of them is receiving traffic.

Prove the move before deleting anything

  1. Shift DNS or the frontend address to the Gateway in a low-traffic window, watching error rate, latency, and 5xx counts rather than only checking that the page loads.
  2. Compare status codes and backend latency between the old and new paths for a defined observation window before touching anything else.
  3. Remove the Ingress only after the rollback path has been exercised and documented, then repeat with the next hostname.

Keep each old Ingress in version control as an unreferenced fallback for a full business cycle. Deleting a manifest during a rollback is an incident; deleting it a sprint later is cleanup.

Verify routing, not just applied manifests

Manifests that apply are not manifests that route. A Gateway can be accepted but not programmed, and an HTTPRoute can be accepted by one parent and rejected by another. Check the status conditions on both resources, then verify from the outside: a TLS chain check against the new address, an HTTP-to-HTTPS redirect test, a request with an unexpected Host header, and a load test at expected peak before the observation window closes.

Network policy still has to hold when the front door changes. Confirm that backends are unreachable except through the new gateway path, and that broad allow rules written for the old ingress controller no longer apply. Our guide to zero-trust network policies covers the default-deny baseline that belongs in every namespace behind the gateway, and the zero-downtime upgrade discipline is the same one that keeps the cutover reversible.

What belongs on the roadmap

Two things make this worth scheduling rather than deferring indefinitely. First, Gateway API is where routing features land; Ingress is where configuration decays. Second, the same model now covers service mesh: the GAMMA initiative for mesh support has been part of the Standard Channel of Gateway API since v1.1.0 and is considered generally available, so one route vocabulary can describe ingress and mesh traffic instead of two. Teams that build the muscle now avoid a second migration later.

The migration itself is not the hard part. The hard part is deciding what your current annotations actually do, who owns the shared Gateway, and how you prove a hostname moved safely. If your platform team wants a second pair of eyes before committing to that cutover, Secpros can review your ingress estate, map it to Gateway API resources, and return a short prioritized plan for the migration and the security gaps it exposes.

/ author

Pawel Bedynski

DevOps Engineer & Kubernetes Consultant. Building cloud-native infrastructure on GCP since 2019. 80+ production clusters deployed.

LinkedIn