Kubernetes rolling updates en zero-downtime deployments

Een rolling update vervangt pods een voor een, maar de standaardinstellingen voorkomen geen verbroken verbindingen. Zonder de juiste readiness probe, preStop hook en terminationGracePeriodSeconds krijgen gebruikers 502-fouten bij elke deploy. Deze gids loopt elke relevante instelling door en eindigt met een compleet, productieklaar Deployment-manifest.

Wat je aan het eind hebt

Een Deployment die nieuwe versies uitrolt zonder ook maar een verbinding te verliezen. De configuratie dekt strategy-tuning, readiness-gating, de endpoint-removal race condition en een PodDisruptionBudget die beschermt tegen voluntary disruptions.

Vereisten

RollingUpdate vs. Recreate

Kubernetes ondersteunt twee strategy-types:

RollingUpdate (standaard) maakt pods aan in een nieuwe ReplicaSet terwijl de oude wordt afgeschaald. Op elk moment tijdens de rollout serveert een mix van oude en nieuwe pods het verkeer. Dit is de enige strategie die zero downtime kan opleveren.

Recreate stopt alle bestaande pods voordat nieuwe worden aangemaakt. De service is volledig onbereikbaar tussen het afbreken van de oude en het Ready worden van de nieuwe pods. Gebruik dit alleen wanneer twee versies niet veilig naast elkaar kunnen draaien (een databasemigratie die backwards compatibility breekt, bijvoorbeeld).

Voor stateless HTTP-services is RollingUpdate de juiste keuze. De rest van dit artikel gaat daar vanuit. Wil je deze Deployment koppelen aan een CI/CD-pipeline, combineer de rollout-instellingen hieronder dan met de GitHub Actions-workflow die container images bouwt en deployt, zodat het cluster pod-vervanging netjes afhandelt terwijl de pipeline alles stroomopwaarts verzorgt.

Stem maxUnavailable en maxSurge af

Twee velden bepalen hoe snel de rollout verloopt:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0   # verlaag capaciteit nooit onder het gewenste aantal
      maxSurge: 1          # maak een extra pod per keer aan

maxUnavailable bepaalt het maximale aantal pods dat unavailable mag zijn tijdens de update. Accepteert een integer of percentage (percentages worden naar beneden afgerond). maxSurge bepaalt hoeveel extra pods er mogen bestaan boven het gewenste aantal (percentages worden naar boven afgerond). Kubernetes verbiedt om beide op 0 te zetten.

De defaults zijn 25% voor allebei. Bij 8 replicas betekent dat: maximaal 2 pods unavailable en maximaal 2 extra pods tegelijk. Dat is agressief genoeg om merkbare capaciteitsdips te veroorzaken.

Zet voor user-facing services maxUnavailable: 0. Zo is de volledige gewenste capaciteit altijd beschikbaar. Geen oude pod wordt verwijderd totdat een nieuwe Ready is.

Patroon maxUnavailable maxSurge Afweging
Veiligst, langzaamst 0 1 Volledige capaciteit altijd; een pod per keer
Veilig, sneller 0 25% Volledige capaciteit; meerdere surge pods versnellen rollout
Gebalanceerd (standaard) 25% 25% Korte capaciteitsdip mogelijk; prima voor niet-kritieke services

Houd er rekening mee dat maxSurge extra node-capaciteit vereist. 100% verdubbelt het aantal pods tijdelijk.

Voeg een stabiliteitsbuffer toe met minReadySeconds

spec:
  minReadySeconds: 10

Een pod telt pas als Available nadat hij minstens minReadySeconds continu Ready is geweest. Dit vangt applicaties op die de readiness probe even halen maar seconden later crashen. De default is 0, wat betekent dat de rollout doorgaat zodra de readiness probe voor het eerst slaagt.

Stuur verkeer via readiness probes

Zonder readiness probe stuurt Kubernetes verkeer naar een pod zodra de container start. Bij een rolling update gaan er dan requests naar een applicatie die nog niet klaar is met opstarten.

readinessProbe:
  httpGet:
    path: /healthz/ready
    port: 8080
  initialDelaySeconds: 5      # stem af op de opstarttijd van je app
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 3
  successThreshold: 1

De readiness probe bepaalt of een pod wordt opgenomen in de Service EndpointSlices. Tijdens een rolling update moet de readiness probe van een nieuwe pod slagen voordat Kubernetes hem als Available beschouwt en voordat oude pods worden beëindigd (bij maxUnavailable 0).

Voor applicaties met langzame of onvoorspelbare opstarttijden (JVM warm-up, modelbestanden laden): voeg een startup probe toe in plaats van initialDelaySeconds op te blazen. De startup probe blokkeert readiness- en liveness-checks totdat het opstarten voltooid is.

Draai je achter een cloud load balancer (AWS ALB/NLB, GCP GCLB)? Overweeg dan Pod Readiness Gates. De ALB-controller injecteert een custom readiness-conditie die pas True wordt wanneer de target group de pod als Healthy registreert. Zonder dat kan de rollout oude pods beëindigen voordat de load balancer überhaupt verkeer naar de nieuwe stuurt.

Kies je probes voor een gloednieuwe Deployment? Kubernetes liveness-, readiness- en startup-probes configureren loopt door de beslisboom en startwaarden per runtime. Voor een dieper duik in probe-types, mechanismen en timing-valkuilen wanneer iets zich misdraagt, zie hoe je Kubernetes health probes configureert.

Los de endpoint-removal race condition op

Dit is veruit de meest voorkomende oorzaak van 502-fouten tijdens deployments. Wanneer Kubernetes besluit een pod te beëindigen, starten twee onafhankelijke processen tegelijkertijd:

  1. De kubelet voert de preStop hook uit en stuurt daarna SIGTERM.
  2. De endpoint controller verwijdert het IP van de pod uit de EndpointSlices, waardoor kube-proxy op elke node zijn iptables/IPVS-regels bijwerkt.

Die twee paden zijn niet gesynchroniseerd. De kube-proxy minSyncPeriod staat standaard op 1 seconde, maar de daadwerkelijke propagatie over alle nodes kan meerdere seconden duren. Gedurende dat venster routeert kube-proxy op sommige nodes nog steeds nieuwe verbindingen naar de terminerende pod. Als de applicatie al gestopt is met het accepteren van verbindingen, falen die requests.

De oplossing is een preStop sleep die de pod lang genoeg in leven houdt totdat de endpoint-verwijdering overal is doorgevoerd:

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 15"]

Tijdens die 15 seconden accepteert de pod nog gewoon verbindingen terwijl alle kube-proxies bijwerken. Na de sleep stuurt Kubernetes SIGTERM en begint de applicatie met afsluiten.

Hoe lang moet de preStop sleep zijn?

Omgeving Aanbevolen sleep Waarom
Eenvoudig on-prem cluster 5 tot 10 seconden Snelle iptables-propagatie
Cloud met externe load balancer (ALB/NLB) 35+ seconden Cloud-LBs doen 15 tot 30 seconden over target-deregistratie
Service mesh (Istio, Linkerd) 5 tot 10 seconden + mesh drain Envoy-sidecar heeft een eigen drain-cyclus

Budget terminationGracePeriodSeconds correct

De timer begint wanneer de preStop hook start, niet nadat hij klaar is. Je budget moet dus zijn:

terminationGracePeriodSeconds >= preStop_sleep + applicatie_shutdown_tijd + buffer

Met een preStop sleep van 15 seconden en een applicatie die 10 seconden nodig heeft om verbindingen te drainnen:

terminationGracePeriodSeconds: 30   # 15 + 10 + 5 buffer

Als de timer afloopt voordat de applicatie stopt, stuurt Kubernetes SIGKILL en worden alle lopende requests afgebroken.

Wil je alles weten over SIGTERM-handling, connection draining in Go/Node.js/Java/Python, en het testen van je shutdown? Zie Kubernetes graceful shutdown.

Bescherm tegen voluntary disruptions met PodDisruptionBudgets

Een rolling update is niet het enige dat pods beëindigt. kubectl drain, cluster autoscaler scale-down en cloud provider node pool upgrades zijn voluntary disruptions die pods onafhankelijk van de Deployment controller kunnen evicten. Zonder PodDisruptionBudget kan een kubectl drain al je pods tegelijk verwijderen.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
spec:
  maxUnavailable: 1
  unhealthyPodEvictionPolicy: AlwaysAllow
  selector:
    matchLabels:
      app: myapp

maxUnavailable: 1 staat toe dat voluntary disruptions een pod per keer verwijderen. Voor hogere replica-aantallen is maxUnavailable: 25% een goed startpunt.

Het unhealthyPodEvictionPolicy: AlwaysAllow-veld (Kubernetes 1.26+) voorkomt dat PDBs node drains blokkeren wanneer pods al in een crash-loop zitten. Zonder dit veld telt een kapotte pod die nooit Ready is mee voor het disruption budget, waardoor kubectl drain eindeloos blijft hangen.

Zet maxUnavailable niet op 0, tenzij je een quorum-gebaseerd systeem draait dat echt geen enkel lid mag verliezen. Het blokkeert alle voluntary evictions, inclusief routine clusteronderhoud.

PDBs en rolling updates zijn onafhankelijk. PDBs bepalen niet hoe de Deployment controller pods uitrolt. Ze beschermen tegen externe disruptions die toevallig samenvallen met een rollout.

Blue/green en canary als alternatieven

Rolling updates werken goed voor de meeste stateless services, maar twee patronen geven je meer controle wanneer je die nodig hebt. Blue-green draait de nieuwe versie naast de oude in een parallelle Deployment en wisselt verkeer atomair om met een Service-selector patch, wat je instant rollback geeft tegen de prijs van dubbele compute. Canary stuurt de nieuwe versie eerst naar een klein percentage van het verkeer (via replica-verhoudingen voor grove splits, of NGINX Ingress canary annotations voor precieze weights) en verhoogt het aandeel zodra de metrics meegaan.

Voor een complete walkthrough van beide, met een beslismatrix, native kubectl-voorbeelden en de stap richting Argo Rollouts voor analysis-gestuurde automatische rollback, zie Kubernetes blue-green en canary deployment-strategieën.

Compleet productieklaar manifest

Dit manifest combineert alle instellingen die hierboven zijn besproken. Inline comments leggen elk veld uit.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 4
  revisionHistoryLimit: 5              # bewaar 5 oude ReplicaSets voor rollback
  progressDeadlineSeconds: 300         # markeer rollout als mislukt na 5 minuten zonder voortgang
  minReadySeconds: 10                  # pod moet 10s Ready zijn voor hij als Available telt
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0                # verlaag capaciteit nooit onder 4
      maxSurge: 1                      # voeg een pod per keer toe
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      terminationGracePeriodSeconds: 60  # preStop 15s + app drain 30s + 15s buffer
      containers:
      - name: app
        image: registry.internal/myapp:v2.4.1
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /healthz/ready
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 2
          failureThreshold: 3
          successThreshold: 1
        livenessProbe:
          httpGet:
            path: /healthz/live
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 2
          failureThreshold: 3
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            memory: "256Mi"            # geen CPU-limit; voorkom onnodige throttling
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: myapp
              topologyKey: kubernetes.io/hostname
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
spec:
  maxUnavailable: 1
  unhealthyPodEvictionPolicy: AlwaysAllow
  selector:
    matchLabels:
      app: myapp

Controleer het resultaat

Na het toepassen van het manifest, bevestig dat de rollout zonder fouten voltooit:

kubectl rollout status deployment/myapp --timeout=5m

Verwachte output:

deployment "myapp" successfully rolled out

Bekijk tijdens de rollout de pod-transities in een tweede terminal:

kubectl get pods -l app=myapp -w

Je zou moeten zien dat nieuwe pods 1/1 Running bereiken voordat oude pods Terminating worden. Op geen enkel moment mag het aantal Running, Ready pods onder je gewenste replica-aantal zakken.

Om te verifiëren dat er nul verbindingen zijn gedropt, draai een load test tegen de Service tijdens een rollout. Tools als hey, wrk of k6 werken goed. Een geslaagde zero-downtime deployment levert 0 non-2xx responses op over het hele rollout-venster.

Veelvoorkomende problemen

Rollout hangt en geen nieuwe pods worden Ready. De readiness probe faalt op nieuwe pods. Check kubectl describe pod <nieuwe-pod> voor probe failure events. De meest voorkomende oorzaak is een verkeerd path of port in de readiness probe, of een initialDelaySeconds die korter is dan de daadwerkelijke opstarttijd.

502-fouten tijdens deploy ondanks werkende readiness probes. De endpoint-removal race condition. De preStop hook ontbreekt of de sleep is te kort voor je omgeving. Voeg de preStop sleep toe of verhoog hem. Achter een cloud load balancer is 35+ seconden normaal.

kubectl drain blokkeert eindeloos. Een PDB voorkomt eviction. Of maxUnavailable: 0 is ingesteld (nul disruptions toegestaan), of unhealthy pods verbruiken het budget. Zet unhealthyPodEvictionPolicy: AlwaysAllow (Kubernetes 1.26+) om drains op crashende pods te deblokkeren.

ProgressDeadlineExceeded-conditie verschijnt. De rollout heeft geen voortgang geboekt binnen progressDeadlineSeconds. Kubernetes doet geen automatische rollback. Rollback handmatig met kubectl rollout undo deployment/myapp en onderzoek waarom nieuwe pods niet Available worden. Voor de complete rollback-workflow, hoe je een specifieke revisie targetet en wat kubectl rollout undo wel en niet daadwerkelijk terugzet, zie Kubernetes deployment rollback met kubectl rollout undo.

Surge pods blijven in Pending hangen. Het cluster heeft niet genoeg node-capaciteit voor de extra pod. Verlaag maxSurge, voeg nodes toe, of bekijk je resource requests om te zien of ze overprovisioned zijn.

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.