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
kubectlverbonden met een Kubernetes 1.26+ cluster (1.26 introduceerde hetunhealthyPodEvictionPolicy-veld voor PDBs; oudere versies werken maar missen die functie)- Een bestaande Deployment met minimaal 2 replicas
- Kennis van hoe health probes werken (liveness vs. readiness vs. startup)
- Kennis van resource requests en limits zodat de extra surge pod gescheduled kan worden
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:
- De kubelet voert de preStop hook uit en stuurt daarna SIGTERM.
- 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.