Roll terug naar de vorige revisie in een commando
De snelste rollback is een commando:
kubectl rollout undo deployment/myapp
Dit vertelt de Deployment-controller om terug te schakelen naar de vorige ReplicaSet. Nieuwe pods komen op tegen de oudere pod-template, oude pods draineren, en het verkeer schuift terug zodra de readiness probes slagen. Het commando is idempotent en respecteert dezelfde maxUnavailable/maxSurge-instellingen als een normale rollout. Een rollback is dus zelf een rolling update, geen instant terugschakeling.
Volg de rollback met kubectl rollout status deployment/myapp. Als de vorige revisie gezond was, staat het cluster er binnen seconden tot een minuut weer op, afhankelijk van het aantal replicas en de probe-timing.
Met dat ene commando is de productie-down-vraag beantwoord. De rest van dit artikel bestaat omdat je zodra je vraagt "terug naar wat precies?" of "wat als de vorige versie ook stuk was?", rollout undo ineens niet meer vanzelfsprekend is.
Hoe deployment-revisiegeschiedenis echt werkt
Een Deployment slaat niet bij elke update een kopie van je YAML op. Kubernetes houdt rollouts bij op het niveau van de ReplicaSet. Elke unieke pod-template (elke wijziging in image, env, labels, resources of probes) levert een nieuwe ReplicaSet op, en de Deployment-controller schaalt de nieuwe op terwijl de oude wordt afgeschaald.
De .spec.revisionHistoryLimit van de Deployment bepaalt hoeveel oude ReplicaSets bewaard blijven voor rollback. De default is 10, ingesteld toen de apps/v1-API GA werd in Kubernetes 1.9 (de oudere apps/v1beta1 had nog 2 als default). Zodra de limiet bereikt is worden de oudste ReplicaSets opgeruimd door de garbage collector en kun je niet meer terug naar die revisies.
kubectl rollout undo deployment/myapp doet precies een ding: de actieve ReplicaSet wisselen naar een eerdere (standaard de meest recente daarvoor). Het herstelt niet de YAML die je hebt toegepast, het herconfigureert geen externe resources die door iets anders worden beheerd, en het zet je cluster ook niet terug in de toestand van het moment van de vorige deploy. Alles buiten de Deployment-template (een Service-aanpassing, een ConfigMap-edit, een databasemigratie via een Job) blijft staan zoals het is.
Dat doet ertoe als de rollbackvraag is "terug naar welke staat?". Voor kubectl rollout undo is het antwoord: terug naar de vorige pod-template. Al het andere mag je zelf coordineren.
Voor StatefulSets en DaemonSets geldt hetzelfde idee, maar het opslagobject is een ControllerRevision in plaats van een ReplicaSet. Het gedrag wijkt op een paar belangrijke punten af; daarover later in dit artikel meer.
Bekijk de rollout-history voordat je undo doet
Voor je terugdraait, kijk eerst even waar je naartoe gaat:
kubectl rollout history deployment/myapp
Verwachte output:
deployment.apps/myapp
REVISION CHANGE-CAUSE
2 <none>
3 <none>
4 <none>
De revisienummers lopen monotoon op. Het hoogste nummer is de huidige rollout. Nummers kunnen overslaan als oudere ReplicaSets al door revisionHistoryLimit zijn opgeruimd.
Om te zien wat er precies in een specifieke revisie zit:
kubectl rollout history deployment/myapp --revision=3
Dit print de volledige pod-template van revisie 3, inclusief image-tag, environment-variabelen, probes en resources. Controleer de image-tag altijd voor je terugrolt, zeker als er meerdere rollouts in een kort tijdsbestek zijn geweest. Als de vorige revisie ook kapot was (komt vaker voor bij incidenten over meerdere slechte deploys heen), wil je terug naar een specifieke oudere revisie en niet naar de eerstvolgende daaronder.
Staat er bij elke rij <none> onder CHANGE-CAUSE, dan is de deployment nooit met de oude --record-vlag bij kubectl set image of kubectl apply gedaan. Dat is voortaan op te lossen; zie Annoteer deployments voor zinvolle history.
Roll terug naar een specifieke revisie (--to-revision)
Als de vorige revisie zelf ook stuk is, richt je expliciet op een oudere:
kubectl rollout undo deployment/myapp --to-revision=2
Dezelfde vlag werkt op DaemonSets en StatefulSets. Het gedrag is identiek: zet de actieve pod-template terug naar die uit revisie 2, ongeacht hoeveel revisies de huidige verder is.
Bestaat het revisienummer dat je targetet niet meer (opgeruimd door revisionHistoryLimit), dan faalt het commando met een foutmelding zoals unable to find specified revision 2 in history. Verhoog revisionHistoryLimit proactief als je team vaak deployt en je een langer rollback-window wilt. De trade-off is dat elke bewaarde ReplicaSet een beetje metadata in etcd kost; voor typische workloads is dat verwaarloosbaar tot je boven de 50 of zo komt.
De default voor --to-revision is 0, en dat betekent "de vorige revisie", niet letterlijk nul. De officiele documentatie zegt het zo: "The revision to rollback to. Default to 0 (last revision)."
Voor batch-operaties over meerdere Deployments die een label delen werkt de -l-vlag:
kubectl rollout undo deployment -l app.kubernetes.io/part-of=payments
Dit rolt elke Deployment die op de selector matcht terug naar de vorige revisie. Wees voorzichtig in productie. Als maar een deel teruggerold moet, kan het terugrollen van de rest een eigen incident veroorzaken.
Annoteer deployments voor zinvolle history (vervanger van --record)
De kolom CHANGE-CAUSE in kubectl rollout history wordt gevuld vanuit de annotatie kubernetes.io/change-cause op elke ReplicaSet. Oudere Kubernetes-versies hadden een --record-vlag die deze annotatie automatisch zette met de commandregel die de rollout startte. Die vlag is sinds Kubernetes 1.22 deprecated en geeft een deprecation warning; de meeste teams kunnen er gewoon van uitgaan dat hij weg is.
De vervanger is dat je de annotatie zelf zet, het liefst in je CI/CD-pipeline:
kubectl annotate deployment/myapp \
kubernetes.io/change-cause="release v2.4.1 - PR #1234 - hotfix login-bug" \
--overwrite
Draai dit commando in dezelfde pipeline-stap die de image bijwerkt. De annotatie hangt dan aan de nieuwe ReplicaSet die Kubernetes aanmaakt, en kubectl rollout history toont een zinvolle regel in plaats van <none>. De --record-vlag bewaarde sowieso alleen de letterlijke commandstring, en dat is veel minder bruikbaar dan een release-tag plus een PR-link.
Wel even nuchter blijven over wat change-cause is: het is gewoon een vrije tekstveld. Kubernetes valideert, parseert of gebruikt het nergens voor logica. Het is een hint voor mensen die de history bekijken. Pipelines die rollback-besluiten automatiseren moeten op de image-tag of op een label van de Deployment kijken, niet op deze annotatie.
Volg rollback-progressie met kubectl rollout status
Heb je de rollback aangezwengeld, kijk dan ook of hij echt afkomt:
kubectl rollout status deployment/myapp --timeout=5m
Verwachte output tijdens een lopende rollback:
Waiting for deployment "myapp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 3 out of 4 new replicas have been updated...
deployment "myapp" successfully rolled out
De --timeout-vlag exit niet-nul als de rollback niet op tijd klaar is. Verwerk dat in je incident-automatisering zodat de operator niet aan de terminal hoeft te kleven.
Open in een tweede terminal de pod-transities:
kubectl get pods -l app=myapp -w
Je hoort nieuwe pods (met de oudere image) 1/1 Running te zien halen voordat de huidige-maar-stukke pods in Terminating gaan. Als de rollback hangt met nieuwe pods in Pending of CrashLoopBackOff, is de vorige revisie ook niet gezond en moet je nog verder terug of juist naar voren fixen. Zie Wanneer rollback het probleem niet oplost onderaan dit artikel.
Voor verdere context over waarom een rollback zelf een rolling update is en hoe je hem zero-downtime houdt, zie Kubernetes rolling updates en zero-downtime deployments.
Zet progressDeadlineSeconds, en handel er ook op
Engineers nemen vaak aan dat progressDeadlineSeconds automatisch een rollback triggert als een rollout vastloopt. Dat klopt niet. De officiele documentatie zegt expliciet dat Kubernetes alleen een status-condition zet met reden ProgressDeadlineExceeded als de deadline wordt overschreden; het cluster doet er verder niets mee.
De default is 600 seconden (10 minuten). Configureer het op de Deployment:
spec:
progressDeadlineSeconds: 300 # markeer rollout als failed na 5 minuten zonder progressie
Om er op te handelen, monitor je de condition in je CI/CD of alerting en roep je kubectl rollout undo van daaruit aan. Een minimale pipeline-check ziet er zo uit:
# Geef de rollout 5 minuten na kubectl apply
if ! kubectl rollout status deployment/myapp --timeout=5m; then
echo "Rollout faalde; rolt terug"
kubectl rollout undo deployment/myapp
exit 1
fi
Dit is wat de meeste teams "automatische rollback" noemen in hun CI/CD-platform. Het cluster doet het niet zelf; de pipeline doet het. Tools als Argo Rollouts breiden dit uit door rollback aan metric-analyse te koppelen, maar onder de motorkap zit nog steeds een script dat kubectl rollout status volgt en bij falen undo aanroept.
Preview een rollback veilig met --dry-run=server
Voor je in productie de trekker overhaalt, even previewen wat er gebeurt:
kubectl rollout undo deployment/myapp --dry-run=server
De vlag --dry-run=server stuurt het verzoek naar de API-server, draait admission en validatie, maar persisteert de wijziging niet. De output is het Deployment-object zoals het zou zijn na de rollback, inclusief de nieuwe (teruggerolde) pod-template. Handig om te bevestigen dat je echt naar de juiste image teruggaat voordat je tijdens een incident commit.
Er is ook --dry-run=client, die alleen print wat kubectl zou versturen zonder de server te raadplegen. Voor rollbacks is server nuttiger omdat hij admission-webhook-rejecties (PSP, OPA, Kyverno) vangt die het echte commando alsnog zouden tegenhouden.
Verschillen met StatefulSet- en DaemonSet-rollback
Hetzelfde kubectl rollout undo-commando werkt op alle drie de workload-controllers, maar het gedrag verschilt op punten die mensen vaak verrassen.
DaemonSets
DaemonSets kregen een RollingUpdate-strategie in Kubernetes 1.6 en revisiegeschiedenis in 1.7. Rollback verloopt via dezelfde flow als bij Deployments:
kubectl rollout history daemonset/fluentd
kubectl rollout undo daemonset/fluentd --to-revision=2
Belangrijk om te weten: DaemonSet-revisies rollen alleen voorwaarts. De Kubernetes-documentatie zegt het direct: "DaemonSet revisions only roll forward. That is to say, after a rollback completes, the revision number (.revision field) of the ControllerRevision being rolled back to will advance. For example, if you have revision 1 and 2 in the system, and roll back from revision 2 to revision 1, the ControllerRevision with .revision: 1 will become .revision: 3."
Dat betekent dat terugrollen naar revisie 1 revisie 3 oplevert, en de volgende rollback met --to-revision=1 faalt omdat revisie 1 onder dat nummer niet meer bestaat. Script je DaemonSet-rollbacks, pin dan niet over operaties heen op specifieke revisienummers; resolve de gewenste revisie elke keer opnieuw uit de history.
StatefulSets
StatefulSets kregen RollingUpdate en revisiegeschiedenis in Kubernetes 1.7. De pod-template uit de ControllerRevision wordt teruggezet bij kubectl rollout undo, maar de praktische consequenties verschillen van een Deployment doordat pods ordinaal aan persistente volumes hangen.
Drie dingen om in gedachten te houden:
- Pods updaten in omgekeerde ordinale volgorde. Een rolling update of rollback loopt van de hoogste ordinaal naar de laagste, en wacht tot elke pod Ready is voor de volgende aan de beurt is. Een rollback van een StatefulSet met 5 replicas duurt grofweg 5x zo lang als die van een Deployment van dezelfde grootte.
- PersistentVolumes rollen niet mee terug. De pod-template wordt hersteld, maar de onderliggende PVCs en PVs houden de data die de vorige (kapotte) versie heeft geschreven. Heeft de bug on-disk state corrupt gemaakt, dan is
rollout undoniet genoeg; je herstelt vanuit backup of draait een aparte data-fix Job. - Soms is handmatig ingrijpen nodig. De Kubernetes-docs waarschuwen: "When using Rolling Updates with the default Pod Management Policy (
OrderedReady), it's possible to get into a broken state that requires manual intervention to repair." Als een teruggerolde StatefulSet-pod niet Ready kan worden (omdat de data niet compatibel is met de oudere applicatieversie), slaat de controller hem niet over. Je kunt dan handmatig de pod verwijderen, de data fixen en de controller hem opnieuw laten aanmaken.
Partitioneer je een StatefulSet-update met .spec.updateStrategy.rollingUpdate.partition, dan geldt de partitie ook voor rollbacks. Pods met een ordinaal onder de partitie worden niet aangeraakt, ook niet bij een rollback.
GitOps: rollback via Argo CD of git revert
Wordt je cluster beheerd door Argo CD of een andere GitOps-controller, dan werkt kubectl rollout undo wel maar levert hij een probleem op: Argo CD ziet het cluster als afgedreven van Git en, als selfHeal: true aanstaat, past hij het kapotte manifest binnen seconden weer toe. Je rollback wordt teruggedraaid voordat hij stabiel is.
De juiste rollback-route onder GitOps is git revert:
git revert <commit-van-de-slechte-deploy>
git push
Argo CD ziet de nieuwe commit, synct het cluster terug naar het vorige manifest, en de rollback loopt door dezelfde pipeline als elke andere wijziging. Dit houdt de audittrail intact en je vecht niet tegen de controller.
Voor incident-snelheid heb je twee opties:
- Zet auto-sync tijdelijk uit. In de Argo CD UI of met
argocd app set <app> --sync-policy none, en draai daarnakubectl rollout undo. Zet auto-sync weer aan zodra de fix in Git staat. Doe dit alleen als je geen tijd hebt voor een Git-rondje. - Gebruik de ingebouwde rollback in de Argo CD UI. Het History-and-Rollback-paneel deployt een eerder Git-commit-manifest zonder Git zelf te wijzigen. Argo CD meldt de applicatie dan als
OutOfSynctot je de revert commit. Dit ligt dichter bijkubectl rollout undo, maar blijft binnen de GitOps-tool.
De meeste teams vinden optie 2 de juiste balans tijdens incidenten en volgen op met een git revert-PR zodra het ergste vuur uit is.
Wanneer rollback het probleem niet oplost
Rollback gaat ervan uit dat de vorige revisie gezond was. Als die aanname onderuit gaat, zijn dit de meest voorkomende faalmodes:
De vorige revisie is ook stuk. Bekijk de history (kubectl rollout history deployment/myapp --revision=N) en rol verder terug met --to-revision. Gaat de kapotte staat verder terug dan revisionHistoryLimit, dan is de enige route naar voren fixen. Verhoog revisionHistoryLimit zodra het incident voorbij is, zodat je bij een volgend incident een langer rollback-window hebt.
Er is een databasemigratie meegelopen tijdens de deploy. kubectl rollout undo zet de pod-template terug, niet het database-schema. Heeft de nieuwe applicatieversie het schema op een niet-backward-compatible manier gemigreerd, dan starten de teruggerolde pods niet meer. Of je rolt de migratie ook terug (als die een down-step heeft) of je fixt naar voren met een hotfix die met het nieuwe schema werkt.
Externe resources zijn meegewijzigd. Een nieuwe IAM-rol, een Service die naar de verkeerde selector wijst, een Ingress met een ander pad: kubectl rollout undo raakt geen van deze. Audit wat er nog meer is veranderd in de deploy en draai elk stuk bewust terug.
De vorige ReplicaSet is opgeruimd. Staat revisionHistoryLimit laag en zijn er meerdere deploys overheen gegaan voor je de breuk merkte, dan kan de doel-ReplicaSet al weg zijn. De fout unable to find specified revision N in history is definitief; een opgeruimde ReplicaSet krijg je niet terug. Fix naar voren en zet de limit daarna omhoog.
Vastgelopen op ProgressDeadlineExceeded na de rollback. De vorige versie kan ook niet starten. Bekijk events met kubectl describe deployment/myapp en logs van de nieuwe (teruggerolde) pods. De fix hangt af van de onderliggende fout, maar een vastgelopen rollback verschilt niet van een vastgelopen deploy. Zie de troubleshooting-sectie van Kubernetes rolling updates en zero-downtime deployments voor probe- en resource-issues die rollouts in beide richtingen laten hangen.
Is rollback echt onmogelijk, dan is de volgende beste zet een gerichte hotfix die door het normale deploy-pad loopt. Zie het falen van de rollback als data: het zegt dat de vorige revisie geen bruikbare fallback was, en de eerstvolgende incident-postmortem moet behandelen waarom.