OnCallReady

Lesson 34.21 · Kubernetes: Ingress, Gateway API & Service Mesh · 8 min read

Migrating from Ingress to Gateway API

In plain words

Imagine moving a shop to a new building on the same street. You do not close the old shop, empty it and hope customers find the new one. You set up the new shop completely, test it with staff, then change the sign at the end of the street to point to the new address, and keep the old one ready for a few days in case something was forgotten.

Moving from Ingress to Gateway API works the same way. ingress2gateway reads your Ingresses and prints Gateway and HTTPRoute objects (with warnings on stderr for what it cannot translate), you review and apply them next to the old controller, the new Gateway gets its own address, you test it there, and the cutover is one DNS change, which can be reverted.

Why now, and the plan

With ingress-nginx retired (no security fixes since March 2026), every team running it has the same project: move to a Gateway API implementation without an outage. The API move is the easy part; the annotations are the work.

What you need to know already: the ingress-nginx lessons of this chapter (annotations, canaries, rewrites), the two Gateway API lessons before this one, external-dns, cert-manager.

The map

ingress-nginx annotation            Gateway API
host + path rules                   HTTPRoute hostnames + matches
pathType Exact / Prefix             path type Exact / PathPrefix
use-regex                           path type RegularExpression (implementation-specific)
rewrite-target: /new                URLRewrite ReplacePrefixMatch / ReplaceFullPath
rewrite-target: /$2                 (no equivalent - capture groups)
ssl-redirect (308)                  RequestRedirect on the port-80 listener
canary + canary-weight              weights on backendRefs
canary-by-header                    a rule with a headers match
proxy-read-timeout                  rules[].timeouts.request
backend-protocol: HTTPS             BackendTLSPolicy (standard since 1.4)
cert-manager.io/cluster-issuer      the same annotation on the Gateway
allowlist-source-range, auth-url,   implementation policies (Envoy Gateway SecurityPolicy,
limit-rps, affinity, snippets       Istio AuthorizationPolicy, ...) or nothing

ingress2gateway

The Kubernetes project's converter (1.0 in March 2026; v1.2 in the lab). It reads Ingresses from the cluster or from files and prints Gateway API objects; it never applies anything:

ingress2gateway print --providers=ingress-nginx -n shop > gwapi.yaml
ingress2gateway print --providers=ingress-nginx --all-namespaces
ingress2gateway print --providers=ingress-nginx --input-file shop-ingress.yaml

It writes one Gateway per Ingress class (gatewayClassName = the class name: edit it to your implementation's class) and one HTTPRoute per host, and it merges canary Ingresses into weights. Everything it cannot translate goes to stderr as a warning box:

┌─ WARN  ────────────────────────────────────────
│  IP-based authorization is not supported
│  source: INGRESS-NGINX
│  object: Ingress: shop/admin
└─

Each WARN is a feature you would silently lose. --emitter envoy-gateway (or kgateway, agentgateway) adds that implementation's extension objects for some of them.

Side by side, then DNS

Never convert in place. The safe sequence:

  1. Install the Gateway API implementation next to ingress-nginx. Its Gateway gets its own address.
  2. Apply the generated (and reviewed) Gateway and routes. Nothing changes for users: DNS still points at ingress-nginx.
  3. Test the new path with the right Host header against the Gateway's address (curl --resolve shop.lab:80:<gateway-ip>), including TLS, every path, the canary weights.
  4. Move DNS: the record now points at the Gateway (external-dns with --source=gateway-httproute, or by hand). Keep TTLs low beforehand.
  5. Watch both data planes' logs until the old one sees no traffic for a few TTLs. Rollback until then = put DNS back.
  6. Delete the Ingresses. Last, ingress-nginx itself.

TLS: put cert-manager.io/cluster-issuer on the Gateway (cert-manager's gateway-shim) or reuse the existing Secrets with certificateRefs - the same certificate can be served by both during the overlap.

What you can now do:

Why it helps

With ingress-nginx retired, this migration is on many teams' roadmaps, and doing it carelessly is how you cause an outage for every app at once. The safe pattern is the same everywhere: translate, review, run side by side, move one host at a time by DNS, keep the rollback.

The tool does the boring part, but the interesting part is what does not translate: snippets, some authentication annotations, controller-specific behaviour such as default timeouts. Knowing the map from annotations to Gateway API fields, and reading the warnings, is what lets you say in a design review exactly which behaviour changes for which app.

Commands in this lesson

ingress2gateway

FAQ

Does ingress2gateway change anything in the cluster?

No. ingress2gateway print only reads Ingresses (from the cluster or a file) and prints YAML to stdout, with warnings on stderr. You review the output, edit it if needed, and apply it yourself. That makes it safe to run against production to see what a migration would look like.

What happens to annotations it cannot translate?

It prints a warning naming the annotation and the Ingress, and leaves that behaviour out. Snippets, many auth annotations and some controller settings have no Gateway API field; they need an implementation-specific policy object, a change in the app, or a decision to drop them.

Why run the old and new edge side by side?

Because the new Gateway gets its own address, you can test every host against it with curl --resolve or a Host header while real users still use the old one. Nothing changes for users until DNS points to the new address, and pointing it back is the rollback.

How is a canary Ingress translated?

A canary Ingress with a weight becomes weighted backendRefs in one HTTPRoute rule, and a canary by header becomes a separate rule that matches the header. The result is clearer than the annotation pair, because both paths are visible in one route.

Do I have to migrate everything at once?

No, and you should not. Move one host (or one team's hosts) at a time: create its routes, test, change its DNS record, watch errors, then the next. Some Ingresses may stay on another controller that still supports Ingress if they rely on features you cannot replace yet.

In an interview Mid

How would you migrate a cluster from Ingress to Gateway API without downtime?

I would inventory the Ingresses and their annotations, then run ingress2gateway print to get Gateway and HTTPRoute YAML and read the warnings on stderr for what does not translate. After review I apply the Gateway next to the existing controller; it gets its own address, so I can test each host against it with a Host header or curl --resolve while users still hit the old edge. Then I move one host at a time by changing its DNS record, with a low TTL set in advance, watch error rates, and keep the old Ingress until the new path is proven. Rolling back is pointing DNS back.

Also asked: Which Ingress features have no direct Gateway API equivalent? · How do you test a new ingress path before users reach it? · What does ingress2gateway produce, and what does it not do?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.