Migrare da ingress-nginx alla Kubernetes Gateway API: Guida Pratica al Cutover
Il progetto ingress-nginx è stato archiviato all’inizio del 2026. Niente più release, niente più bugfix e — la parte che deve preoccuparti — niente più patch di sicurezza. Se hai Kubernetes in produzione c’è una buona probabilità che ti riguardi direttamente: i sondaggi recenti danno l’uso di ingress-nginx intorno alla metà di tutti i cluster. “Non fare niente” è ormai un incident al rallentatore.
La community ha convergito sulla Gateway API come sostituto a lungo termine di Ingress. Non è un nuovo controller: è un nuovo set di risorse Kubernetes (GatewayClass, Gateway, HTTPRoute) che diversi controller implementano — Envoy Gateway, Cilium, Istio, Kong e altri. Questa guida segue una migrazione reale: installi un controller Gateway accanto all’ingress-nginx esistente, converti gli oggetti Ingress, traduci le annotation che non si convertono da sole e sposti il traffico host per host senza una grossa finestra di manutenzione.
Prerequisiti
- Un cluster Kubernetes 1.29+ con accesso
kubectle cluster-admin per i passi di installazione - Helm 3.14+
- Un deployment ingress-nginx esistente con veri oggetti
Ingressda migrare - cert-manager già in esecuzione se termini il TLS nel cluster (questa guida lo assume)
ingress2gatewayv0.4.0+ installato in locale (go install github.com/kubernetes-sigs/ingress2gateway@latest)- Familiarità con il comportamento delle tue annotation Ingress attuali (rewrite, redirect, timeout, auth)
Devi essere a tuo agio nel leggere e fare diff di manifest YAML e nel modificare record DNS o load balancer.
Architettura e Concetti Chiave
Ingress impacchettava tutto — listener, hostname, path, TLS e comportamenti vendor-specific — in un solo oggetto più una zuppa di annotation. La Gateway API divide quella superficie in tre risorse, di proprietà di tre ruoli diversi.
flowchart TD
subgraph Infra["Team infrastruttura"]
GC[GatewayClass<br/>controllerName: envoy / cilium]
end
subgraph Cluster["Cluster operator"]
GW[Gateway<br/>listener :80 / :443<br/>riferimenti cert TLS]
end
subgraph Dev["Team applicativi"]
R1[HTTPRoute<br/>shop.example.com]
R2[HTTPRoute<br/>api.example.com]
end
GC --> GW
GW --> R1
GW --> R2
R1 --> S1[(Service: storefront)]
R2 --> S2[(Service: api)]
R2 -. peso 90/10 .-> S3[(Service: api-canary)]
GatewayClassnomina il controller (spec.controllerName). Ne installi uno per controller e non lo tocchi quasi più.Gatewaydefinisce i listener: porte, protocolli, hostname e riferimenti ai certificati TLS. È di proprietà dei cluster operator.allowedRoutesdecide quali namespace possono attaccare route.HTTPRoutecontiene le regole di routing:hostnames,matches(path, header, method, query),filters(mutazione header, redirect, rewrite, mirroring) ebackendRefscon pesi opzionali per il traffic splitting. Sono di proprietà dei team applicativi, nei loro namespace.
I vantaggi pratici: le annotation diventano campi tipizzati e validati; il canary è nativo invece di un secondo Ingress con canary-weight; e uno sviluppatore non può cambiare per sbaglio la config TLS o l’hostname di un altro team, perché vivono in oggetti che non controlla.
Scegliere un controller
| Controller | Data plane | Quando conviene |
|---|---|---|
| Envoy Gateway | Envoy (user-space) | Vuoi l’implementazione di riferimento, alta conformance alla Gateway API e portabilità tra cloud |
| Cilium | eBPF per L4, Envoy per L7 | Cilium è già la tua CNI; vuoi load balancing L4 in-kernel vicino al line rate |
| Istio | Envoy | Hai già Istio e vuoi che mesh ed edge condividano la config |
Questa guida usa Envoy Gateway negli esempi perché è autosufficiente e cloud-neutral. Ogni manifest mostrato è Gateway API v1 standard — cambiano solo il controllerName della GatewayClass e l’installazione per Cilium o Istio.
Implementazione Passo-Passo
Installare un controller Gateway accanto a ingress-nginx
I due possono coesistere. ingress-nginx osserva gli oggetti Ingress; il controller Gateway osserva Gateway/HTTPRoute. Installa le CRD della Gateway API ed Envoy Gateway:
| |
Crea la GatewayClass e un Gateway condiviso. Il Gateway ottiene un suo IP esterno / load balancer, separato da quello che ingress-nginx ha già — è proprio quella separazione a rendere sicuro il cutover:
| |
| |
💡 Tieni i certificati TLS nel
Gateway, non in ogni route. Un solocertificateRefwildcard sul listener copre ogniHTTPRoute*.example.com. Se ti servono certificati per-host, aggiungi altri listener con valorihostnamespecifici.
Conversione di massa degli Ingress con ingress2gateway
Non scrivere a mano decine di route. ingress2gateway legge i tuoi oggetti Ingress in esecuzione (e le annotation ingress-nginx che conosce) e produce YAML Gateway API:
| |
Per un Ingress tipico come questo:
| |
…lo strumento produce lo scheletro dell’HTTPRoute. Lo rivedi comunque: ingress2gateway converte ciò che mappa in modo pulito e lascia commenti dove non ci riesce:
| |
Tradurre le annotation che non si convertono da sole
La maggior parte dei comportamenti di ingress-nginx mappa su un filtro o un campo della Gateway API. Quelli che incontrerai più spesso:
| Annotation ingress-nginx | Equivalente Gateway API |
|---|---|
rewrite-target | filtro URLRewrite → path.type: ReplacePrefixMatch |
ssl-redirect / force-ssl-redirect | filtro RequestRedirect con scheme: https, statusCode: 301 su una route del listener HTTP |
permanent-redirect | filtro RequestRedirect (hostname, path, statusCode: 301) |
proxy-read-timeout / proxy-send-timeout | rules[].timeouts.backendRequest |
configuration-snippet che aggiunge header | filtri RequestHeaderModifier / ResponseHeaderModifier |
canary + canary-weight | più backendRefs con weight |
enable-cors + cors-* | ResponseHeaderModifier (statico) o la CRD di policy CORS del controller |
auth-url / auth-signin (auth esterna) | dipende dal controller: SecurityPolicy di Envoy Gateway, CiliumEnvoyConfig di Cilium |
Il redirect HTTP→HTTPS vale la pena mostrarlo perché sta su quasi ogni Ingress. Attacca una route di solo redirect al listener HTTP:
| |
Tutto ciò che è guidato da annotation e non ha ancora un campo — mTLS verso il backend, Lua complesso, rate limiting — è dove usi le CRD di policy del controller (BackendTrafficPolicy, SecurityPolicy, ClientTrafficPolicy in Envoy Gateway). Fai l’inventario di queste prima di iniziare: sono la coda lunga della migrazione.
TLS e cert-manager
cert-manager capisce la Gateway API. Annoti il Gateway invece di un Ingress e lui gestisce il certificato del listener:
| |
Abilita l’integrazione Gateway API di cert-manager con --set config.enableGatewayAPI=true sul suo chart Helm.
Configurazione per Produzione
Canary e traffic splitting
È nativo. Nessun secondo oggetto, nessuna annotation — solo pesi sui backendRefs:
| |
I pesi sono relativi, non percentuali — 9 e 1 si comportano come 90 e 10. I tool di progressive delivery (Argo Rollouts, Flagger) guidano questi numeri in automatico ed entrambi supportano la Gateway API come provider di prima classe.
Route cross-namespace con ReferenceGrant
Di default un HTTPRoute può referenziare solo Service nel suo namespace, e un Gateway accetta route solo dai namespace che i suoi allowedRoutes permettono. Per instradare verso un Service in un altro namespace, il namespace proprietario deve pubblicare un ReferenceGrant:
| |
È una feature, non un attrito: l’accesso cross-namespace ora è esplicito e verificabile invece che implicito.
Il cutover affiancato
Fai girare entrambi gli stack e sposta il traffico per hostname:
- Deploya il
Gatewaycon il suo IP di load balancer. Ancora nessuno ci punta. - Crea gli
HTTPRouteper un hostname. Verifica con un header Host direttamente contro l’IP del Gateway:1 2curl -H "Host: shop.example.com" -kso /dev/null -w "%{http_code}\n" \ https://<gateway-ip>/healthz - Sposta il DNS di
shop.example.comdal LB di ingress-nginx al LB del Gateway. Abbassa il TTL a 60s il giorno prima, così il rollback è rapido. - Osserva entrambi i set di access log. Il traffico ingress-nginx per quell’host scende a zero man mano che le cache scadono.
- Ripeti per hostname. Quando l’ultimo è spostato e i log di ingress-nginx sono silenziosi per un giorno intero, cancella gli oggetti
Ingresse poi disinstalla ingress-nginx.
Il rollback a qualsiasi passo è un cambio DNS all’indietro. Nessuna route è mai servita da entrambi gli stack contemporaneamente, quindi non c’è rischio di doppio routing.
Errori Comuni e Troubleshooting
L’HTTPRoute mostra Accepted: False con reason NotAllowedByListeners. L’allowedRoutes.namespaces del listener del Gateway non include il namespace della route, oppure gli hostnames della route non intersecano l’hostname del listener. Controlla kubectl describe httproute: le condizioni di status ti dicono esattamente quale.
404 da Envoy ma la route sembra corretta. In parentRefs manca il namespace (di default è quello della route, non quello del Gateway), oppure sectionName punta a un listener che non esiste.
Le catture regex di rewrite-target non si traducono. I rewrite della Gateway API sono basati su prefisso, non regex. path: /app(/|$)(.*) → rewrite /$2 diventa PathPrefix: /app + ReplacePrefixMatch: /. I rewrite davvero regex-dipendenti richiedono il path match RegularExpression (implementation-specific) o una piccola modifica al routing dell’app.
L’handshake TLS fallisce dopo il cutover. Il Secret del certificateRef è nel namespace del Gateway ma il Gateway è altrove, oppure cert-manager non ha ancora emesso. kubectl get certificate -A e controlla la condizione Programmed nello status del Gateway.
L’auth esterna / OAuth2 proxy ha smesso di funzionare. auth-url non ha un equivalente core nella Gateway API. Ti serve la SecurityPolicy del controller (Envoy Gateway) o equivalente, collegata allo stesso provider OIDC. Non spostare questo hostname finché non è testato.
L’IP del client ora è quello del load balancer. Imposta externalTrafficPolicy: Local sul Service del Gateway, oppure configura la ClientTrafficPolicy del controller per fidarsi di X-Forwarded-For dal tuo LB.
Performance e Scalabilità
- Il data plane eBPF di Cilium gestisce L4 nel kernel e passa a Envoy solo il L7 (modifiche header HTTP, gRPC). Se sei già su Cilium come CNI, questo significa latenza e CPU misurabilmente più basse di un proxy solo user-space a conteggi di connessione alti.
- Envoy Gateway provisiona un deployment Envoy per
Gateway(o perGatewayClass, configurabile). Dimensionalo con una risorsaEnvoyProxye un HPA; le due repliche di default sono un punto di partenza, non un numero di produzione. - Meno reload. ingress-nginx ricaricava e ri-forkava NGINX a ogni cambio Ingress. Envoy applica i cambi di route via xDS senza drop di connessione, quindi gli ambienti ad alto churn smettono di pagare la tassa del reload.
- Un Gateway, molte route. Non creare un
Gatewayper team — sarebbe un load balancer per team. Condividi unGateway, isola con namespace eallowedRoutes. - Tieni piccoli gli oggetti route. Qualche centinaio di
HTTPRouteva bene; una singola route da 5.000 regole no. Dividi per hostname o gruppo di path.
Conclusioni e Next Steps
L’archiviazione di ingress-nginx forza una decisione, ma la Gateway API è un upgrade vero, non uno spostamento laterale: configurazione tipizzata invece di stringhe di annotation, canary nativo, vera separazione dei ruoli e una spec che più controller implementano, così non sei di nuovo legato al ciclo di vita di un solo progetto.
La migrazione in sé è meccanica se la fai a tappe: installi un controller accanto a ingress-nginx, converti con ingress2gateway, traduci la coda lunga delle annotation in filtri e CRD di policy, poi sposti il traffico un hostname alla volta dietro il DNS.
Prossimi passi:
- Lancia
ingress2gateway printsu ogni namespace adesso, solo per vedere quanto è grande la tua coda lunga di annotation. - Scegli il controller che si adatta al tuo stack (Cilium se è la tua CNI, altrimenti Envoy Gateway) e tira su un
Gatewaynon-prod. - Collega Argo Rollouts o Flagger alla Gateway API così i canary sono automatizzati dal primo giorno.
- Aggiungi
BackendTrafficPolicyper retry, timeout e circuit breaking che prima vivevano nelle annotation.