Wat je aan het einde hebt
Een werkende Gateway API-setup (GatewayClass, Gateway, HTTPRoutes) die al het verkeer afhandelt dat ingress-nginx nu doet, met cert-manager die automatisch TLS-certificaten uitgeeft, en ingress-nginx volledig uitgefaseerd.
Vereisten
- Een Kubernetes-cluster op versie 1.29 of hoger (Gateway API v1.0 werd GA in Kubernetes 1.29)
kubectlgeconfigureerd met cluster-admin of vergelijkbare RBAC- Helm 3.x lokaal geinstalleerd
- Een bestaande ingress-nginx-deployment die je wilt vervangen
- cert-manager v1.16+ geinstalleerd (v1.20+ als je ListenerSet-ondersteuning nodig hebt voor multi-tenant TLS)
- DNS-toegang om A/AAAA/CNAME-records voor je domeinen bij te werken
- Kennis van Kubernetes Services en hoe L4/L7-verkeer in een cluster gerouteerd wordt
Waarom ingress-nginx is gestopt
SIG Network heeft de ingress-nginx-repository gearchiveerd op 24 maart 2026. Het project was sinds november 2025 in best-effort-onderhoud met slechts een of twee vrijwillige maintainers. De directe aanleiding was CVE-2025-1974 (IngressNightmare), een CVSS 9.8 unauthenticated RCE via de admission webhook die kon leiden tot volledige cluster-overname. In februari 2026 volgden nog vier HIGH-severity CVE's.
Nu de repository gearchiveerd is, worden toekomstige kwetsbaarheden simpelweg niet meer gepatcht. EOL-software in het L7-datapad levert bevindingen op bij SOC 2, PCI-DSS, ISO 27001 en HIPAA-audits. De officiele aanbeveling: migreer naar Gateway API.
Gateway API-concepten in 60 seconden
Gateway API vervangt de monolithische Ingress-resource door een rolgebaseerde hierarchie van drie resourcetypen:
| Resource | Scope | Wie beheert het | Doel |
|---|---|---|---|
| GatewayClass | Cluster | Infrastructuurprovider | Bepaalt welke controller Gateways beheert |
| Gateway | Namespace | Clusteroperator | Declareert listeners (poorten, protocollen, TLS), toegestane route-namespaces |
| HTTPRoute | Namespace | Applicatieontwikkelaar | Definieert host/pad-routing, filters, backend-targets |
Die scheiding zorgt ervoor dat operators de infrastructuur beheren (welke poorten open staan, welke TLS-certificaten gebruikt worden) en developers de routing (welke paden naar welke services gaan). Dezelfde HTTPRoute werkt op Envoy Gateway, NGINX Gateway Fabric, Cilium, Istio en andere conforme implementaties.
Stap 1: kies een Gateway API-implementatie
Je hebt een controller nodig die Gateway API-resources bewaakt en het data plane programmeert. Kies er een voor je begint met manifests converteren.
| Implementatie | Data plane | Geschikt voor |
|---|---|---|
| Envoy Gateway | Envoy Proxy | Hoogste conformance (v1.4.0 full), meest actieve community, beste ingress2gateway-emitterondersteuning |
| NGINX Gateway Fabric | NGINX OSS/Plus | Bekend NGINX data plane; logische keuze als je team NGINX-internals kent |
| Cilium | eBPF + Envoy | Draai je al Cilium als CNI; sterke L4-prestaties |
| Istio | Envoy (sidecar/ambient) | Draai je al Istio; wil je gecombineerde ingress + service mesh |
Voor de meeste teams die van ingress-nginx migreren zonder bestaande service mesh is Envoy Gateway de pragmatische keuze. Als je team diepgaande ervaring heeft met NGINX-configuratie en de kleinste gedragswijziging wil, is NGINX Gateway Fabric logischer.
De rest van deze handleiding gebruikt Envoy Gateway in de voorbeelden. De Gateway- en HTTPRoute-manifests zijn identiek ongeacht de implementatie; alleen de installatiestap en implementatiespecifieke policies (rate limiting, auth) verschillen.
Stap 2: installeer Gateway API CRDs en de controller
Installeer eerst de Gateway API CRDs, dan de controller.
# Installeer Gateway API v1.5.0 CRDs (standard channel)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml
Verwachte output:
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created
...
Installeer Envoy Gateway (v1.7.1):
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.7.1 \
-n envoy-gateway-system \
--create-namespace
# Wacht tot de controller klaar is
kubectl wait --timeout=5m \
-n envoy-gateway-system \
deployment/envoy-gateway \
--for=condition=Available
Controleer of de GatewayClass geaccepteerd is:
kubectl get gatewayclass
Verwachte output:
NAME CONTROLLER ACCEPTED AGE
envoy-gateway gateway.envoyproxy.io/gatewayclass-controller True 30s
ACCEPTED: True bevestigt dat de controller actief is en klaarstaat.
Stap 3: inventariseer je bestaande Ingress-resources
Voordat je iets gaat converteren, breng je in kaart wat ingress-nginx op dit moment afhandelt.
# Lijst alle Ingress-resources in alle namespaces
kubectl get ingress -A -o wide
# Exporteer annotations (dit is wat ingress2gateway moet vertalen)
kubectl get ingress -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {.metadata.annotations}{"\n"}{end}'
Bekijk de annotations. Markeer alles dat configuration-snippet, server-snippet, auth-url, limit-rps of modsecurity-* gebruikt. Daar is geen standaard Gateway API-equivalent voor; dat vereist handwerk (zie veelvoorkomende valkuilen hieronder).
Stap 4: converteer manifests met ingress2gateway
ingress2gateway v1.0 is de officiele SIG Network-migratietool. Het leest je Ingress-resources en genereert equivalente Gateway API-manifests.
# Installeer via Homebrew
brew install ingress2gateway
# Of via Go
go install github.com/kubernetes-sigs/ingress2gateway@v1.0.0
Genereer Gateway API-manifests ter review:
# Alle namespaces, uit het live cluster
ingress2gateway print --providers=ingress-nginx -A > gateway-resources.yaml
# Enkele namespace
ingress2gateway print --providers=ingress-nginx --namespace production > prod-gateway.yaml
# Vanuit lokale bestanden (geen clustertoegang nodig)
ingress2gateway print \
--input-file my-ingress.yaml,other-ingress.yaml \
--providers=ingress-nginx > gateway-resources.yaml
# Met Envoy Gateway-specifieke extensies
ingress2gateway print --providers=ingress-nginx --emitter envoy-gateway > eg-resources.yaml
Lees de output goed door. De tool print WARNING-regels voor elke annotation die hij niet kan vertalen. Elke warning is een handmatige conversietaak.
Wat ingress2gateway automatisch afhandelt
De tool ondersteunt 30+ ingress-nginx annotations met geverifieerde gedragsgelijkheid:
| ingress-nginx annotation | Gateway API-equivalent |
|---|---|
ssl-redirect |
HTTPRoute RequestRedirect-filter (scheme: https) |
rewrite-target |
HTTPRoute URLRewrite-filter (ReplacePrefixMatch) |
proxy-set-headers |
HTTPRoute RequestHeaderModifier-filter |
enable-cors + gerelateerd |
HTTPRoute CORS-filter (GA in v1.5) |
canary + canary-weight |
HTTPRoute backendRefs met weight |
canary-header |
HTTPRoute header-based matches |
proxy-read-timeout |
HTTPRoute timeouts.backendRequest |
backend-protocol: "HTTPS" |
BackendTLSPolicy |
backend-protocol: "GRPC" |
GRPCRoute |
ssl-passthrough |
TLSRoute met Passthrough-listener |
spec.tls[].secretName |
Gateway listener tls.certificateRefs |
Wat handmatige conversie vereist
configuration-snippet/server-snippet: directe NGINX-directiveinjectie. Daar bestaat geen Gateway API-equivalent voor. Zoek een native HTTPRoute-filter of herontwerp op applicatieniveau.limit-rps/limit-rpm/limit-connections: rate limiting is niet gestandaardiseerd. Gebruik een implementatiespecifieke policy (bijv. Envoy Gateway's BackendTrafficPolicy metrateLimit).auth-url: externe authenticatie. Gebruik een implementatiespecifieke SecurityPolicy met ExtAuth.modsecurity-*: WAF-regels. Vereist een aparte WAF-oplossing.
Stap 5: maak de Gateway-resource aan
De Gateway declareert je listeners. Dit is de resource die cert-manager annoteert voor automatische TLS.
# gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: envoy-gateway-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # activeert automatische TLS
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: https
hostname: "*.jouwbedrijf.nl" # pas aan naar je eigen domein
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-jouwbedrijf-tls # cert-manager maakt dit aan
allowedRoutes:
namespaces:
from: All
Apply en controleer de status:
kubectl apply -f gateway.yaml
kubectl describe gateway production-gateway -n envoy-gateway-system
# Zoek naar: Programmed: True, Accepted: True
Haal het nieuwe externe IP op:
kubectl get gateway production-gateway -n envoy-gateway-system \
-o jsonpath='{.status.addresses[*].value}{"\n"}'
Dit IP is je nieuwe load balancer-endpoint. Wijs DNS hier nog niet naartoe.
Stap 6: deploy HTTPRoutes
Apply de HTTPRoutes die ingress2gateway heeft gegenereerd (of je handmatige equivalenten). Een typische HTTPRoute:
# httproute-app.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app
namespace: production
spec:
parentRefs:
- name: production-gateway
namespace: envoy-gateway-system
sectionName: https # bind aan de HTTPS-listener
hostnames:
- app.jouwbedrijf.nl
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: my-app-service
port: 8080
Controleer na het applyen of de route geaccepteerd is:
kubectl describe httproute my-app -n production
Beide conditions moeten True zijn:
- Accepted: de Gateway-listener heeft deze route geaccepteerd
- ResolvedRefs: alle backend Services en Secrets bestaan en zijn bereikbaar
Als Accepted op False staat met reden NotAllowedByListeners, matcht de hostname niet met een Gateway-listener of is de namespace niet toegestaan. Als ResolvedRefs op False staat met reden RefNotPermitted, heb je een ReferenceGrant nodig in de doelnamespace.
Cross-namespace-references vereisen ReferenceGrant
Als je HTTPRoute in namespace production een Service in namespace shared-infra referenceert, wordt de referentie stilletjes genegeerd zonder een ReferenceGrant in de doelnamespace:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-routes-from-production
namespace: shared-infra
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: production
to:
- group: ""
kind: Service
Stap 7: configureer cert-manager voor Gateway API
De Gateway API-integratie van cert-manager werkt anders dan de Ingress-integratie. Bij Ingress bewaakte cert-manager Ingress-resources. Bij Gateway API bewaakt het Gateway-resources omdat TLS op listerniveau geconfigureerd wordt.
Schakel Gateway API-ondersteuning in
Als cert-manager geinstalleerd is zonder de Gateway API-featurevlag, upgrade dan:
helm upgrade cert-manager oci://quay.io/jetstack/charts/cert-manager \
--namespace cert-manager \
--set config.enableGatewayAPI=true \
--set crds.enabled=true
Heb je de Gateway API CRDs na cert-manager geinstalleerd? Herstart dan de deployment zodat de nieuwe CRD-types opgepikt worden:
kubectl rollout restart deployment/cert-manager -n cert-manager
Configureer de ACME-issuer voor Gateway API
De ClusterIssuer moet de gatewayHTTPRoute-solver gebruiken in plaats van de Ingress-gebaseerde solver:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: admin@jouwbedrijf.nl
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: production-gateway
namespace: envoy-gateway-system
kind: Gateway
cert-manager maakt een tijdelijke HTTPRoute aan voor de ACME HTTP-01-challenge, valideert deze via de HTTP-listener van de Gateway, en verwijdert hem weer. De Gateway moet een HTTP-listener (poort 80) hebben die routes uit de cert-manager-namespace toestaat (of from: All).
Controleer of certificaten worden uitgegeven:
kubectl get certificates -n envoy-gateway-system
# READY-kolom moet True tonen
Stap 8: test tegen het Gateway-IP
Beide controllers draaien nu. ingress-nginx handelt nog steeds productieverkeer af; de nieuwe Gateway heeft een eigen IP. Test de Gateway zonder DNS aan te raken.
GATEWAY_IP=$(kubectl get gateway production-gateway -n envoy-gateway-system \
-o jsonpath='{.status.addresses[0].value}')
# Basisrouting
curl -v --resolve "app.jouwbedrijf.nl:443:$GATEWAY_IP" \
https://app.jouwbedrijf.nl/
# HTTPS-redirect (moet 301 teruggeven)
curl -v -H "Host: app.jouwbedrijf.nl" http://$GATEWAY_IP/ \
-o /dev/null -w "%{http_code}\n"
# TLS-certificaatcheck
echo | openssl s_client -servername app.jouwbedrijf.nl \
-connect $GATEWAY_IP:443 2>/dev/null | openssl x509 -noout -subject
Vergelijk responses tussen het oude en nieuwe endpoint:
INGRESS_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
diff \
<(curl -s --resolve "app.jouwbedrijf.nl:443:$INGRESS_IP" https://app.jouwbedrijf.nl/) \
<(curl -s --resolve "app.jouwbedrijf.nl:443:$GATEWAY_IP" https://app.jouwbedrijf.nl/)
Als diff geen output geeft, zijn de responses identiek. Test elke route, niet alleen de hoofdroute.
Stap 9: knip DNS over
Zodra parallelle tests zijn afgerond, wijs je DNS naar het nieuwe Gateway-IP.
24 uur voor de switch, verlaag je de DNS TTL naar 60 seconden:
# Controleer huidige TTL
dig app.jouwbedrijf.nl +short +ttlid
Pas de TTL aan via het controlepaneel van je DNS-provider. Wacht een volledige originele-TTL-cyclus tot de caches verlopen zijn.
Voer de cutover uit: werk het A/AAAA-record bij naar $GATEWAY_IP. Met 60 seconden TTL resolven de meeste clients het nieuwe IP binnen enkele minuten.
Monitor tijdens propagatie: verwacht verkeer op beide endpoints gedurende de TTL-drain. Let op:
- HTTP 4xx/5xx-foutpercentage (moet op baseline blijven)
- TLS-handshakefouten (certificaatmismatch als cert-manager nog niet klaar is)
- Latentiepercentilen (p50, p95, p99)
Rollback
Gaat er iets mis, wijs DNS dan terug naar het ingress-nginx-IP. Met 60 seconden TTL is de rollback binnen een minuut klaar. De oude Ingress-resources zijn nog actief en ingress-nginx draait gewoon nog.
Stap 10: decommission ingress-nginx
Laat ingress-nginx minimaal 24 tot 48 uur draaien na de DNS-cutover als rollbackverzekering. Na de validatieperiode:
# Verwijder Ingress-resources (label ze tijdens de migratie voor makkelijke opruiming)
kubectl delete ingress -A -l migrated-to-gateway=true
# Verwijder ingress-nginx
helm uninstall ingress-nginx -n ingress-nginx
# Ruim de namespace op
kubectl delete namespace ingress-nginx
Veelvoorkomende valkuilen
Stille route-afwijzing. De meest verwarrende foutmodus. Een HTTPRoute wordt succesvol geapplied (kubectl apply geeft geen fout), maar er stroomt geen verkeer. Draai altijd kubectl describe httproute <naam> en check de statusconditions. Veelvoorkomende redenen: hostname matcht geen listener (NotAllowedByListeners), ontbrekende ReferenceGrant voor cross-namespace-referenties (RefNotPermitted), of een backend Service die niet bestaat (BackendNotFound).
cert-manager negeert Gateway-annotations. De Gateway API-ondersteuning van cert-manager vereist de config.enableGatewayAPI=true-vlag. Zonder die vlag negeert cert-manager stilletjes alle Gateway-annotations. Als je Gateway API CRDs na cert-manager hebt geinstalleerd, is ook een herstart van de cert-manager-deployment nodig.
Geen configuration-snippet-vervanging. Als je ingress-nginx-setup leunt op configuration-snippet voor directe NGINX-directiveinjectie, bestaat daar geen Gateway API-equivalent voor. Elk snippet moet individueel geanalyseerd worden. Sommige kun je vervangen door native HTTPRoute-filters (headermanipulatie, redirects, rewrites). Andere vereisen implementatiespecifieke policies zoals Envoy Gateway's EnvoyPatchPolicy. Sommige vereisen aanpassingen op applicatieniveau.
Rate limiting is niet portabel. De limit-rps-, limit-rpm- en limit-connections-annotations hebben geen standaard Gateway API-equivalent. Rate limiting is altijd implementatiespecifiek. Als je later van Gateway API-implementatie wisselt, moet je je rate limiting-configuratie herschrijven.
CRD-versieconflicten. Het installeren van Gateway API v1.5.x CRDs kan Istio 1.28/1.29 laten CrashLoopBackOffen door API-veldmismatches. Draai je die Istio-versies, gebruik dan Gateway API v1.4.0 CRDs en upgrade Istio eerst.
Multi-tenant TLS-selfservicegat. Bij ingress-nginx kon elk team hun eigen Ingress-resource annoteren om TLS-certificaten van cert-manager te krijgen. Met een gedeelde Gateway kunnen alleen operators listeners en TLS-configuratie aanpassen. ListenerSet (experimenteel in cert-manager 1.20, stabiel verwacht in 1.21/1.22) herstelt per-team TLS-autonomie. Plan voor meer operatorbetrokkenheid in de tussenperiode.
DNS-propagatie is niet instant. Zelfs met 60 seconden TTL cachen sommige resolvers agressief. Zowel ingress-nginx als de Gateway moeten productieverkeer correct afhandelen gedurende het overlapvenster. Verwijder nooit Ingress-resources of stop ingress-nginx voordat DNS-propagatie voltooid is.
Controleer of de migratie compleet is
Je weet dat de migratie geslaagd is als:
kubectl get ingress -Ageen resources meer teruggeeft (alles verwijderd)kubectl get httproute -Aalle routes toont metAccepted: TrueenResolvedRefs: Truekubectl get certificates -n envoy-gateway-systemalle certificaten toont metREADY: True- Applicatiemonitoring bevestigt dat latentie en foutpercentages op baseline zitten
- De ingress-nginx-namespace niet meer bestaat
- DNS-records naar het Gateway-IP wijzen met productie-TTL's hersteld
Wanneer escaleren
Als je vastloopt na het volgen van deze handleiding, verzamel dan het volgende voordat je om hulp vraagt:
- Output van
kubectl get gateway,httproute,referencegrant -A -o yaml - Output van
kubectl describe gateway <naam> -n <namespace> - Output van
kubectl describe httproute <naam> -n <namespace>(de statusconditions zijn doorslaggevend) - cert-manager-logs:
kubectl logs -n cert-manager deployment/cert-manager --tail=100 - Gateway-controllerlogs (voor Envoy Gateway:
kubectl logs -n envoy-gateway-system deployment/envoy-gateway --tail=100) - De geinstalleerde Gateway API CRD-versie:
kubectl get crd gateways.gateway.networking.k8s.io -o jsonpath='{.metadata.labels.gateway\.networking\.k8s\.io/bundle-version}' - Je Kubernetes-versie:
kubectl version --short