Kubernetes blue-green en canary deployment-strategieën

Rolling updates dekken de meeste stateless services prima af, maar geven je geen instant rollback en geen mogelijkheid om een nieuwe versie aan 5% van het verkeer te tonen voor de volledige cutover. Blue-green en canary deployments wel. Deze tutorial loopt beide patronen door, eerst native met kubectl, daarna met Argo Rollouts en Gateway API erbovenop, zodat je precies ziet wat elk extra stuk gereedschap je oplevert.

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:

  • kubectl verbonden 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 Deployment met minimaal 2 replicas en een werkende readiness probe
  • Cluster-permissies om Service, Ingress en (optioneel) HTTPRoute en Rollout resources 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 previewReplicaCount van 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

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.