Ressources
ingress-nginx n'est plus maintenu : migrer vers Gateway API sans interruption
Migrer d'ingress-nginx vers Gateway API après son retrait : inventaire des annotations, choix de l'implémentation, double exposition, bascule et retour arrière.
Par Corentin Mas, publié le · 24 min de lecture
Base d'expérience : Méthode et documentation officielle : annonces du blog Kubernetes, dépôts kubernetes/ingress-nginx, kubernetes-sigs/gateway-api et kubernetes-sigs/ingress2gateway, documentations cert-manager et external-dns.
Relecture factuelle : Claude Code (revue indépendante déléguée par Corentin Mas, 28/09/2026), le
Le contrôleur communautaire ingress-nginx a été retiré en mars 2026. Rien ne casse le jour du retrait, et c'est le piège : une faille découverte ensuite ne sera jamais corrigée. Ce contrôleur porte souvent toute l'exposition HTTP (TLS, redirections, réécritures, limites de taille, listes d'adresses autorisées, fragments NGINX injectés par annotation) : le remplacer revient à réécrire un comportement rarement documenté en entier.
Ce guide traite la migration vers Gateway API comme une bascule de production : inventaire, correspondance des annotations, choix d'une implémentation d'après les rapports de conformité, double exposition, bascule par nom d'hôte, retour arrière et décommissionnement.
Faits datés
Cette lecture est établie sur Gateway API v1.6.2, ingress2gateway v1.2.0 et la dernière version publiée d'ingress-nginx, v1.15.1 ; les pages officielles et les dépôts cités ont été vérifiés le .
Vérifié le
Ce que le retrait d'ingress-nginx change réellement
Calendrier et portée du retrait
Le 11 novembre 2025, SIG Network et le Security Response Committee de Kubernetes ont annoncé le retrait d'ingress-nginx, avec une maintenance « best-effort » (sans engagement) jusqu'en mars 2026. Depuis, le projet ne publie plus ni version, ni correctif, y compris de sécurité ; les déploiements existants fonctionnent et les charts Helm et images restent disponibles. La bannière du dépôt kubernetes/ingress-nginx indique un archivage en lecture seule le 24 mars 2026. InGate, le remplaçant envisagé par les mainteneurs, est retiré lui aussi.
Dans une déclaration commune du 29 janvier 2026, le Steering Committee et le Security Response Committee, citant une étude interne de Datadog, qualifient ingress-nginx d'infrastructure critique pour environ la moitié des environnements cloud native, écrivent que rester dessus après le retrait expose les utilisateurs à des attaques, et qu'aucune alternative n'est un remplacement direct.
Les dernières versions, controller-v1.15.1, v1.14.5 et v1.13.9, datent du 19 mars 2026. Le tableau de compatibilité du dépôt s'arrête, pour la v1.15.1, à Kubernetes 1.35, alors que Kubernetes 1.37 est publié depuis le 26 août 2026 : chaque montée de version du cluster sort un peu plus du périmètre testé.
Pour repérer les contrôleurs concernés, avec des droits d'administrateur du cluster, la première commande est celle de l'annonce ; la seconde rattrape par la classe d'entrée les installations aux étiquettes non standard :
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx
# Classes d'entrée servies par ingress-nginx (contrôleur k8s.io/ingress-nginx par défaut)
kubectl get ingressclass -o custom-columns=NOM:.metadata.name,CONTROLEUR:.spec.controllerPourquoi un contrôleur non maintenu est un risque concret
Le 24 mars 2025, les mainteneurs ont corrigé cinq vulnérabilités en v1.12.1 et v1.11.5 : CVE-2025-1097, CVE-2025-1098, CVE-2025-24513, CVE-2025-24514 et CVE-2025-1974, cette dernière notée 9,8 au CVSS. Toutes sauf CVE-2025-24513 forment la série que les chercheurs de Wiz, qui les ont signalées, ont baptisée « IngressNightmare ». Selon le Security Response Committee, un objet Ingress forgé pouvait amener NGINX à révéler des Secrets accessibles au contrôleur, qui lit par défaut tous les Secrets du cluster ; CVE-2025-1974 rendait l'attaque possible depuis n'importe quel point du réseau des pods, par le webhook d'admission, sans identifiant.
Une série comparable découverte aujourd'hui ne recevrait plus de correctif : il ne resterait que des parades de configuration, comme la désactivation du webhook d'admission proposée à l'époque contre CVE-2025-1974. L'annonce de retrait nomme la cause de fond : des options comme l'injection de directives NGINX par les annotations « snippets » sont devenues des failles de conception. Le README du dépôt déconseille d'ailleurs tout usage multilocataire en production, le projet supposant que quiconque crée un Ingress est administrateur du cluster.
Ingress reste, ingress-nginx part : deux confusions à éviter
Sur l'API d'abord : la documentation Kubernetes recommande Gateway et déclare Ingress figée, GA et sans retrait prévu, mais sans évolution. Les objets Ingress restent valides ; c'est leur contrôleur qui n'est plus maintenu.
Sur le produit ensuite. ingress-nginx, projet communautaire gouverné par Kubernetes, et NGINX Ingress Controller, développé par F5, sont deux contrôleurs distincts : tous deux utilisent NGINX comme plan de données, sans autre lien. Leurs annotations diffèrent (nginx.ingress.kubernetes.io/ d'un côté, nginx.org/ de l'autre) et F5 publie un guide de migration depuis ingress-nginx. NGINX Gateway Fabric, implémentation Gateway API de l'organisation GitHub nginx, est un troisième projet. Le retrait ne concerne que le premier.
SIG Network et le Security Response Committee recommandent de migrer vers Gateway API ou vers un autre contrôleur Ingress listé par la documentation Kubernetes, dont certains lisent une partie des annotations d'ingress-nginx (fournisseur dédié de Traefik, par exemple). Cette seconde voie évite de réécrire les objets Ingress, mais conserve une API figée : c'est un choix à documenter comme tel.
Inventorier ce que le contrôleur fait vraiment
Le risque principal consiste à migrer les objets Ingress et à oublier le reste. Le comportement d'ingress-nginx vient de cinq sources, que la migration doit toutes couvrir.
- Les annotations de chaque Ingress, préfixées par défaut par
nginx.ingress.kubernetes.io/(préfixe modifiable par le drapeau--annotations-prefix). - La ConfigMap globale du contrôleur, dont les défauts s'appliquent sans apparaître dans aucun Ingress :
proxy-body-sizeà1m,ssl-redirectàtrue,hstsàtrue(hsts-max-ageà31536000,hsts-include-subdomainsàtrue),use-forwarded-headersàfalse,allow-snippet-annotationsàfalse,annotations-risk-levelàHigh. - Les drapeaux du contrôleur :
--default-ssl-certificate,--enable-ssl-passthrough,--watch-ingress-without-class,--ingress-classet--controller-class. - Les comportements implicites, détaillés par le billet « Before You Migrate » du blog Kubernetes : une expression régulière est un préfixe insensible à la casse ;
use-regexs'applique à tous les chemins d'un hôte, tous Ingress confondus ;rewrite-targetactive silencieusement ce mode ; une requête sans barre oblique finale est redirigée en 301 quand seul le chemin avec barre est déclaré (hors expressions régulières) ; les URL sont normalisées avant la correspondance. - Les couplages externes : solveurs HTTP-01 de cert-manager en
ingressClassName: nginx, external-dns alimenté par la source Ingress, alertes sur les métriques du contrôleur, politiques d'admission, charts Helm qui génèrent des Ingress.
Extraire l'inventaire
Le premier filtre, testé avec jq 1.7 sur un export d'exemple, produit une ligne par Ingress ; le second compte l'usage de chaque annotation, ce qui donne l'ordre de traitement.
# 1. Une ligne par Ingress : namespace, nom, classe, hôtes, annotations ingress-nginx
kubectl get ingress --all-namespaces -o json | jq -r '
.items[]
| (.metadata.annotations // {}) as $a
| [ .metadata.namespace,
.metadata.name,
(.spec.ingressClassName // $a["kubernetes.io/ingress.class"] // "-"),
([.spec.rules[]?.host // "*"] | unique | join(",")),
($a | to_entries
| map(select(.key | startswith("nginx.ingress.kubernetes.io/")))
| map("\(.key | ltrimstr("nginx.ingress.kubernetes.io/"))=\(.value)")
| join(" ; "))
]
| @tsv' > inventaire-ingress.tsv
# 2. Fréquence de chaque annotation dans le cluster
kubectl get ingress --all-namespaces -o json | jq -r '
[ .items[].metadata.annotations // {} | keys[]
| select(startswith("nginx.ingress.kubernetes.io/")) ]
| group_by(.) | map("\(length)\t\(.[0])") | .[]' | sort -rn
# 3. Réglages globaux et drapeaux (noms courants d'une installation Helm, à adapter)
kubectl -n ingress-nginx get configmap ingress-nginx-controller -o jsonpath='{.data}'
kubectl -n ingress-nginx get deployment ingress-nginx-controller \
-o jsonpath='{.spec.template.spec.containers[0].args}'Une classe « - » signale un Ingress sans classe, servi seulement si le contrôleur surveille ces objets ou porte la classe par défaut : ce sont les premiers à disparaître silencieusement.
Table de correspondance des annotations
La colonne ingress2gateway reprend la documentation du fournisseur ingress-nginx en version 1.2.0 et son journal des modifications ; les niveaux de support viennent des CRD et journaux de Gateway API.
| Annotations ingress-nginx | Équivalent Gateway API | Niveau | ingress2gateway 1.2.0 | Point de vigilance |
|---|---|---|---|---|
rewrite-target, use-regex | Filtre URLRewrite (ReplaceFullPath, ReplacePrefixMatch) ; correspondance RegularExpression | Réécriture Extended ; expressions régulières propres à l'implémentation | Converti (ReplaceFullPath) ; captures ($1) signalées, non traduites | Préfixe insensible à la casse chez ingress-nginx, correspondance complète et sensible à la casse chez plusieurs implémentations fondées sur Envoy |
ssl-redirect, force-ssl-redirect | Route sur l'écouteur HTTP avec filtre RequestRedirect (scheme: https) | Redirection de schéma (scheme) Extended ; codes 301 et 302 Core, 303, 307 et 308 Extended | ssl-redirect converti (308 ajoutée par défaut pour un Ingress avec TLS, supprimée si false) ; force-ssl-redirect non pris en charge | ingress-nginx redirige en 308 dès qu'un Ingress porte du TLS ; rien de tel sans route explicite |
permanent-redirect, temporal-redirect, app-root, from-to-www-redirect | RequestRedirect | Core ou Extended selon le code | Convertis (app-root et from-to-www-redirect depuis 1.1.0) | Redirection implicite de la barre oblique finale à rendre explicite |
proxy-connect-timeout, proxy-send-timeout, proxy-read-timeout | timeouts.request et timeouts.backendRequest | Extended, Standard depuis 1.2 | Convertis en un seul timeouts.request égal à 10 fois le plus grand des trois délais ; backendRequest non renseigné | proxy-read-timeout borne l'inactivité entre deux lectures (60 s par défaut), timeouts.request la réponse entière |
proxy-body-size, client-body-buffer-size | Aucun champ standard | Propre à l'implémentation | Émetteur envoy-gateway : BackendTrafficPolicy, requestBuffer.limit ; avertissement sinon | Défaut ingress-nginx de 1 Mo (413 au-delà), différent de celui de la cible |
enable-cors, cors-* | Filtre CORS | Extended, Standard depuis 1.5 | Convertis | Défauts implicites d'ingress-nginx (origine *, cors-allow-credentials à vrai) à trancher |
auth-url, auth-signin, auth-type, auth-secret | Pas de champ Standard ; authentification expérimentale (GEP-1494) | Propre à l'implémentation | Non pris en charge | SecurityPolicy (Envoy Gateway), AuthenticationFilter (NGINX Gateway Fabric, Basic, OIDC ou JWT, sans autorisation externe), middleware (Traefik) : à reconstruire et tester |
limit-rps, limit-rpm, limit-connections | Aucun | Propre à l'implémentation | Non pris en charge | Revalider l'unité de comptage (adresse, réplique, global) |
canary, canary-weight, canary-by-header | Poids des backendRefs, correspondance d'en-tête exacte | Core | Convertis ; canary-by-cookie et canary-by-header-pattern signalés | Le canari devient une propriété de la route |
whitelist-source-range, denylist-source-range | Aucun champ standard | Propre à l'implémentation | Émetteur envoy-gateway : SecurityPolicy, règles authorization | Sans effet si l'adresse du client n'atteint pas le proxy |
backend-protocol (HTTPS, GRPC, GRPCS) | BackendTLSPolicy ; GRPCRoute | Standard (depuis 1.4 et 1.1) | Convertis | Vérifier le profil GATEWAY-GRPC et BackendTLSPolicy dans le rapport de conformité |
ssl-passthrough | TLSRoute, écouteur TLS en mode Passthrough | GA depuis 1.5 | Converti depuis 1.1.0 | Exigeait --enable-ssl-passthrough |
affinity, session-cookie-* | Persistance de session (GEP-1619) | Expérimental | Signalé ; traduit seulement par l'émetteur gce | Pas de canal expérimental pour ce seul besoin sans décision explicite |
configuration-snippet, server-snippet, auth-snippet | Aucun | Sans équivalent portable | Non pris en charge | Décomposer chaque fragment par intention ; SnippetsFilter de NGINX Gateway Fabric reste propre à NGINX |
custom-http-errors, default-backend | Aucun champ standard | Propre à l'implémentation | Non pris en charge | Pages d'erreur à reconstruire (responseOverride chez Envoy Gateway) |
upstream-vhost, x-forwarded-prefix, custom-headers | RequestHeaderModifier ; réécriture d'hôte par URLRewrite | En-têtes de requête Core ; réécriture Extended | Deux premiers convertis ; custom-headers signalé | En-têtes de réponse globaux, dont HSTS, à reposer par ResponseHeaderModifier (Extended) ou politique |
auth-tls-secret, auth-tls-verify-client | Validation des certificats clients sur la Gateway (GEP-91) | Standard depuis 1.5 | Absents des annotations prises en charge | Réglage porté par l'écouteur, donc par l'équipe plateforme |
enable-modsecurity | Hors périmètre de Gateway API | Sans objet | Non pris en charge | Pare-feu applicatif en amont ou fonction propre à l'implémentation |
Deux lignes demandent le plus de travail. Les snippets n'ont aucune traduction mécanique : un configuration-snippet qui ajoute trois en-têtes et une règle de cache devient trois modifications d'en-têtes et une décision sur le cache. L'authentification externe se reconstruit et se teste comme une fonction de sécurité : les requêtes non authentifiées doivent échouer.
Ce que fait ingress2gateway, et ce qu'il ne fait pas
ingress2gateway est l'outil de conversion du sous-projet Gateway API de SIG Network. La version 1.0.0, publiée le 20 mars 2026, a introduit les émetteurs et une prise en charge étendue des annotations d'ingress-nginx ; la 1.2.0, du 7 juillet 2026, est construite contre Gateway API v1.5.0. Un fournisseur lit les Ingress, un émetteur produit les ressources finales : l'émetteur standard, par défaut, s'en tient à Gateway API ; envoy-gateway, kgateway, agentgateway, gce et airlock-microgateway ajoutent les politiques propres à chaque projet, pour les annotations sans équivalent direct.
# Depuis le cluster, tous namespaces, classe d'entrée nginx, sortie standard
ingress2gateway print --providers=ingress-nginx -A \
--ingress-nginx-ingress-class=nginx > gateway-api-standard.yaml
# Même conversion vers les politiques d'une implémentation donnée
ingress2gateway print --providers=ingress-nginx -A \
--emitter=envoy-gateway > gateway-api-envoy-gateway.yamlChaque avertissement de l'outil devient une ligne de l'inventaire, traitée ou acceptée par écrit. En cas de conflit, l'Ingress créé le plus tôt l'emporte et le suivant est signalé en erreur. L'outil ne reproduit pas les comportements implicites qu'il ignore, et le guide officiel de migration depuis Ingress rappelle que toute conversion doit être testée : la sortie est un brouillon versionné, pas un livrable.
Choisir l'implémentation Gateway API sur preuves
Ce que Gateway API standardise en v1.6
À la date du , la version courante est v1.6.2, publiée le 3 septembre 2026. Sont GA en v1 : GatewayClass, Gateway, ListenerSet, HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute, BackendTLSPolicy et ReferenceGrant. La v1.5 (27 février 2026) a fait passer au canal Standard ListenerSet, le filtre CORS, TLSRoute v1 et la validation des certificats clients ; la v1.6 (29 juin 2026) y a ajouté TCPRoute et UDPRoute.
Chaque fonction porte un niveau de support : Core, que toute implémentation conforme doit fournir ; Extended, portable entre les implémentations qui la déclarent ; Implementation-specific, sans garantie de portabilité (grille reprise dans l'article « Kubernetes cloud-agnostique »). Les extensions passent par des filtres et des politiques, pas par des annotations, que le guide de migration déconseille fortement. Depuis la v1.5, une ValidatingAdmissionPolicy safe-upgrades.gateway.networking.k8s.io empêche d'installer les CRD expérimentales par-dessus les CRD standard et de revenir sous la version 1.5.
Lire un rapport de conformité plutôt qu'une page marketing
La page Implementations du projet classe les implémentations en « Conformant » ou « Partially Conformant », mais ce qui compte est le rapport lui-même, publié dans conformance/reports/v1.6/<implémentation>/ du dépôt kubernetes-sigs/gateway-api : version de l'implémentation, version et canal de Gateway API testés, mode, profils (GATEWAY-HTTP, GATEWAY-GRPC, GATEWAY-TLS, par exemple), résultat des tests Core et Extended, et deux listes, fonctions déclarées prises en charge et fonctions non prises en charge.
La lecture se fait ligne à ligne contre la table de correspondance : chaque fonction Extended dont l'inventaire a besoin (réécriture de chemin, délais de route, CORS, BackendTLSPolicy, redirection de schéma et code 308) doit figurer parmi les fonctions déclarées prises en charge. Une fonction absente du rapport n'est pas forcément impossible, puisqu'une politique propre à l'implémentation peut la couvrir ; à l'inverse, une fonction déclarée ne vaut que pour la version, le canal et le mode testés. L'écart se lève en testant la version réellement déployée.
Critères de décision
- Couverture de l'inventaire. Chaque ligne de la table en Extended ou propre à l'implémentation se vérifie dans le rapport, puis dans la documentation des politiques.
- Emplacement du plan de données. Proxy dans le cluster derrière un Service
LoadBalancer(Envoy Gateway, Istio, Cilium, NGINX Gateway Fabric, Traefik) ou répartiteur managé piloté par un contrôleur (AWS Load Balancer Controller vers ALB ou NLB, avec ses ressourcesLoadBalancerConfiguration,TargetGroupConfigurationetListenerRuleConfiguration; GKE). Le choix déplace la terminaison TLS, l'adresse client, le pare-feu applicatif et le coût. - Prérequis. Cilium exige
kubeProxyReplacement=trueet le proxy L7 ; Istio peut servir de contrôleur Gateway API sans les fonctions de maillage ; GKE installe les CRD avec l'option--gateway-api=standard. - Gestion des CRD. Globales au cluster, elles imposent une seule version et un seul canal pour toutes les implémentations ; certains rapports portent sur le canal expérimental, choix qui engage toute la plateforme.
- Politiques pour les lacunes. Envoy Gateway :
SecurityPolicy(basicAuth,extAuth,oidc,jwt,authorization) etBackendTrafficPolicy(rateLimit,requestBuffer,responseOverride). NGINX Gateway Fabric 2.7 :ClientSettingsPolicy(body.maxSize),RateLimitPolicy,AuthenticationFilteretSnippetsFilter. Traefik : middlewares référencés par un filtreExtensionRef, avec le fournisseur de CRD Kubernetes activé. - Exploitation. Cadence de versions, compétences, maillage existant, observabilité : le nouveau contrôleur est un composant de plus à maintenir.
La décision s'écrit avec son critère déterminant : lignes de l'inventaire couvertes, rapport consulté (version, canal, mode) et politique retenue pour chaque lacune.
Migrer en parallèle et basculer nom d'hôte par nom d'hôte
Architecture cible minimale
Gateway API sépare les rôles qu'Ingress confondait : l'équipe plateforme possède la Gateway, ses écouteurs et donc les certificats ; les équipes applicatives possèdent leurs HTTPRoute, dans leurs namespaces, et ne s'attachent qu'aux écouteurs qui les y autorisent (voir l'article « Platform engineering pour une scale-up »). Le manifeste reprend l'exemple d'un Ingress qui retirait le préfixe /v1 par une expression régulière de chemin et rewrite-target: /$2, rappelés en commentaire ; sa structure a été contrôlée contre le schéma OpenAPI des CRD standard v1.6.2.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: publique
namespace: edge
spec:
gatewayClassName: classe-de-l-implementation # fournie par l'implémentation retenue
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
exposition: publique
- name: https-api
protocol: HTTPS
port: 443
hostname: api.example.com
tls:
mode: Terminate
certificateRefs:
- name: api-example-com-tls # Secret dans le namespace edge
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
exposition: publique
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
namespace: boutique # namespace portant le label exposition: publique
spec:
parentRefs:
- name: publique
namespace: edge
sectionName: https-api
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /v1
filters:
- type: URLRewrite # remplace rewrite-target: /$2 sur /v1(/|$)(.*)
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api
port: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-vers-https
namespace: boutique
spec:
parentRefs:
- name: publique
namespace: edge
sectionName: http
hostnames:
- api.example.com
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301 # redirection de schéma : niveau Extended ; 308, défaut d'ingress-nginx, également ExtendedPathPrefix compare des éléments de chemin entiers : /v1 correspond à /v1 et /v1/x, pas à /v1x, ce qui reproduit l'expression régulière d'origine, à la casse près : ingress-nginx acceptait aussi /V1/x. La redirection vers HTTPS (scheme) relève du niveau Extended, quel que soit le code ; le code 301 est Core, et conserver le 308 d'ingress-nginx suppose une implémentation qui déclare aussi cette fonction. Les en-têtes HSTS que posait ingress-nginx ne sont pas recréés : il faut les ajouter par ResponseHeaderModifier ou par politique. Les CRD, la GatewayClass et la Gateway relèvent de la couche plateforme du dépôt GitOps et doivent exister avant les routes : c'est l'ordonnancement traité dans l'article « Argo CD App-of-Apps et ApplicationSet », et la revue décrite dans « GitOps en production » s'applique aux HTTPRoute comme aux Ingress.
Certificats pendant la double exposition
cert-manager crée un certificat pour chaque écouteur HTTPS d'une Gateway annotée par cert-manager.io/issuer ou cert-manager.io/cluster-issuer, si l'écouteur porte un hostname, le mode Terminate et une référence de Secret dans le même namespace. La fonction, en bêta depuis cert-manager 1.15, s'active par config.gatewayAPI.enabled: true dans les valeurs Helm ; les CRD Gateway API doivent exister au démarrage de cert-manager, sinon il faut le redémarrer.
Pendant la double exposition, un défi HTTP-01 suit le DNS public, donc aboutit à l'ancien contrôleur tant que le nom d'hôte n'a pas basculé. Deux options évitent l'impasse : un solveur DNS-01, ou la réutilisation du Secret existant, copié ou référencé depuis un autre namespace par une ReferenceGrant. Attendre la bascule pour émettre imposerait une coupure TLS. Autre piège : un ClusterIssuer dont le solveur déclare http01.ingress.ingressClassName: nginx cessera de renouveler les certificats le jour où ingress-nginx disparaît. Le solveur gatewayHTTPRoute, qui attache une HTTPRoute temporaire à une Gateway dotée d'un écouteur sur le port 80, doit le remplacer avant le décommissionnement.
Tester sur la nouvelle adresse sans toucher au DNS
La Gateway reçoit sa propre adresse externe, ce que le guide Gateway API destiné aux utilisateurs d'ingress-nginx recommande précisément pour valider la nouvelle configuration sans toucher au trafic de production. curl --resolve interroge le vrai nom d'hôte, avec le bon SNI et le bon en-tête Host, sur l'une ou l'autre adresse. Le script ci-dessous, à adapter, compare code de statut et cible de redirection pour chaque URL de l'inventaire.
#!/usr/bin/env bash
# urls.txt : une URL complète par ligne, tirée de l'inventaire
IP_ANCIENNE=203.0.113.10 # contrôleur ingress-nginx (exemple fictif)
IP_NOUVELLE=203.0.113.20 # nouvelle Gateway (exemple fictif)
while read -r url; do
host=$(printf '%s' "$url" | awk -F/ '{print $3}')
fmt='%{http_code} %{redirect_url}'
old=$(curl -s -o /dev/null -w "$fmt" --resolve "$host:443:$IP_ANCIENNE" "$url")
new=$(curl -s -o /dev/null -w "$fmt" --resolve "$host:443:$IP_NOUVELLE" "$url")
[ "$old" = "$new" ] || printf 'ECART %s\n ingress-nginx : %s\n gateway : %s\n' "$url" "$old" "$new"
done < urls.txtIl faut aussi comparer les en-têtes de réponse (HSTS, CORS), le seuil de rejet des corps volumineux, les requêtes longues au regard des délais, l'adresse client vue par l'application (X-Forwarded-For), les WebSockets, le gRPC et les chemins avec barre oblique finale ou segments ...
Piège d'outillage : si external-dns surveille déjà les sources gateway-httproute et apparentées, créer une HTTPRoute portant le nom d'hôte de production peut modifier l'enregistrement DNS avant la date prévue, donc basculer le trafic sans décision. Pendant la double exposition, il faut filtrer les routes prises en compte (drapeau --label-filter) ou tester sur un nom d'hôte temporaire.
Basculer : DNS, poids du répartiteur ou bascule franche
| Mécanisme | Granularité | Retour arrière | Point d'attention |
|---|---|---|---|
| DNS pondéré, si le fournisseur le propose | Par nom d'hôte, par paliers de poids | Remise des poids, au rythme des TTL et des caches des résolveurs | Réduire le TTL au moins une durée d'ancien TTL avant le premier palier |
| Poids sur un répartiteur, un CDN ou un pare-feu applicatif en amont | Par nom d'hôte, parfois par chemin | Immédiat, sans cache DNS | L'équipement amont doit joindre les deux adresses et conserver l'en-tête Host |
| Bascule franche par nom d'hôte | Tout le trafic d'un hôte d'un coup | Retour du DNS, borné par le TTL | Suffisant pour les hôtes internes ou à faible trafic, bien testés |
Bascule d'ingress-nginx vers Gateway API, nom d'hôte par nom d'hôte
Le schéma enchaîne l'inventaire, la traduction, le choix de l'implémentation, la double exposition, la bascule hôte par hôte avec retour arrière, puis le décommissionnement.
Schéma défilable horizontalement ; sa version textuelle complète suit.
Lire le schéma sous forme textuelle
- L'inventaire recense les Ingress, leurs annotations, la ConfigMap globale, les drapeaux et les couplages externes.
- ingress2gateway produit une traduction, puis chaque avertissement est revu.
- L'implémentation est choisie d'après son rapport de conformité.
- La Gateway est déployée en parallèle d'ingress-nginx, sur une nouvelle adresse.
- Chaque nom d'hôte est testé sur cette adresse avec curl --resolve.
- Si les comportements diffèrent, on revient à la traduction ; s'ils sont identiques, un nom d'hôte bascule par DNS ou par poids du répartiteur.
- En cas d'erreurs ou d'écarts après la bascule, le trafic revient vers ingress-nginx et la traduction est reprise.
- Sans erreur, on passe au nom d'hôte suivant, tant qu'il en reste.
- Quand tous ont basculé, une période de garde vérifie qu'ingress-nginx ne reçoit plus aucune requête.
- ingress-nginx est alors décommissionné.
Retour arrière
Le retour arrière suppose trois conditions : Ingress et contrôleur intacts pendant toute la double exposition ; critères de retour écrits avant la bascule avec les équipes applicatives (taux de 5xx, latence, erreurs d'authentification, volume de 404 par hôte) ; toute modification d'exposition portée par l'Ingress et la HTTPRoute dans la même demande de fusion, faute de quoi le retour ramène une configuration périmée. Avec une bascule DNS, le retour est progressif lui aussi, au rythme des TTL. La durée de la double exposition se fixe avant la première bascule, avec la condition qui y met fin.
Liste de contrôle de la bascule
Avant la première bascule :
- Inventaire complet : annotations, ConfigMap, drapeaux, Ingress sans classe, couplages cert-manager, external-dns, alertes.
- Chaque avertissement d'ingress2gateway traité ou accepté par écrit.
- Rapport de conformité relu pour la version et le mode déployés.
- Certificats présents sur la Gateway pour chaque nom d'hôte, solveurs ACME prêts.
- HSTS, redirections 308, limite de corps et délais reposés explicitement.
- Adresse client préservée jusqu'au proxy ; nouvelle adresse ajoutée aux listes d'autorisation des partenaires et des pare-feu.
- Journaux et métriques de la Gateway visibles par nom d'hôte.
- TTL DNS réduit, critères de retour arrière écrits, external-dns filtré.
Pendant la bascule :
- Un nom d'hôte à la fois, en commençant par le moins critique.
- Taux d'erreurs comparés par hôte entre les deux contrôleurs.
- Gel des modifications d'exposition non doublées.
Après la bascule :
- Journaux d'accès d'ingress-nginx vides pour les hôtes migrés sur toute la période de garde.
- Renouvellement d'un certificat testé par le nouveau solveur.
Décommissionner ingress-nginx et vérifier la plateforme
L'ordre de retrait
Le décommissionnement commence quand les journaux d'accès d'ingress-nginx ne montrent plus que des sondes et des robots. L'ordre évite les pannes différées : solveurs de cert-manager et sources d'external-dns d'abord, puis les objets Ingress (en vérifiant le sort de chaque Certificate que cert-manager avait créé pour eux), la classe d'entrée nginx et enfin la release Helm du contrôleur. Il reste à contrôler qu'aucune ValidatingWebhookConfiguration ingress-nginx-admission ne subsiste, que le Service LoadBalancer et son adresse sont libérés, et que les listes d'autorisation citant l'ancienne adresse sont nettoyées.
Deux garde-fous empêchent le retour du problème : une politique d'admission qui refuse tout nouvel Ingress de classe nginx et toute annotation nginx.ingress.kubernetes.io/*, et des modèles de charts applicatifs qui génèrent des HTTPRoute. Sans eux, le premier chart tiers installé réintroduit un Ingress que plus aucun contrôleur ne sert.
Ce qu'une vérification doit contrôler ensuite
Après la migration, la surface d'exposition se relit comme un nouveau composant. Premières preuves : version et canal des CRD installées, puis routes non acceptées ou aux références non résolues (filtre jq testé sur un export d'exemple).
# Version et canal des CRD Gateway API installées
kubectl get crd gateways.gateway.networking.k8s.io -o jsonpath='{.metadata.annotations}'
# Routes sans parent, non acceptées ou aux références non résolues
kubectl get httproute --all-namespaces -o json | jq -r '
.items[]
| . as $r
| if ((.status.parents // []) | length) == 0
then "\($r.metadata.namespace)/\($r.metadata.name)\taucun parent\t-"
else (.status.parents[].conditions[]
| select((.type == "Accepted" or .type == "ResolvedRefs") and .status != "True")
| "\($r.metadata.namespace)/\($r.metadata.name)\t\(.type)=\(.status)\t\(.reason)")
end'La grille de vérification complète :
- Plus aucun pod, classe d'entrée, webhook ni Ingress servi par ingress-nginx.
- CRD Gateway API homogènes (annotations
gateway.networking.k8s.io/bundle-versionetgateway.networking.k8s.io/channel), canal choisi et documenté. - Écouteurs publics sans
allowedRoutesenfrom: All, sauf décision explicite. - Chaque ReferenceGrant justifiée, puisqu'elle ouvre un accès entre namespaces.
- HSTS et versions TLS définis explicitement.
- Renouvellement des certificats prouvé par un renouvellement réel.
- Politiques propres à l'implémentation versionnées dans Git, avec un propriétaire nommé.
SnippetsFilteret ressources équivalentes réservés par RBAC à l'équipe plateforme.- Implémentation et CRD intégrées au calendrier de mises à jour de la plateforme.
Cette grille recoupe celle d'une revue indépendante de l'exposition d'un cluster, décrite sur la page audit Kubernetes. Quand la migration s'inscrit dans un chantier plus large sur la plateforme, la page Optimisation Kubernetes : mesurer, changer, vérifier présente ce cadre ; l'article « ECS ou EKS, migrer sans big bang » applique la même double exposition à un changement d'orchestrateur.
Sources officielles et limites de lecture
Les faits datés viennent du blog et de la documentation Kubernetes, des dépôts kubernetes/ingress-nginx, kubernetes-sigs/gateway-api et kubernetes-sigs/ingress2gateway et des documentations officielles des outils cités ; la lecture des rapports de conformité s'appuie sur les fichiers YAML publiés dans le dépôt Gateway API, pas sur la page de synthèse. Le nom « IngressNightmare » vient de la publication de Wiz Research, qui l'a donné.
Limites : les rapports de conformité vieillissent à chaque version de l'implémentation. Les équivalences de la table sont syntaxiques ; leur sémantique (délais, expressions régulières, normalisation des URL, comptage des limites de débit) dépend de l'implémentation et se prouve par des tests sur le trafic réel. Les modules ingress-nginx intégrés à des offres managées ou distributions suivent le calendrier de leur fournisseur, non traité ici. Aucune durée de migration type n'est donnée : elle dépend du nombre d'annotations, de snippets et d'hôtes, que l'inventaire sert à mesurer. La double exposition réduit le risque de coupure sans l'exclure : caches DNS, connexions longues, renouvellement des certificats et comportements non inventoriés peuvent encore produire des erreurs, que les critères de retour arrière servent à borner.
Sources
- Ingress NGINX Retirement: What You Need to Know, blog Kubernetes (SIG Network et Security Response Committee), 11/11/2025, https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/, consulté le 28/09/2026.
- Ingress NGINX: Statement from the Kubernetes Steering and Security Response Committees, blog Kubernetes, 29/01/2026, https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/, consulté le 28/09/2026.
- Before You Migrate: Five Surprising Ingress-NGINX Behaviors You Need to Know, blog Kubernetes, 27/02/2026, https://kubernetes.io/blog/2026/02/27/ingress-nginx-before-you-migrate/, consulté le 28/09/2026.
- Ingress-nginx CVE-2025-1974: What You Need to Know, blog Kubernetes (Security Response Committee), 24/03/2025, https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/, consulté le 28/09/2026.
- Ingress, documentation Kubernetes, https://kubernetes.io/docs/concepts/services-networking/ingress/, consulté le 28/09/2026.
- Kubernetes v1.37: Garhwal, blog Kubernetes, 26/08/2026, https://kubernetes.io/blog/2026/08/26/kubernetes-v1-37-release/, consulté le 28/09/2026.
- kubernetes/ingress-nginx, README et bannière d'archivage, GitHub, https://github.com/kubernetes/ingress-nginx, consulté le 28/09/2026.
- kubernetes/ingress-nginx, page des versions, GitHub, https://github.com/kubernetes/ingress-nginx/releases, consulté le 28/09/2026.
- ConfigMap (NGINX configuration), documentation ingress-nginx, https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/configmap.md, consulté le 28/09/2026.
- Annotations (NGINX configuration), documentation ingress-nginx, https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md, consulté le 28/09/2026.
- Command line arguments et Multiple Ingress controllers, documentation ingress-nginx, https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/cli-arguments.md et https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/multiple-ingress.md, consulté le 28/09/2026.
- kubernetes-sigs/gateway-api, README (ressources GA), GitHub, tag v1.6.2, https://github.com/kubernetes-sigs/gateway-api/tree/v1.6.2, consulté le 28/09/2026.
- kubernetes-sigs/gateway-api, page des versions, GitHub, https://github.com/kubernetes-sigs/gateway-api/releases, consulté le 28/09/2026.
- Gateway API, journaux de modifications 1.1, 1.2, 1.4, 1.5 et 1.6, GitHub, tag v1.6.2, https://github.com/kubernetes-sigs/gateway-api/tree/v1.6.2/CHANGELOG, consulté le 28/09/2026.
- Gateway API, CRD du canal standard v1.6.2 (HTTPRoute, Gateway, ReferenceGrant, ValidatingAdmissionPolicy safe-upgrades), GitHub, https://github.com/kubernetes-sigs/gateway-api/tree/v1.6.2/config/crd/standard, consulté le 28/09/2026.
- Migrating from Ingress, Gateway API, https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress/, consulté le 28/09/2026.
- A Welcome Guide for Ingress-NGINX Users, Gateway API, https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress-nginx/, consulté le 28/09/2026.
- Implementations, niveaux de conformité, Gateway API, https://gateway-api.sigs.k8s.io/docs/implementations/list/, consulté le 28/09/2026.
- Conformance reports v1.6 et règles de publication, Gateway API, https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports, consulté le 28/09/2026.
- kubernetes-sigs/ingress2gateway, README (v1.2.0), GitHub, https://github.com/kubernetes-sigs/ingress2gateway/blob/v1.2.0/README.md, consulté le 28/09/2026.
- kubernetes-sigs/ingress2gateway, CHANGELOG et page des versions, GitHub, https://github.com/kubernetes-sigs/ingress2gateway/blob/v1.2.0/CHANGELOG.md et https://github.com/kubernetes-sigs/ingress2gateway/releases, consulté le 28/09/2026.
- Ingress Nginx Provider (annotations prises en charge), ingress2gateway v1.2.0, https://github.com/kubernetes-sigs/ingress2gateway/blob/v1.2.0/pkg/i2gw/providers/ingressnginx/README.md, consulté le 28/09/2026.
- Emitters: Design and Governance, et code de l'émetteur Envoy Gateway (buffer.go, iprange.go), ingress2gateway v1.2.0, https://github.com/kubernetes-sigs/ingress2gateway/blob/v1.2.0/docs/emitters.md et https://github.com/kubernetes-sigs/ingress2gateway/tree/v1.2.0/pkg/i2gw/emitters/envoygateway, consulté le 28/09/2026.
- Annotated Gateway resource, documentation cert-manager, https://cert-manager.io/docs/usage/gateway/, consulté le 28/09/2026.
- ACME HTTP01, documentation cert-manager, https://cert-manager.io/docs/configuration/acme/http01/, consulté le 28/09/2026.
- Gateway sources, documentation external-dns, https://github.com/kubernetes-sigs/external-dns/blob/master/docs/sources/gateway.md, consulté le 28/09/2026.
- Migrate from Ingress-NGINX Controller to NGINX Ingress Controller, documentation F5 NGINX (dépôt nginx/documentation), https://github.com/nginx/documentation/blob/main/content/nic/install/migrate-ingress-nginx.md, consulté le 28/09/2026.
- Traefik Kubernetes Ingress NGINX et Kubernetes Gateway API (ExtensionRef), documentation Traefik (dépôt traefik/traefik), https://github.com/traefik/traefik/blob/master/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md et https://github.com/traefik/traefik/blob/master/docs/content/reference/routing-configuration/kubernetes/gateway-api.md, consulté le 28/09/2026.
- Envoy Gateway v1.9.2, types d'API SecurityPolicy et BackendTrafficPolicy, GitHub, https://github.com/envoyproxy/gateway/tree/v1.9.2/api/v1alpha1, consulté le 28/09/2026.
- NGINX Gateway Fabric v2.7.0, types d'API ClientSettingsPolicy, SnippetsFilter, RateLimitPolicy et AuthenticationFilter, GitHub, https://github.com/nginx/nginx-gateway-fabric/tree/v2.7.0/apis/v1alpha1, consulté le 28/09/2026.
- Gateway API Support, prérequis d'installation, documentation Cilium (dépôt cilium/cilium), https://github.com/cilium/cilium/blob/main/Documentation/network/servicemesh/gateway-api/installation.rst, consulté le 28/09/2026.
- Gateway API, documentation AWS Load Balancer Controller (dépôt kubernetes-sigs/aws-load-balancer-controller), https://github.com/kubernetes-sigs/aws-load-balancer-controller/tree/main/docs/guide/gateway, consulté le 28/09/2026.
- Deploying Gateways, documentation GKE, https://docs.cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways, consulté le 28/09/2026.
- CVE-2025-1974: The IngressNightmare in Kubernetes, Wiz Research, 24/03/2025, https://www.wiz.io/blog/ingress-nginx-kubernetes-vulnerabilities, consulté le 28/09/2026 (source du nom de la série uniquement).