Doel
Aan het eind van dit artikel heb je een werkende Ingress-resource die extern HTTP/HTTPS-verkeer routeert naar een of meer backend-Services via host-based en path-based rules, met automatische TLS-certificaten via cert-manager.
Vereisten
- Een Kubernetes-cluster op v1.28 of nieuwer met
kubectl-toegang en rechten om Ingress-, IngressClass- en Secret-objecten aan te maken - Minimaal een applicatie achter een ClusterIP Service. Ingress routeert naar Services, niet rechtstreeks naar pods. Ben je niet bekend met Service-typen? Het gelinkte artikel behandelt ClusterIP, NodePort, LoadBalancer en wanneer je welk type kiest.
- Een Ingress controller geinstalleerd in het cluster (zie volgende sectie). Zonder controller doet een Ingress-resource niets.
- Voor automatische TLS: cert-manager v1.16+ met een werkende ClusterIssuer. De cert-manager-documentatie beschrijft de installatie.
- Helm 3.x lokaal geinstalleerd (voor de controller-installatievoorbeelden)
- DNS-records die je domein(en) naar het externe IP van de Ingress controller wijzen
Ingress vs Service LoadBalancer
Ingress werkt op OSI Layer 7 (HTTP/HTTPS). Een enkele Ingress controller zit achter een cloud load balancer en routeert verkeer naar meerdere backend-Services op basis van hostnaam en URL-pad. Een Service van het type LoadBalancer werkt op Layer 4 (TCP/UDP) en maakt per Service een aparte cloud load balancer aan.
Het kostenverschil loopt snel op. Tien LoadBalancer-Services betekent tien cloud load balancers, elk met een eigen IP en maandelijkse kosten. Op AWS kost een NLB zo'n $16/maand nog voor data transfer. Ingress brengt alle HTTP/HTTPS-routing samen achter een enkele load balancer.
| Criterium | LoadBalancer Service | Ingress |
|---|---|---|
| Protocol | TCP, UDP, elk | Alleen HTTP/HTTPS |
| Routinglogica | Geen (IP + poort) | Host-based, path-based |
| TLS-terminatie | Nee (pass-through of elders) | Ja, aan de edge |
| Cloud LBs aangemaakt | Een per Service | Een totaal |
| Toepassing | Non-HTTP-protocols, enkele service | Meerdere HTTP-services achter een IP |
Gebruik LoadBalancer voor non-HTTP-protocols (raw TCP, UDP, gRPC zonder HTTP-transcoding). Gebruik Ingress voor alles wat HTTP/HTTPS is.
IngressClass en controller-selectie
Een IngressClass is een cluster-scoped resource die een Ingress koppelt aan een specifieke controller. Zonder IngressClass kan een cluster met meerdere controllers niet bepalen welke controller een bepaalde Ingress moet verwerken.
Stap 1: installeer een Ingress controller
De Kubernetes API definieert de Ingress-resource, maar levert geen controller mee. Die moet je zelf installeren. Voor clusters die ingress-nginx gebruiken: de community controller (kubernetes/ingress-nginx) is in maart 2026 gearchiveerd en krijgt geen beveiligingspatches meer. Kies voor nieuwe deployments een actief onderhouden controller.
Gangbare keuzes per april 2026:
- Traefik (traefik.io): dynamische service discovery, ingebouwde Let's Encrypt-ondersteuning, eenvoudige migratie vanuit ingress-nginx
- HAProxy Ingress (haproxy-ingress.github.io): hoogste ruwe doorvoer in gepubliceerde benchmarks (~42.000 req/s vs ~19.000 voor Traefik), zero-downtime reloads, HTTP/3 native
- Contour (projectcontour.io): Envoy-based, goede Gateway API-ondersteuning
- AWS Load Balancer Controller: cloud-native op EKS
Voorbeeld-installatie (Traefik via Helm):
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik --create-namespace
Controleer of de controller draait:
kubectl get pods -n traefik
# Verwacht: traefik-<hash> in Running-status
Stap 2: controleer de IngressClass
De meeste Helm charts maken automatisch een IngressClass aan. Check:
kubectl get ingressclass
Verwachte output:
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 2m
Heeft de IngressClass van je controller de annotatie ingressclass.kubernetes.io/is-default-class: "true", dan gebruiken Ingress-resources zonder ingressClassName automatisch deze class. In clusters met meerdere controllers zet je ingressClassName altijd expliciet op elke Ingress.
Stap 3: verwijs naar de IngressClass in je Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
spec:
ingressClassName: traefik # moet overeenkomen met de IngressClass-naam
rules: [...]
Is de IngressClass-naam fout of ontbreekt die, en is er geen default ingesteld? Dan negeert de controller de Ingress volledig. Geen foutmelding, geen event, gewoon stilte. Dit is de meest voorkomende setupfout.
Host-based routing
Host-based routing inspecteert de Host-HTTP-header en stuurt verkeer naar verschillende Services op basis van de hostnaam.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host
namespace: production
spec:
ingressClassName: traefik
rules:
- host: app.staging.infra.example.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: frontend
port:
number: 80
- host: api.staging.infra.example.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: backend-api
port:
number: 8080
Requests naar app.staging.infra.example.com gaan naar de frontend-Service. Requests naar api.staging.infra.example.com gaan naar backend-api.
Kubernetes ondersteunt single-level wildcard hosts: *.example.com matcht app.example.com en api.example.com, maar niet example.com (bare domain) of foo.bar.example.com (twee niveaus diep).
Een rule zonder host-veld is een catch-all die alle inkomende requests matcht, ongeacht de hostnaam.
Path-based routing en pathType
Binnen een host-blok routeren path-rules requests naar verschillende backends op basis van het URL-pad. Elke path-rule vereist een pathType-veld (verplicht sinds Kubernetes v1.18).
Prefix (meest gebruikt)
Matcht het pad element-voor-element. /api matcht /api, /api/ en /api/v1/users, maar niet /apiv1. De grens is altijd een /-karakter.
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
Exact
Matcht alleen het letterlijke pad. /api matcht /api, maar niet /api/ (trailing slash) en niet /api/v1.
- path: /healthz
pathType: Exact
backend:
service:
name: health-service
port:
number: 8080
Gebruik Exact spaarzaam. Een gebruiker of load balancer die een trailing slash toevoegt, breekt de match.
ImplementationSpecific
Laat de matching over aan de controller. Met ingress-nginx en de annotatie nginx.ingress.kubernetes.io/use-regex: "true" wordt het pad behandeld als een POSIX extended reguliere expressie.
Let op: use-regex inschakelen op een willekeurige Ingress voor een bepaalde host forceert case-insensitive regex matching op alle paden voor die host, ook paden op andere Ingress-resources. Dit is een cluster-breed neveneffect dat Exact- en Prefix-matching voor andere services op dezelfde host kan breken.
Padprioriteit
Als meerdere rules matchen, wint het langste matchende pad. Exact gaat voor Prefix bij gelijke padlengte.
Gecombineerd voorbeeld
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-routing
namespace: production
spec:
ingressClassName: traefik
rules:
- host: app.staging.infra.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-api
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Requests naar /api/v1/orders matchen de /api-Prefix-rule en gaan naar backend-api. Al het andere valt door naar de /-rule en gaat naar frontend.
Controleer of routing werkt:
kubectl describe ingress app-routing -n production
# Bekijk de "Rules"-sectie voor correcte host/path/backend-mappings
curl -H "Host: app.staging.infra.example.com" http://<ingress-controller-ip>/api/health
# Verwacht: response van backend-api
TLS-terminatie
TLS-terminatie betekent dat de Ingress controller HTTPS ontsleutelt aan de edge. Backend-Services ontvangen plain HTTP, wat certificaatbeheer centraliseert en cryptografische overhead van applicatiepods wegneemt.
Stap 1: maak een TLS Secret aan
Het Secret moet in dezelfde namespace staan als de Ingress. Het bevat tls.crt (PEM-certificaatketen) en tls.key (PEM-private key).
kubectl create secret tls app-tls-cert \
--cert=fullchain.pem \
--key=privkey.pem \
-n production
Stap 2: verwijs naar het Secret in de Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
namespace: production
spec:
ingressClassName: traefik
tls:
- hosts:
- app.staging.infra.example.com
secretName: app-tls-cert
rules:
- host: app.staging.infra.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Elke host in spec.tls[].hosts moet ook in spec.rules staan. De Ingress API ondersteunt TLS alleen op poort 443.
Controleer of TLS actief is:
kubectl describe ingress tls-ingress -n production
# Zoek naar "TLS: app-tls-cert terminates app.staging.infra.example.com"
curl -v https://app.staging.infra.example.com/
# Verwacht: TLS-handshake met het juiste certificaat
Stap 3: automatiseer certificaten met cert-manager
Handmatig certificaten beheren schaalt niet. cert-manager let op Ingress-resources met de juiste annotations, maakt automatisch Certificate-objecten aan, haalt certificaten op via ACME (Let's Encrypt) HTTP-01 of DNS-01 challenges, en verlengt ze voor het verlopen.
Voeg de cert-manager-annotatie en een secretName toe die cert-manager aanmaakt en bijhoudt:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # verwijst naar je ClusterIssuer
spec:
ingressClassName: traefik
tls:
- hosts:
- app.staging.infra.example.com
secretName: app-auto-tls # cert-manager maakt dit Secret aan
rules:
- host: app.staging.infra.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Controleer of het certificaat is uitgegeven:
kubectl get certificate -n production
# Verwacht: app-auto-tls met READY=True
kubectl describe certificate app-auto-tls -n production
# Check "Status > Conditions" voor "Ready: True" en de vervaldatum
Blijft het certificaat in een niet-ready status hangen, bekijk dan de cert-manager logs en de Challenge-resource:
kubectl get challenges -n production
kubectl logs -n cert-manager deploy/cert-manager
Veelgebruikte ingress-nginx annotations
Als je de community ingress-nginx controller draait (of de opvolger van NGINX Inc.), passen annotations met het prefix nginx.ingress.kubernetes.io/ het gedrag per Ingress aan. Deze zijn controller-specifiek en werken niet met Traefik, HAProxy of andere controllers.
Deze sectie behandelt de annotations die ik het vaakst tegenkom in productie. Een volledige referentie staat in de ingress-nginx annotation-documentatie.
Routing en rewrites
| Annotatie | Doel | Voorbeeld |
|---|---|---|
rewrite-target |
Herschrijf het pad voor forwarding; ondersteunt capture groups | /$2 |
use-regex |
Schakel POSIX regex-padmatching in | "true" |
backend-protocol |
Protocol naar de backend: HTTP, HTTPS, GRPC, GRPCS | "HTTPS" |
Een gangbaar rewrite-patroon om een padprefix te strippen:
metadata:
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: app.staging.infra.example.com
http:
paths:
- path: /app1(/|$)(.*) # capture group $2 = rest van het pad
pathType: ImplementationSpecific
backend:
service:
name: app1
port:
number: 80
Een request naar /app1/login komt bij de backend aan als /login.
Request-afhandeling
| Annotatie | Doel | Default | Voorbeeld |
|---|---|---|---|
proxy-body-size |
Max request body; retourneert 413 bij overschrijding | 1m |
"50m" |
proxy-read-timeout |
Upstream read-timeout (seconden) | 60 |
"300" |
proxy-connect-timeout |
Connectie-timeout naar upstream (seconden) | 5 |
"30" |
Rate limiting
| Annotatie | Doel | Voorbeeld |
|---|---|---|
limit-rps |
Requests per seconde per IP | "20" |
limit-connections |
Gelijktijdige connecties per IP | "10" |
Per-replica-valkuil. Rate limit-waarden gelden per ingress-nginx replica. Drie replica's met limit-rps: "10" laat 30 requests per seconde toe van een enkel IP. Houd hier rekening mee bij autoscaling.
TLS-besturing
| Annotatie | Doel | Voorbeeld |
|---|---|---|
ssl-redirect |
Schakel de automatische HTTP-naar-HTTPS redirect in/uit (default: "true" bij TLS) |
"false" |
force-ssl-redirect |
Forceer redirect ook als TLS extern wordt getermineerd (bijv. AWS ELB) | "true" |
IP-allowlisting
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,203.0.113.5/32"
Requests van IP's buiten deze CIDR-ranges krijgen een 403.
Wanneer migreren naar Gateway API
Gateway API is de officiele opvolger van de Ingress API. Het bereikte v1.0 GA in oktober 2023 en staat op v1.4.0 sinds november 2025. HTTPRoute, Gateway en GatewayClass zitten allemaal in het Standard-kanaal met backward-compatibiliteitsgaranties.
De belangrijkste architectuurverbetering: Gateway API scheidt infrastructuur (Gateway, beheerd door clusteroperators) van routing (HTTPRoute, beheerd door developers). Bij Ingress zijn beide zorgen samengevoegd in een enkele resource, wat dwingt tot ofwel te brede RBAC of tickets heen en weer tussen teams.
Migreer nu als:
- Je kubernetes/ingress-nginx (community) draait. De repository is in maart 2026 gearchiveerd. Er volgen geen beveiligingspatches meer.
- Je een nieuw cluster opzet. Er zijn geen migratiekosten.
- Je geavanceerde routing nodig hebt: traffic splitting, header-based matching, request mirroring of retries. Dit zit ingebouwd in de HTTPRoute-spec van Gateway API en is niet beschikbaar in standaard Ingress.
Migreer op je eigen tempo als:
- Je een actief onderhouden controller gebruikt (Traefik, HAProxy, Contour) die Gateway API al ondersteunt als parallelle optie. Ingress- en Gateway API-resources kunnen naast elkaar bestaan.
De ingress2gateway-tool (v1.0, maart 2026) converteert Ingress-manifests naar Gateway API-resources en ondersteunt 30+ gangbare ingress-nginx annotations. Voor een volledige migratie-walkthrough, zie de handleiding voor migratie van ingress-nginx naar Gateway API.
Compleet voorbeeld: productie-klare Ingress
Dit manifest brengt host-based routing, path-based routing, TLS met cert-manager en gangbare annotations samen:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: production-ingress
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
nginx.ingress.kubernetes.io/limit-rps: "25"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.staging.infra.example.com
- api.staging.infra.example.com
secretName: production-tls
rules:
- host: app.staging.infra.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- host: api.staging.infra.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1
port:
number: 8080
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: api-v2 # standaard naar de nieuwste versie
port:
number: 8080
Veelvoorkomende problemen
| Symptoom | Waarschijnlijke oorzaak | Oplossing |
|---|---|---|
| Ingress bestaat maar verkeer komt nooit aan | Geen controller geinstalleerd, of ingressClassName is fout/ontbreekt |
kubectl get ingressclass en controleer of de naam klopt |
| 404 op paden die zouden moeten matchen | Verkeerde pathType (meestal Exact waar Prefix nodig is) |
Schakel over naar Prefix voor sub-tree routing |
| TLS-handshake faalt met "secret not found" | TLS Secret in een andere namespace dan de Ingress | Verplaats het Secret naar de Ingress-namespace |
| 502 Bad Gateway | Backend-Service heeft geen ready endpoints | kubectl get endpoints <service> om pod-IPs te controleren |
| Rate limits voelen te hoog | limit-rps is per replica, niet cluster-breed |
Deel de gewenste limiet door het aantal replica's |
use-regex: "true" breekt andere services op dezelfde host |
Regex-modus geldt voor alle paden op die host | Isoleer Ingress-resources met regex op aparte hosts |
Voor diepere diagnose bekijk je de controller-logs:
kubectl logs -n <controller-namespace> deploy/<controller-deployment>