Skip to content
Hogin Hogin
Go back

Gateway API over Ingress: migrating before ingress-nginx dies

10 мин чтения

On March 24, 2026, kubernetes/ingress-nginx — the most widely used ingress controller in the ecosystem — officially reached End-of-Life. The repo is frozen: no bugfixes, no security patches, no responses on issues. If it’s still handling traffic in your cluster, the question isn’t “should we migrate” — it’s “how long until the first unpatched vulnerability.” Gateway API isn’t an alternative syntax for Ingress; it’s the official replacement built into Kubernetes itself, and SIG Network has already shipped a migration tool. Here’s how to move over without a fire drill.

Table of contents

Open Table of contents

Why Ingress hit a wall

Ingress was specified back in 2015 as a minimal common denominator: one resource, host/path matching, a TLS block — and that’s it. Real controllers hit the ceiling of that spec almost immediately: canary releases, rate limiting, rewrite rules, mTLS between services — none of it exists in the API. Every controller plugged the gaps with nginx.ingress.kubernetes.io/...-style annotations, and the end result wasn’t a portable API but a pile of dialects. A manifest written for ingress-nginx wouldn’t run on Traefik or HAProxy without rewriting half the annotations.

The second problem is roles. In a real organization, the platform team owns the load balancer and TLS certificates, while a service team owns only its own routes. Ingress doesn’t distinguish between the two: it’s one YAML file, so either the developer gets rights over the whole load balancer, or the platform team becomes a bottleneck, manually reviewing every PR that adds a new path.

What Gateway API changes

Gateway API isn’t a patch on top of Ingress — it’s a different resource model, designed from the start around roles and typed fields.

Ingress: one resource and vendor annotations vs. Gateway API: roles and typed resources

The resource model: GatewayClass → Gateway → HTTPRoute

Three resources, each solving its own layer of the problem.

GatewayClass is a cluster-scoped resource that declares an available implementation (Envoy Gateway, NGINX Gateway Fabric, a cloud LB) — roughly the way a StorageClass declares an available disk type. The platform team, or the implementation’s own operator during install, creates it.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

Gateway is a concrete entry point instance: ports, protocols, TLS certificates, and a restriction on which namespaces are allowed to attach routes to it. Also platform team territory.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-gateway
  namespace: infra
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        certificateRefs:
          - name: wildcard-tls
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels: { gateway-access: "allowed" }

HTTPRoute holds one service’s routes, lives in the team’s own namespace, and references a Gateway through parentRefs. This is also where the weighted canary split lives — no annotation needed.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: checkout
  namespace: checkout-team
spec:
  parentRefs:
    - name: public-gateway
      namespace: infra
  hostnames: ["checkout.example.com"]
  rules:
    - matches:
        - path: { type: PathPrefix, value: /api }
      backendRefs:
        - name: checkout-stable
          port: 80
          weight: 90
        - name: checkout-canary
          port: 80
          weight: 10

GatewayClass sets the implementation, Gateway is the entry point, HTTPRoute holds the team's routes

ingress2gateway: conversion without a manual rewrite

SIG Network shipped ingress2gateway 1.0 specifically for the wave of migrations off EOL ingress-nginx. The tool reads a cluster’s existing Ingress resources (including a fair chunk of common annotations) and generates the equivalent Gateway/HTTPRoute pair.

# install the CLI
go install sigs.k8s.io/ingress2gateway@latest

# read the current Ingress resources from the cluster and print generated manifests
ingress2gateway print --namespace checkout-team > gateway-generated.yaml

# same, but scoped to a specific annotation provider (e.g. nginx)
ingress2gateway print --providers=ingress-nginx --namespace checkout-team

The generated YAML is a draft, not a final result: check the parentRefs (does the name and namespace match the Gateway you already created), confirm TLS actually got pulled from the right Gateway listener instead of coming out empty, and run it through kubectl apply --dry-run=server to catch schema errors before applying for real.

Picking an implementation

Gateway API is a spec, not a running controller — you need an implementation that supports it.

The deciding factor is almost always not “which implementation is technically stronger” but “what’s already running in the cluster.” If the CNI is Cilium, bolting on a separate L7 load balancer just for Gateway API is an extra component in the dataplane; if the infrastructure is fully cloud-hosted and L7 is already delegated to a managed LB, it’s more natural to turn on Gateway API support there rather than stand up Envoy Gateway alongside it. A standalone install (Envoy Gateway, NGINX Gateway Fabric) makes sense where you need control over a specific version and aren’t tied to one cloud.

Comparison: Ingress vs. Gateway API

IngressGateway API
RolesOne resource, all-or-nothing accessGatewayClass/Gateway — platform, HTTPRoute — team
Canary/weight routingVendor annotations, not portableweight field in the backendRefs schema
Portability across controllersLow — every vendor’s annotations differHigh — a shared typed schema
ProtocolsHTTP/HTTPS onlyHTTP, gRPC, TCP, TLS — separate route types
Cross-namespace referencesNonstandard controller flagsReferenceGrant, explicit opt-in
Project statusingress-nginx: EOL since March 2026Actively developed, GA since v1.0

What it takes to migrate

An order of operations that doesn’t break production traffic along the way:

  1. Install the Gateway API CRDskubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml.
  2. Install one implementation — Envoy Gateway via its Helm chart, for example, and create a GatewayClass + Gateway for a new public entry point.
  3. Run the existing Ingress resources through ingress2gateway, per namespace, to get a draft HTTPRoute.
  4. Review every generated HTTPRoute by hand — especially annotations with no 1:1 equivalent (rate limiting, custom headers, Lua/nginx.conf snippets) — those need porting through an implementation-specific ExtensionRef or rewriting as separate logic.
  5. Run Gateway and HTTPRoute alongside the old Ingress, sending a small slice of traffic to the new entry point via a DNS weight or a separate test hostname — don’t switch off Ingress right away.
  6. Ramp the canary weight in HTTPRoute gradually from 10% to 100% while watching metrics, and only then remove the old Ingress resources.

Ingress → ingress2gateway → draft HTTPRoute → 10% canary → 100%, old Ingress switched off last

Migration traps

How to verify it works

Three quick checks after applying the manifests. The Gateway’s own status: kubectl get gateway public-gateway -n infra -o jsonpath='{.status.conditions}' should show Programmed: True and Accepted: True. The specific route’s status: kubectl get httproute checkout -n checkout-team -o jsonpath='{.status.parents}' confirms the Gateway actually accepted this HTTPRoute, not just that the resource exists unattached. And live traffic: curl -H "Host: checkout.example.com" https://<gateway-ip>/api/health run a few times in a row should show the split between checkout-stable and checkout-canary tracking the configured weights.

Bottom line

ingress-nginx won’t break overnight — it’ll keep running right up until the first vulnerability that no longer gets a patch. That makes migration less a matter of taste and more a matter of timing: the sooner you start shifting traffic to Gateway API alongside the old Ingress, the smaller the odds of a security incident landing on dead code in production. ingress2gateway handles most of the routine conversion, but manually checking every nonstandard rule is a required step, not an optional one.


Share this post:

Next Post
Backstage in a weekend: an IDP and golden paths for a small team