Wat je gaat leren
Aan het eind van deze tutorial kun je kiezen tussen rolling updates, blue-green en canary op basis van een helder risicoprofiel, en kun je beide geavanceerde strategieën op twee manieren implementeren: eerst native met kubectl (zonder extra controllers), daarna met Argo Rollouts v1.9.0 zodra je voorbij wat de native primitives kunnen bent. Je ziet ook waar Gateway API in past, en welke overwegingen (sessies, caches, databasemigraties, kosten) een cutover in de praktijk echt om zeep helpen.
Wat ik aanneem dat je al weet
Deze tutorial gaat ervan uit dat je een zero-downtime rolling update al kunt opzetten en weet wat maxSurge, maxUnavailable, readiness probes en een preStop-hook doen. Klinken die termen nieuw? Begin daar dan eerst.
Je hebt nodig:
kubectlverbonden met een Kubernetes 1.29+ cluster (1.29 levert Gateway API v1.0 als stable extension; de rolling-update-secties werken op 1.26+)- Een applicatie verpakt als
Deploymentmet minimaal 2 replicas en een werkende readiness probe - Cluster-permissies om
Service,Ingressen (optioneel)HTTPRouteenRolloutresources te maken
Wanneer kies je rolling, blue-green of canary
Elke strategie maakt een andere afweging tussen kosten, blast radius en rollback-snelheid. De eerlijke samenvatting:
| Criterium | Rolling update | Blue-green | Canary |
|---|---|---|---|
| Native Kubernetes-support | Ingebouwd (Deployment.spec.strategy) |
Alleen patroon (label-switch) | Alleen patroon (replica-ratio of ingress-weight) |
| Resource-overhead tijdens rollout | Laag (maxSurge extra pods) |
2x (volledige tweede omgeving) | Laag (kleine canary-pool) |
| Tijd tot nieuwe versie zichtbaar | Geleidelijk, automatisch | Atomic switch | Geleidelijk, gecontroleerd |
| Rollback-snelheid | Traag (oude image opnieuw uitrollen) | Direct (selector terugzetten) | Snel (weight op 0 zetten) |
| Oud en nieuw draaien tegelijk | Ja, kort | Nee (na de switch) | Ja, by design |
| Geschikt voor | De meeste stateless HTTP-services | High-stakes releases die instant rollback nodig hebben | Releases die op echt verkeer gevalideerd moeten worden |
| Lastig met | Releases die instant rollback vereisen | Schemawijzigingen die backwards compatibility breken | Alles met sticky sessions die je niet backend-affine kunt maken |
Mijn standpunt: begin gewoon met een rolling update. Ga pas naar canary als je echt op productieverkeer wilt valideren voor een volledige uitrol. Ga pas naar blue-green als instant rollback meer waard is dan de verdubbelde kosten. De meeste teams kiezen canary of blue-green omdat het professioneel klinkt, en gebruiken die rollback-knop vervolgens nooit. Dat is gewoon dure complexiteit.
Blue-green native met kubectl
Native Kubernetes heeft geen BlueGreen-strategie. Het patroon bestaat uit twee aparte Deployment-resources achter één Service, waarbij je de Service.spec.selector omklapt om verkeer te switchen.
Stap 1: Deploy de blue-versie
# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
spec:
replicas: 4
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: registry.internal/myapp:v1.4.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
Toepassen:
kubectl apply -f blue-deployment.yaml
Stap 2: Maak de Service die blue selecteert
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: blue # de live versie
ports:
- port: 80
targetPort: 8080
kubectl apply -f service.yaml
De selector van de Service bepaalt welke pods verkeer krijgen. Op dit moment alle vier de blue-pods.
Stap 3: Deploy de green-versie ernaast
De green Deployment is identiek, op het label version: green en de nieuwe image na:
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
spec:
replicas: 4
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: registry.internal/myapp:v1.5.0 # de nieuwe release
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
kubectl apply -f green-deployment.yaml
kubectl rollout status deployment/myapp-green --timeout=5m
De green-pods draaien nu wel, maar krijgen nog geen productieverkeer, omdat de Service nog steeds version: blue selecteert. Dit is het moment om smoke tests tegen de green-pods te draaien, ofwel via een aparte preview-Service, ofwel met kubectl port-forward naar een green-pod.
Checkpoint: controleer dat beide versies draaien
kubectl get pods -l app=myapp
Verwachte output:
NAME READY STATUS RESTARTS AGE
myapp-blue-7d9f4c8b6c-abcde 1/1 Running 0 12m
myapp-blue-7d9f4c8b6c-fghij 1/1 Running 0 12m
myapp-blue-7d9f4c8b6c-klmno 1/1 Running 0 12m
myapp-blue-7d9f4c8b6c-pqrst 1/1 Running 0 12m
myapp-green-5b8c2d1f9d-uvwxy 1/1 Running 0 2m
myapp-green-5b8c2d1f9d-zabcd 1/1 Running 0 2m
myapp-green-5b8c2d1f9d-efghi 1/1 Running 0 2m
myapp-green-5b8c2d1f9d-jklmn 1/1 Running 0 2m
Acht pods, vier blue actief, vier green idle.
Stap 4: De cutover met één selector-patch
kubectl patch service myapp \
-p '{"spec":{"selector":{"app":"myapp","version":"green"}}}'
Dit is de cutover. De Service-controller werkt de EndpointSlices bij, kube-proxy op elke node ververst zijn forwarding-regels en binnen een paar seconden gaan nieuwe verbindingen alleen nog naar green-pods. Bestaande verbindingen op blue-pods lopen netjes af tot ze sluiten.
Waarom dit werkt: het selector-veld van een Service is muteerbaar. Het wijzigen ervan komt vanuit het cluster gezien neer op een nieuwe Service deployen, alleen blijven het Service-IP en de DNS-naam intact. Dat is het hele mechanisme.
Stap 5: Direct rollbacken als het mis is
kubectl patch service myapp \
-p '{"spec":{"selector":{"app":"myapp","version":"blue"}}}'
Dit is precies waarom je deze strategie kiest. Een slechte release wordt een no-op rollback met één commando, omdat de blue-pods nooit zijn weggegaan.
Stap 6: Decommissie blue zodra green stabiel is
Na een afgesproken soak window (meestal 15 tot 30 minuten gezond productieverkeer) schaal je blue af:
kubectl scale deployment myapp-blue --replicas=0
Houd het Deployment-object van blue nog een paar releases beschikbaar, zodat een rollback ná de volgende deploy ook nog kan. Bij de volgende release wordt blue de nieuwe green, enzovoorts.
Canary met NGINX Ingress weight-annotaties
Canary werkt anders. In plaats van een atomic switch route je een klein percentage van het verkeer naar de nieuwe versie, kijk je naar de metrics, en verhoog je het gewicht naarmate je vertrouwen groeit.
Belangrijk: ingress-nginx is retired. De
kubernetes/ingress-nginx-repo is op 24 maart 2026 read-only gearchiveerd. Hij werkt nog wel, maar krijgt geen bugfixes of CVE-patches meer. De Kubernetes Steering en Security Response Committees adviseren te migreren naar Gateway API of een andere onderhouden ingress-controller. Het voorbeeld hieronder staat erin omdat het in 2026 nog steeds het meest uitgerolde canary-mechanisme is (Datadog hield ingress-nginx op het moment van retirement op ongeveer 50% van alle cloud-native omgevingen). Gebruik het om het patroon te begrijpen, maar plan je migratie.
Stap 1: Deploy de stable- en canary-versie
Twee Deployments met verschillende version-labels, net als bij blue-green:
# stable-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-stable
spec:
replicas: 6
selector:
matchLabels:
app: myapp
version: stable
template:
metadata:
labels:
app: myapp
version: stable
spec:
containers:
- name: app
image: registry.internal/myapp:v1.4.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-stable
spec:
selector:
app: myapp
version: stable
ports:
- port: 80
targetPort: 8080
# canary-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
spec:
replicas: 1 # kleine canary-pool
selector:
matchLabels:
app: myapp
version: canary
template:
metadata:
labels:
app: myapp
version: canary
spec:
containers:
- name: app
image: registry.internal/myapp:v1.5.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: myapp-canary
spec:
selector:
app: myapp
version: canary
ports:
- port: 80
targetPort: 8080
Elke versie heeft zijn eigen Service. Dat is de voorwaarde voor traffic splitting.
Stap 2: Definieer de stable Ingress
# stable-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
spec:
ingressClassName: nginx
rules:
- host: app.example.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-stable
port:
number: 80
Stap 3: Definieer de canary Ingress met een weight-annotatie
De canary-feature van NGINX Ingress vereist een tweede Ingress op dezelfde host, gemarkeerd als canary:
# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "5" # 5% van het verkeer
spec:
ingressClassName: nginx
rules:
- host: app.example.internal
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-canary
port:
number: 80
canary-weight is het percentage random requests dat naar de canary-Service gaat. De precedence-volgorde is eerst canary-by-header, dan canary-by-cookie, dan canary-weight. Gecombineerd kun je daarmee bijvoorbeeld "stuur ons QA-team altijd naar de canary, plus 5% van iedereen" bouwen zonder codewijzigingen.
Stap 4: Promoten in stappen
Het hele punt van canary is dat je het gewicht alleen verhoogt als de metrics meegaan. Een typisch schema:
# Begin op 5% voor 10 minuten, kijk naar error rate en latency
kubectl annotate ingress myapp-canary \
nginx.ingress.kubernetes.io/canary-weight=25 --overwrite
# Na nog 10 minuten
kubectl annotate ingress myapp-canary \
nginx.ingress.kubernetes.io/canary-weight=50 --overwrite
# Daarna 100, op dat punt zit al het verkeer op de nieuwe versie
kubectl annotate ingress myapp-canary \
nginx.ingress.kubernetes.io/canary-weight=100 --overwrite
Zodra de canary op 100% staat, swap je de image van de stable-Deployment naar de nieuwe versie, schaal je de canary terug naar 1 replica met een placeholder, en ben je klaar voor de volgende release.
Wat deze canary niet kan
Een mislukte canary rolt zichzelf niet terug. Als de error rate piekt op 25% weight, moet jij het opmerken en handmatig terugdraaien. Geen metric-loop, geen analyse, geen automatisering. Dat gat vult Argo Rollouts.
Beide strategieën automatiseren met Argo Rollouts
Argo Rollouts (huidige stable v1.9.0, uitgebracht op 20 maart 2026) vervangt de Deployment-resource door een Rollout-CRD. Het beheert dezelfde ReplicaSets en Services, maar voegt step-based progressive delivery, geïntegreerde traffic routing en analyse-gestuurde automatische rollback toe.
Waarom je het zou willen: bij de canary hierboven moet je naar Grafana zitten staren terwijl je kubectl annotate met de hand draait. Argo Rollouts kijkt voor jou naar Prometheus, verhoogt het gewicht automatisch zolang de SLO's gehaald worden, en rolt terug op het moment dat ze breken.
Blue-green met Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: registry.internal/myapp:v1.5.0
ports:
- containerPort: 8080
strategy:
blueGreen:
activeService: myapp-active # productie-Service
previewService: myapp-preview # interne Service om green te testen
autoPromotionEnabled: false # vereis handmatige `kubectl argo rollouts promote`
scaleDownDelaySeconds: 600 # houd blue 10 min in leven na de cutover
De activeService is de Service die gebruikers raken. De previewService wijst naar de green-pods tot de promotion. Met autoPromotionEnabled: false gebeurt de cutover pas wanneer je kubectl argo rollouts promote myapp draait. De scaleDownDelaySeconds (default 30) houdt blue lang genoeg in leven om verkeer te laten draineren en rollback instant te houden.
Canary met Argo Rollouts en analysis
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 6
strategy:
canary:
canaryService: myapp-canary
stableService: myapp-stable
trafficRouting:
nginx:
stableIngress: myapp # hergebruikt het Ingress-patroon hierboven
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate # rollback als de AnalysisTemplate faalt
- setWeight: 25
- pause: { duration: 10m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: registry.internal/myapp:v1.5.0
Gekoppeld aan een AnalysisTemplate die Prometheus bevraagt op het succespercentage van requests:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
value: myapp-canary
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring.svc:9090
query: |
sum(rate(
nginx_ingress_controller_requests{
service="NaN",
status!~"5.."
}[2m]
)) /
sum(rate(
nginx_ingress_controller_requests{
service="NaN"
}[2m]
))
Zakt het succespercentage drie samples lang onder de 99%, dan breekt de Rollout af, gaat het verkeer terug naar de stable-versie en komt de Rollout in Degraded status. Dit is de enige configuratie in dit artikel waarbij een mislukte canary zichzelf automatisch terugrolt. Native Kubernetes en de bare NGINX Ingress canary hierboven vereisen allebei menselijke interventie.
Gateway API: de toekomst van native traffic splitting
Het langetermijnantwoord is Gateway API, dat op 31 oktober 2023 GA ging met v1.0 naast Kubernetes 1.29. De HTTPRoute-resource ondersteunt weighted backends out of the box, zonder annotaties of extra controller (anders dan een Gateway-implementatie zelf):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp
spec:
parentRefs:
- name: my-gateway
hostnames:
- app.example.internal
rules:
- backendRefs:
- name: myapp-stable
port: 80
weight: 95
- name: myapp-canary
port: 80
weight: 5
De backendRefs-weights zijn proportioneel, geen percentages. Met weights 95 en 5 krijgt de canary 5% van het verkeer; met 9 en 1 krijgt-ie 10%. Default weight is 1.
In 2026 is dit waar ik nieuwe clusters zou bouwen. Het haalt de afhankelijkheid van controller-specifieke annotatie-vocabulaire weg, en Argo Rollouts ondersteunt al trafficRouting via de Gateway API-plugin, dus progressive delivery blijft gewoon werken.
Trafficshift-overwegingen: sessies, caches, databasemigraties
Dit is het stuk dat meer cutovers om zeep helpt dan welke controller-fout dan ook.
Sessies. Blue-green gaat ervan uit dat een gebruiker zonder problemen naar elke omgeving gerouteerd kan worden. Sla je sessiestate op in pod-geheugen, dan logt de cutover iedereen uit op het moment dat de selector flipt. Verplaats sessies naar Redis, Memcached of een databasetabel voordat je blue-green gebruikt. Hetzelfde geldt voor canary: een klein deel van de gebruikers ping-pongt op elke request tussen stable en canary, dus alle in-memory sessiestate gaat zich voor hen vreemd gedragen.
Caches. Beide strategieën leggen koude caches bloot. Na een blue-green cutover raken alle cache-warming requests in één keer de nieuwe pods. Bij rolling updates gebeurt dat geleidelijk; bij een harde switch kan het database-load opdrijven tot het op een outage lijkt. Of warm de green-omgeving voor met een klein percentage verkeer (via een analysis-pause in Argo Rollouts), of ship met cache-aside patronen die degraderen op een cold miss.
Databasemigraties. Dit is het echte plafond. Blue-green werkt alleen als beide versies dezelfde database veilig kunnen lezen en schrijven. Schemawijzigingen die een kolom droppen of hernoemen breken die afspraak. De discipline is het expand/contract migratiepatroon: maak elke schemawijziging eerst additief (expand), deploy de nieuwe code, en verwijder pas in een latere release de oude kolom (contract). Kun je niet zonder die kolom-drop in één release? Dan is blue-green het verkeerde gereedschap. Gebruik een onderhoudsvenster of een rolling update met een feature flag die het schemagebruik gaten houdt.
Long-lived connections. WebSockets, gRPC streams en SSE leven langer dan een typische terminationGracePeriodSeconds. Beide strategieën vragen daar expliciete aanpak, vaak een server-side close bij rollout, gecombineerd met een client die automatisch herverbindt.
Kostenimpact van twee volledige omgevingen
Blue-green kost ruwweg 2x compute tijdens het soak window. Bij 20 replicas van 4 GB elk is dat 80 GB extra geheugen dat het cluster moet hosten. Op managed Kubernetes vertaalt zich dat direct naar node-pool grootte en factuur.
Een paar eerlijke mitigaties:
- Gebruik
previewReplicaCountvan Argo Rollouts om green op een lager replica-aantal te draaien tijdens preview, en pas vlak voor de cutover op te schalen. - Vertrouw erop dat de cluster autoscaler de spare capaciteit snel evict zodra blue is gedecommissioneerd. Hoe sneller je blue na het soak window afschaalt, hoe minder de kosten in je maandfactuur opduiken.
- Voor niet-kritieke services: kies canary in plaats van blue-green. Eén canary-pod naast 6 stable-pods kost ~17% extra, geen 100%.
Voor de meeste interne services is de meerprijs van blue-green de instant rollback niet waard. Bewaar het voor revenue-kritische paden en releases met een grote blast radius.
Wat blue-green en canary niet zijn
Drie misverstanden duiken bij elke code review wel weer op:
"Blue-green betekent twee permanente clusters." Nee. Het patroon werkt op elke schaal: twee Deployments achter één Service in dezelfde namespace is de meest voorkomende vorm. Multi-cluster blue-green bestaat, maar dat is een extreme variant voor disaster recovery, niet de baseline. De clustergrens is irrelevant; wat telt is dat twee parallelle sets pods één Service-identiteit delen.
"Canary deployments vereisen een service mesh." Niet waar. Een service mesh (Istio, Linkerd) geeft je precieze per-request routing en rijkere observability, wat canaries veiliger maakt. Maar replica-ratio canaries (1 canary-pod naast 9 stable-pods) werken op elk cluster, en ingress-weight canaries werken op elk cluster met een ondersteunde ingress-controller. De mesh is een nice-to-have, geen voorwaarde.
"Een mislukte canary rolt automatisch terug." Alleen als er een controller voor je meekijkt. De bare NGINX Ingress canary in Stap 3 hierboven doet dat niet. Native Kubernetes doet dat niet. Argo Rollouts met een AnalysisTemplate, of Flagger, of een service-mesh-integratie plus een SLO-controller, doet dat wel. Staat er in jullie runbook "we gebruiken canary deployments dus mislukte releases rollen automatisch terug" en draai je geen van die tools? Dan klopt jullie runbook niet.
Wat je hebt geleerd
De strategie die je kiest is een functie van de rollback-snelheid die je nodig hebt versus de kosten die je kunt dragen. Rolling updates zijn de default. Blue-green koopt instant rollback voor twee keer de compute. Canary koopt traffic-gevalideerde promotion voor de prijs van één extra pod plus de operationele discipline om bij elke stap naar metrics te kijken.
Native Kubernetes kan alle drie, maar specifiek voor canary stoppen de native primitives net voor automatic rollback. Dat is de grens waar Argo Rollouts zijn complexiteit terugverdient. En Gateway API is waar deze hele stack heen gaat, zodra ingress-nginx echt achter ons ligt.
Waar je hierna kunt verder lezen
- De fundamenten onder elke uitrol: Kubernetes rolling updates en zero-downtime deployments.
- De CI/CD-kant van progressive delivery: Kubernetes CI/CD met GitHub Actions.
- De GitOps-controller die natuurlijk samenwerkt met Argo Rollouts: GitOps met Argo CD.