De laatste tijd heb ik gewerkt aan de oplossing voor een probleem dat al een tijdje in de lucht hing. In de wereld van Kubernetes en netwerken staat een interessant moment op punt van beginnen. Het open-source project ingress-nginx houdt ermee op, en wel per maart 2026. Het is één van de belangrijkste projecten in het Kubernetes ecosysteem, dat in menig cluster toegepast wordt om inkomend verkeer naar de juiste Pod te leiden. Het einde van het project werd in november 2025 aangekondigd op de officiële Kubernetes website:

Ingress NGINX Retirement: What You Need to Know
By Tabitha Sable (Kubernetes SRC) | Tuesday, November 11, 2025
https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/

In de blog wordt een probleem uitgelegd dat zich voordoet bij een groot aantal open-source projecten, het gebrek aan vrijwilligers:

“Despite the project’s popularity among users, Ingress NGINX has always struggled with insufficient or barely-sufficient maintainership. For years, the project has had only one or two people doing development work, on their own time, after work hours and on weekends.”

Voor de ontwikkelaars achter ingress-nginx is het niet langer vol te houden om het project in leven te houden. Ze zeggen achter de feiten aan te lopen wat betreft binnenkomende bugmeldingen en verzoeken voor nieuwe features.

Hoewel in de blog staat beschreven dat het na maart 2026 mogelijk blijft de Ingress-controller te installeren en te gebruiken, is dat geen ideale situatie voor productie-clusters. Het is van belang bij de tijd te blijven met updates, om zo min mogelijk kwetsbaarheden te hebben waarvan hackers misbruik kunnen maken. Daarom moesten we een migratie voorbereiden.

Een goed begin is het halve werk: de strategie

Omdat een dergelijke migratie de ruggengraat van je infrastructuur raakt, geldt hier: een goed begin is het halve werk. De eerste fase bestond daarom uit een breed marktonderzoek. Er zijn diverse alternatieven gewogen op basis van populariteit, de kwaliteit van de documentatie en de voorspelde eenvoud van de migratie.

Ik ben op zoek gegaan naar een ingress controller die geschikt is voor langdurig gebruik in onze Kubernetes clusters en die de deur opent [Jv1] voor het uiteindelijke gebruik van de Gateway API. Dat is een experimenteel ingress controller project dat een prominent plekje heeft gekregen in het Kubernetes ecosysteem. Daarnaast hecht ik waarde aan een zo vloeiend mogelijke overgang voor de ontwikkelaars in onze teams, dus projecten die voorzien in handige hulpmiddelen hebben een streepje voor.

Traefik kwam hierbij als winnaar uit de bus. Deze ingress controller kan voor dezelfde doeleinden ingezet worden als ingress-nginx. Traefik beschikt ook over een compatibility-module voor ingress-nginx, die speciaal ontworpen is voor het aanstaande migratieprobleem dat veel cluster admins zullen hebben. Er is zelfs een aparte documentatiepagina die beschrijft hoe men nagenoeg moeiteloos kan omschakelen van ingress-nginx naar Traefik:

https://doc.traefik.io/traefik/migrate/nginx-to-traefik

Met deze documentatie zou de migratie van de ingress controllers toch zeker moeten lukken. Het migratieplan dat ik al ongeveer in mijn hoofd had, werd hiermee bevestigd:

  1. Installeer Traefik als 2e ingress controller naast ingress-nginx
  2. Controleer dat Traefik goed werkt door requests te proberen
  3. Verander DNS-instellingen voor alternatieve routering van verkeer
  4. Verwijder ingress-nginx nadat Traefik alles heeft overgenomen

Bij dit plan moet opgemerkt worden dat Traefik’s compatibility-module niet volledig dekkend is. Voor sommige zaken moeten dus enkele extra stappen worden gedaan, als ze van toepassing zijn op jouw situatie. Verder ligt de migratie van ingress-nginx naar Traefik voor de hand en zou het relatief eenvoudig moeten zijn. Bijna te mooi om waar te zijn. Ik ben er dan ook voortvarend mee aan de slag gegaan.

Spoiler: Marco Borsato had gelijk, de meeste dromen zijn bedrog.

De Realisatie: Pulumi, Helm en de DNS-ontdekking

Om Traefik in onze omgeving te landen, wilde ik trouw blijven aan onze Infrastructure-as-Code (IaC) filosofie. Traefik biedt een uitstekende Helm Chart aan, wat de integratie in ons bestaande Pulumi project zeer eenvoudig maakte. Met een paar regels TypeScript konden we de controller declaratief uitrollen en direct koppelen aan onze Azure-infrastructuur.

Tijdens het configureren van de DNS-records in Pulumi stuitte ik op een interessant detail in de Azure DNSZone resource. Voorheen zag ik een A-record altijd als een statische 1-op-1 koppeling tussen een domeinnaam en een IP-adres. Echter, Azure staat toe om een lijst van IPv4-adressen mee te geven aan een enkele RecordSet.

Het blijkt dat dit geen unieke Azure-feature is, maar onderdeel van de standaard werking van DNS. Het werd daarmee mogelijk om verkeer achter hetzelfde domein te routeren naar 2 verschillende (Azure) loadbalancers. Door beide IP-adressen (van zowel de oude NGINX- als de nieuwe Traefik-controller) in het A-record te zetten, gingen clients deze adressen round-robin gebruiken.

Dit was de doorbraak die ik nodig had voor een veilige migratie. In plaats van een “Big Bang” waarbij we de DNS volledig zouden omzetten en maar moesten hopen dat alles werkte, konden we nu het verkeer verdelen. Als Traefik een fout maakte, was de kans 50% dat een refresh bij de gebruiker alsnog via NGINX liep en wel werkte. Dit gaf me de nodige ademruimte om de logs te analyseren en de configuratie te verfijnen terwijl het Kubernetes cluster stabiel bleef. De ontwikkelaars zagen zo nu en dan een onverwachte statuscode voorbijkomen, maar konden hun werk normaal blijven doen.

Voor een heel groot deel van de ingresses [JV2] [RS3] [JV4] werkte de module van Traefik uitstekend. Het verkeer werd netjes verdeeld over de 2 controllers en stroomde vlot door het netwerk. Omdat we op de productie-omgeving slechts 1 niet-standaard ingress hadden, besloten we om met deze constructie live te gaan. Dit ging geruisloos en alles bleef prima werken. De ene niet-standaard service deed het ook goed, zolang jouw requests de ingress-nginx route volgden. Dat was acceptabel; de service wordt niet heel intensief gebruikt. Ondertussen moesten we wel aan de slag met de laatste, toch wel vrij essentiële, loodjes.

Toch niet zo compatible: Authenticatie en 401-fouten

Eén van de speerpunten in mijn plan was het gebruik van de providers.kubernetesIngressNginx module van Traefik. Deze module is specifiek ontworpen om de transitie te versoepelen door de bestaande ingress-class “nginx” te blijven ondersteunen, zelfs nadat de NGINX-controller uit het cluster is verwijderd. In theorie zou Traefik de bestaande annotaties simpelweg overnemen. Dat klinkt als de gedroomde moeiteloze migratie, maar hier kwamen de eerste barsten in de droom.

Het grootste probleem deed zich voor bij onze authenticatie. Voor onze applicaties maken we intensief gebruik van oauth2-proxy. Bij ingress-nginx regel je dat met twee specifieke annotaties:

  1. nginx.ingress.kubernetes.io/auth-url: Om te checken of een gebruiker is ingelogd.
  2. nginx.ingress.kubernetes.io/auth-signin: Om de gebruiker naar de juiste loginpagina te sturen als dat niet het geval is.

Na het inschakelen van de compatibility-module bleek de harde realiteit: Traefik ondersteunde wel de auth-url, maar deed helemaal niets met de auth-signin.

Het resultaat was een eindeloze stroom aan 401 Unauthorized errors. Omdat de tweede annotatie werd genegeerd, wist de Ingress-controller niet dat hij de gebruiker door moest sturen naar de OAuth-loginpagina. Het resultaat van de OAuth2 Proxy, een open-source product dat we voor authenticatie gebruiken, werd rechtstreeks doorgestuurd naar de client. Inloggen was simpelweg onmogelijk geworden. De compatibility-module, die de migratie “moeiteloos” moest maken, was op dit cruciale punt dus niet compatibel genoeg.

De oplossing: Middleware Chains

Om dit op te lossen moest ik afstappen van de NGINX-annotaties en de logica vertalen naar Traefik’s eigen taal: Middlewares.

Ik heb een zogenaamde Middleware Chain opgezet waarin twee componenten samenwerken:

  • forwardAuth: Deze neemt de taak van de auth-url over en controleert de sessie bij de oauth2-proxy.
  • errors: Deze vangt de 401-statuscode op en verzorgt de redirect naar de login-pagina (de vervanger voor auth-signin).

Het was een flinke puzzel om dit exact goed te krijgen, maar uiteindelijk werkte het vlekkeloos. Er zat echter één addertje onder het gras: deze geavanceerde configuratie werkte alleen als ik de ingressClassName van de Ingress-objecten direct aanpaste naar traefik.

De hoop om de Ingress-objecten van de ontwikkelaars volledig ongemoeid te laten via de compatibility-module moest ik dus deels laten varen voor onze beveiligde apps. Hierbij moet worden opgemerkt dat de traefik-module prima werkte voor annotatie-loze ingresses. [Jv5] [RS6] 

Architectuurverschillen

Het probleem met de ontbrekende redirect naar de loginpagina had een diepere technische oorzaak. Het zit hem in de manier waarop de controllers omgaan met responses van de authenticatie-proxy.

De ingress-nginx is proactief in het omgaan met responses: als de auth-url een 401 teruggeeft, inspecteert de controller dit resultaat en onderneemt actie op basis van de auth-signin annotatie. Het onderschept de fout en stuurt de gebruiker direct door als de juiste configuratie beschikbaar is.

Traefik werkt anders. Traefik is ontworpen om transparant te zijn; het geeft in de basis gewoon door wat er terugkomt van de backend of de auth-proxy. Als de proxy een 401 geeft, laat Traefik die 401 simpelweg zien aan de gebruiker. Met de Middleware Chain van Traefik kon ik de “slimme” actie van NGINX nabootsen.

Er worden verschillende kant-en-klare Middlewares aangeboden door Treafik, die ik opnam in de chain. De eerste is de ForwardAuth middleware. Als de middleware wordt geactiveerd, stuurt Treafik het request eerst door naar de authenticatie-proxy. Geeft deze een 200 resultaat, dan is de gebruiker ingelogd. Een request resulterend in 401 stuurt Traefik simpelweg terug naar de gebruiker als zijnde “niet ingelogd”. De configuratie van de Middleware is erg eenvoudig; je geeft alleen het forward address op.

In gevallen waar de gebruiker al ingelogd was, werkte dit prima. De authenticatie-proxy levert een 200 resultaat op, waarna de gebruiker doorgaat naar de bestemming. De niet-ingelogde gebruikers kregen een lege pagina met “Unauthorized” te zien. Deze foutmelding was te verhelpen door een tweede Middleware toe te voegen.

De Errors middleware van Traefik is een handig stukje configuratie dat automatisch in werking treedt als de juiste statuscode langskomt. Door een “query” op te geven, weet Traefik wat het moet doen in die gevallen. Dit betekent voor de authenticatie dat de middleware wordt geactiveerd zodra blijkt dat de gebruiker niet ingelogd is. In die gevallen sturen we de gebruiker naar de inlogpagina. We hebben dit uitgebreid getest op de ontwikkelomgeving, waar bleek dat het ontzettend goed werkt.

Wel blijft van toepassing dat deze annotations alleen werken op ingresses van de class “traefik”. Die ingresses moesten dus meteen worden omgebouwd voordat we afscheid konden nemen van de ingress-nginx.

De CORS-puzzel: Veiligheid boven Gemak

Net toen ik dacht dat de authenticatie-flow stabiel was, liep ik tegen het volgende struikelblok aan: CORS (Cross-Origin Resource Sharing). In onze testomgevingen gebruikten we voorheen vaak de bekende wildcard (*) als allow-origin. Dat is makkelijk, want dan accepteert de Ingress verkeer van elke bron.

Echter blijkt Traefik een stuk strikter in het handhaven van de officiële webstandaarden dan NGINX. Ik kwam er op de harde manier achter dat een wildcard (*) als origin niet is toegestaan als er ook credentials in de request worden meegestuurd.

Dit werd duidelijk bij onze OpenTelemetry endpoints. Voor het verzamelen van traces en metrics gebruiken we Basic Auth[Jv7] [RS8] , wat betekent dat credentials over de lijn gaan. Omdat we de access-control-allow-credentials header op true moesten zetten voor deze verzoeken, weigerde Traefik (en vervolgens de browser) elke request waarbij de origin op * stond.

De oplossing was wederom een specifieke Middleware waarbij ik de toegestane origins expliciet moest opsommen in de accessControlAllowOriginList. Geen wildcards meer dus en dat is eigenlijk wel zo veilig.

De Ontknoping: Het afscheid van ingress-nginx (en een onverwachte verdwijning)

Na dagen van onderzoeken, finetunen, policy-updates en het configureren van de Middleware chains, was het moment daar: de “duale situatie” mocht ten einde komen. Het verkeer op alle omgevingen liep stabiel via de DNS round-robin [Jv9] [Jv10] [RS11] en de Traefik-logs lieten geen enkele onverwachte 401 of 404 meer zien. Het was tijd voor de laatste stap van het migratieplan: het definitief verwijderen van ingress-nginx.

Om op safe te spelen, had ik het advies uit de migratie-documentatie van Traefik opgevolgd. Daar werd aangeraden om de IngressClass van NGINX te voorzien van de annotatie “helm.sh/resource-policy: keep”. De gedachte hierachter: de controller-software mag verdwijnen, maar de definitie van de class moet blijven bestaan. Zo kan de kubernetesIngressNginx module van Traefik de bestaande Ingress-resources blijven herkennen en afhandelen onder de oude naam.

Toen deze wijziging werd toegepast in onze Infrastructure-as-Code oplossing Pulumi, gebeurde er iets onverwachts. Ondanks de keep-policy uit de documentatie, werd de IngressClass resource tijdens de opschoning van de NGINX-stack simpelweg verwijderd. Ineens “kenden” de bestaande Ingressen hun class niet meer.

Binnen enkele seconden kregen gebruikers een waarschuwing in hun browser: er werd een Internal Traefik Fake Certificate geserveerd. Omdat de koppeling tussen de Ingress en de controller via de verdwenen class was verbroken, wist Traefik niet meer welke TLS-certificaten hij moest presenteren en viel hij terug op zijn standaard (niet-vertrouwde) certificaat. Dit maakte de ontwikkelomgeving tijdelijk onwerkbaar voor de collega’s.

Gelukkig was de oorzaak snel duidelijk. Zodra ik de IngressClass met behulp van Pulumi weer had toegevoegd aan het cluster, pikte Traefik de draad direct weer op. Dankzij de actieve kubernetesIngressNginx module herkende de controller de Ingress-objecten onmiddellijk en werden de juiste certificaten weer netjes uitgeserveerd. De hartslag kon weer omlaag en de ontwikkelaars door met hun werk.

Met deze laatste hobbel achter de rug, was de weg vrij naar de hogere omgevingen. Dankzij de lessen uit de eerste fase konden we de deployments die ingress-nginx verwijderden op Acceptatie en Productie met veel meer zelfvertrouwen uitvoeren.

In Pulumi pasten we de DNS-records aan naar het definitieve IP-adres van de Traefik LoadBalancer, waardoor het oude NGINX-adres langzaam uit de internet-caches verdween. De migratie was voltooid: we zijn nu volledig overgestapt op een moderne, door de community gedragen controller, waarbij we de belofte om geruisloos te zijn voor de ontwikkelaars grotendeels hebben kunnen waarmaken.

Conclusie: Wat hebben we geleerd?

De gedroomde “moeiteloze migratie” bleek inderdaad bedrog; de praktijk is altijd weerbarstiger dan de documentatie belooft. Het was meer dan alleen het vervangen van een component; het was een waardevol leertraject op het gebied van Kubernetes-netwerken. Hoewel de weg naar Traefik wat meer voeten in de aarde had dan de gemiddelde tutorial doet vermoeden, heeft het proces ons een veel robuustere setup opgeleverd. Het resultaat: een modern platform dat klaar is voor de toekomst en regelmatig security patches binnenkrijgt. Ik heb er veel van geleerd.

De belangrijkste lessen die ik graag deel met iedereen die voor een vergelijkbare uitdaging staat:

  • Bereid je grondig voor: Begin niet direct met installeren, maar inventariseer eerst al je bestaande Ingress-objecten. Door alle annotaties in kaart te brengen, identificeer je de mogelijke valkuilen.
  • DNS als vangnet: Het feit dat een A-record simpelweg meerdere IP-adressen kan bevatten, was voor mij een eye-opener. Het stelt je in staat om een duale situatie te creëren waarbij je het verkeer “round-robin” verdeelt. Dit geeft je de ruimte om in alle rust te finetunen.[JV12] [RS13] [JV14] 
  • Verdiep je in netwerk-debugging: Een migratie als deze dwingt je om diep in de HTTP-headers en response-codes te duiken. Ik heb ontzettend veel geleerd over hoe verschillende controllers omgaan met authenticatie-responses en browser-beveiliging. Met het command “curl” kun je ontzettend veel doen.
  • Vier ogen zien meer dan twee: Hoe goed je voorbereiding ook is, een extra paar ogen bij de cruciale stappen (zoals het de-installeren van de oude controller) is altijd een goed idee. Anderen stuiten op situaties die je misschien zelf gemist hebt. Laat je helpen door de mensen voor wie je het uiteindelijk doet.

Ik hoop dat deze ervaringen je helpen als je zelf de overstap van ingress-nginx naar Traefik (of een andere ingress controller) gaat maken. Het is even wat werk, maar het resultaat mag er zijn. Veel succes!