Joe Attwood

Joe Attwood

DevOps, Platform & infrastructure engineering, London

Open to Senior / Staff
Notice 3 months
CV (PDF)

/writing/envoy-gateway-migration

Moving from Ingress to Envoy Gateway

The Gateway API is usually explained as a better Ingress. That undersells it. Ingress collapsed two different jobs into one resource: the infrastructure-level concern of running a load balancer and terminating TLS, and the application-level concern of routing a path to a service. Gateway API splits those into Gateway and HTTPRoute, owned by different people.

That split is the reason to migrate. The routing improvements are real but secondary.

Where we started

Ingress-NGINX with a growing pile of annotations: TLS, rewrites, timeouts, affinity, and the occasional snippet that nobody wanted to own. Changes that touched shared ingress config required platform involvement even when the intent was “route this path to that service.” Ownership was muddy because the resource itself was muddy.

Why Envoy Gateway rather than the alternatives

We already ran Envoy in places and understood its config model. Envoy Gateway implements Gateway API without inventing a parallel control plane vocabulary, and the project was moving at a pace that matched what we needed. Contour and other implementations were fine on paper; existing Envoy familiarity tipped it. That is a legitimate engineering reason — the migration cost is mostly people, not YAML.

Running both during the cutover

We did not flip the cluster in one change. New routes landed on Gateway API first; existing Ingress stayed serving until each service had an equivalent HTTPRoute and we were happy with health checks and TLS. Cutover was per-service: point DNS or the upstream load balancer at the Gateway listener, watch, then remove the Ingress object. Rollback was the reverse — leave the Ingress in place until the Gateway path was proven, not delete-and-hope.

What the annotations became

Ingress annotation (typical) Gateway API equivalent
cert-manager.io/cluster-issuer Certificate on the Gateway / listener TLS
nginx.ingress.kubernetes.io/rewrite-target URLRewrite filter on the HTTPRoute
nginx.ingress.kubernetes.io/proxy-body-size Envoy proxy policy / connection limits
nginx.ingress.kubernetes.io/affinity Session persistence policy (where supported)
nginx.ingress.kubernetes.io/configuration-snippet Usually nowhere — rewrite as a first-class filter or admit you needed a custom Envoy config

The last row is the migration tax. Snippets were an escape hatch that let teams paper over missing features. Gateway API pushes those into explicit policies or makes you admit the requirement was never supported properly.

What I’d tell someone starting this

  1. Treat ownership as the success metric. If app teams still open tickets to change a path, you migrated the API without migrating the model.
  2. Inventory every annotation before you write the first HTTPRoute. The ones with no equivalent are your real scope.
  3. Run both controllers until the last Ingress is gone. Parallel is boring and correct; a big-bang cutover is a weekend you do not get back.
My cat, extremely close to the camera, unimpressed

resource "cat" "whiskers" {
  mood    = "unbothered"
  consent = false
}
Apply complete. 1 added, 0 changed, 0 destroyed.