Every CKAD candidate can run kubectl set image and kubectl rollout undo. Our Deployments and rolling updates guide covers those commands and the question variants they solve. What that guide does not do, and what this one does, is explain the machine underneath: why a 3-replica Deployment with default settings never drops below 3 available Pods, why a Pod that reports Ready may still not count as available, why kubectl rollout status exits with code 1 after ten minutes even though nothing is rolling back, and why the revision you rolled back to shows up with a new number.
Those details are what separate a candidate who can perform a rollout from one who can diagnose a stuck one in two minutes, and the CKAD exam has plenty of tasks of the second kind: “the Deployment is not progressing, fix it”, “ensure at most one Pod is unavailable during updates”, “the rollout must be considered failed after 2 minutes”. This guide is verified against the current Kubernetes documentation (v1.37) and the apps/v1 API reference.
The Three-Layer Model: Deployment, ReplicaSet, Pod
A Deployment never touches Pods directly. It manages ReplicaSets, and each ReplicaSet manages Pods that match its selector. A rollout is nothing more than the Deployment controller scaling one ReplicaSet up while scaling another down.
Deployment web (replicas: 3, template: nginx:1.27)
├── ReplicaSet web-5d4f8c7b6 revision 1 nginx:1.26 replicas 0 (old)
└── ReplicaSet web-7f9d6b8c5 revision 2 nginx:1.27 replicas 3 (new)
├── Pod web-7f9d6b8c5-2kx9q
├── Pod web-7f9d6b8c5-h7m2z
└── Pod web-7f9d6b8c5-qv8lp
Three consequences follow immediately:
- Revisions live in ReplicaSets. The Deployment object holds only the current template. Every previous template still exists as a scaled-to-zero ReplicaSet, which is why
revisionHistoryLimitcontrols how far back you can roll. - Scaling and rolling out are different operations. Changing
.spec.replicasresizes the current ReplicaSet (and, mid-rollout, the others proportionally). It does not create a ReplicaSet or a revision. - Do not manage ReplicaSets owned by a Deployment. If you scale or delete one by hand, the Deployment controller will undo your change on its next reconcile.
What triggers a rollout
The documentation is precise: “A Deployment’s rollout is triggered if and only if the Deployment’s Pod template (that is, .spec.template) is changed.” Anything inside .spec.template counts: image, environment variables, labels on the template, resource requests, volumes, annotations on the template. Anything outside does not:
| Change | New ReplicaSet / revision? |
|---|---|
kubectl set image, kubectl set env, kubectl set resources | Yes |
kubectl rollout restart (adds kubectl.kubernetes.io/restartedAt to the template) | Yes |
Editing .spec.template.metadata.labels or annotations | Yes |
kubectl scale, HPA changes to .spec.replicas | No |
Changing strategy, minReadySeconds, progressDeadlineSeconds, revisionHistoryLimit, paused | No |
Changing the Deployment’s own metadata.labels/annotations | No |
That is why kubectl rollout restart works: it changes the template by stamping a timestamp annotation, which the controller treats like any other template change.
pod-template-hash: How the Controller Tells ReplicaSets Apart
When the Deployment controller creates (or adopts) a ReplicaSet, it hashes the Pod template and writes the result as the label pod-template-hash into three places: the ReplicaSet’s selector, the ReplicaSet’s Pod template labels, and any Pods the ReplicaSet already owns. The ReplicaSet’s name is <deployment-name>-<hash>, and Pods inherit <replicaset-name>-<random>.
kubectl get rs -l app=web --show-labels
# NAME DESIRED CURRENT READY AGE LABELS
# web-5d4f8c7b6 0 0 0 9m app=web,pod-template-hash=5d4f8c7b6
# web-7f9d6b8c5 3 3 3 2m app=web,pod-template-hash=7f9d6b8c5
The hash is what keeps two ReplicaSets of the same Deployment from fighting over Pods: both select app=web, but each also selects its own pod-template-hash. The documentation’s caution is unambiguous: do not change this label. Two practical consequences:
- If you create a bare Pod with labels that match a Deployment’s selector and the right hash, the ReplicaSet adopts it and may delete one of its own Pods to stay at the desired count. Without the hash label it matches the Deployment’s selector but no ReplicaSet’s, and the Deployment controller’s accounting gets confused. Keep bare Pods out of Deployment selectors, which is also why our labels and selectors guide recommends an app-specific label set per workload.
- Because the hash is computed from the template, rolling back to a previous template produces the same hash as before, so the controller reuses the old ReplicaSet rather than creating a new one. That reuse is what makes rollback cheap, and it explains the revision renumbering described later.
The Deployment status carries a collisionCount field: if a new template hashes to a name that already exists for a different template (a genuine hash collision), the controller increments collisionCount, folds it into the hash, and tries again. You will almost never see it non-zero, but it is why the field exists.
Topology spread constraints and affinity rules that key on pod-template-hash behave per-ReplicaSet rather than per-Deployment, which matters during a rollout and is covered in our topology spread deep dive.
RollingUpdate Math: maxSurge Rounds Up, maxUnavailable Rounds Down
The default strategy is RollingUpdate with maxSurge: 25% and maxUnavailable: 25%. Both accept an absolute number or a percentage of .spec.replicas, and the rounding rules are asymmetric by design:
maxUnavailable: “The absolute number is calculated from percentage by rounding down.” It caps how many Pods may be unavailable, so the controller guarantees at leastreplicas - maxUnavailableavailable Pods at all times.maxSurge: “The absolute number is calculated from the percentage by rounding up.” It caps how many Pods may exist abovereplicas, so the total never exceedsreplicas + maxSurge.- Both cannot be 0 at the same time; the API rejects it.
Worked examples with the 25% defaults:
| replicas | maxUnavailable 25% → floor | maxSurge 25% → ceil | Minimum available during rollout | Maximum total Pods |
|---|---|---|---|---|
| 1 | floor(0.25) = 0 | ceil(0.25) = 1 | 1 | 2 |
| 3 | floor(0.75) = 0 | ceil(0.75) = 1 | 3 | 4 |
| 4 | floor(1.0) = 1 | ceil(1.0) = 1 | 3 | 5 |
| 10 | floor(2.5) = 2 | ceil(2.5) = 3 | 8 | 13 |
| 100 | 25 | 25 | 75 | 125 |
The 3-replica row is the one the documentation walks through: “at least 3 Pods are available and at max 4 Pods in total”. The controller surges one new Pod, waits for it to become available, scales the old ReplicaSet to 2, surges the next, and so on. The event stream you see in kubectl describe deploy is exactly that dance:
Normal ScalingReplicaSet Scaled up replica set web-7f9d6b8c5 to 1
Normal ScalingReplicaSet Scaled down replica set web-5d4f8c7b6 to 2
Normal ScalingReplicaSet Scaled up replica set web-7f9d6b8c5 to 2
Normal ScalingReplicaSet Scaled down replica set web-5d4f8c7b6 to 1
Normal ScalingReplicaSet Scaled up replica set web-7f9d6b8c5 to 3
Normal ScalingReplicaSet Scaled down replica set web-5d4f8c7b6 to 0
Because percentages round in opposite directions, the defaults always give you zero unavailable Pods on small Deployments (1 to 3 replicas) and a surge of 1. If you need the opposite trade-off, no extra capacity but tolerate downtime, set maxSurge: 0 and maxUnavailable: 1; the controller must then delete an old Pod before it can create a new one. One implementation detail worth knowing: if your percentages round both values to 0 (for example maxUnavailable: 10% and maxSurge: 0 on 3 replicas), the controller treats maxUnavailable as 1 so the rollout can make progress rather than deadlock.
Common exam phrasings and their settings:
| Requirement | Setting |
|---|---|
| ”No reduction in capacity during the update” | maxUnavailable: 0, maxSurge ≥ 1 |
| ”At most one Pod unavailable at a time” | maxUnavailable: 1 |
| ”Never run more Pods than the desired count” (resource quota is tight) | maxSurge: 0, maxUnavailable ≥ 1 |
| ”Replace all Pods at once, downtime acceptable” | strategy.type: Recreate |
| ”Update half the fleet at a time” | maxUnavailable: 50% or maxSurge: 50% depending on whether capacity or speed matters |
The two numbers are written on the ReplicaSets themselves as the annotations deployment.kubernetes.io/desired-replicas (the Deployment’s .spec.replicas) and deployment.kubernetes.io/max-replicas (replicas + maxSurge); the controller uses them to implement the surge cap.
Recreate
With strategy.type: Recreate, all Pods of the old ReplicaSet are terminated and their removal awaited before any Pod of the new revision is created. The documentation adds a caveat that catches people: this ordering guarantee applies to upgrades. If you manually delete a Pod, its ReplicaSet replaces it immediately, even while the old Pod is still Terminating. If you need a true “at most one instance” guarantee, use a StatefulSet.
Ready Is Not Available: minReadySeconds
Three status numbers describe Pods in a Deployment, and the rollout is paced by the third one:
.status field | Counts Pods that… |
|---|---|
replicas | match the selector and are not terminating |
readyReplicas | have the Ready condition true |
availableReplicas | have been Ready for at least minReadySeconds without any container crashing |
minReadySeconds defaults to 0, so by default Ready and Available coincide. Set it to 30 and every newly created Pod must stay Ready for 30 seconds before the controller counts it as available and proceeds to the next step of the rollout. A container crash during that window resets the clock. This is the knob for “my readiness probe passes immediately but the app falls over 15 seconds later”: a probe can only tell you the Pod is Ready now; minReadySeconds makes the controller wait and see. The probe mechanics themselves are covered in our probes guide.
The kubectl get deploy columns map directly: READY is readyReplicas/replicas, UP-TO-DATE is updatedReplicas (Pods with the current template), AVAILABLE is availableReplicas. During a rollout with minReadySeconds: 30 you will see READY 4/3, UP-TO-DATE 1, AVAILABLE 3 for half a minute: the surged Pod is Ready, not yet Available, so the old Pod it will replace has not been scaled down.
Two rules bind the field to others: progressDeadlineSeconds must be greater than minReadySeconds, and .spec.template.spec.restartPolicy may only be Always (the default).
Deployment Conditions: Progressing, Available, ReplicaFailure
The fastest way to diagnose a Deployment on the exam is kubectl describe deploy <name> and the Conditions: block, or kubectl get deploy <name> -o jsonpath='{.status.conditions}'. There are three condition types.
Progressing
| Status | Reason | Meaning |
|---|---|---|
| True | NewReplicaSetCreated | The controller just created the new ReplicaSet |
| True | FoundNewReplicaSet | An existing ReplicaSet already matches the template (typical after rollback) |
| True | ReplicaSetUpdated | Scaling up the new or down the old ReplicaSet is in progress |
| True | NewReplicaSetAvailable | Rollout complete: all replicas updated and available, no old replicas running |
| False | ProgressDeadlineExceeded | No progress for progressDeadlineSeconds; the rollout is considered failed |
| False | ReplicaSetCreateError | The new ReplicaSet could not be created |
A subtlety the docs call out: once the rollout completes, Progressing=True/NewReplicaSetAvailable stays until a new rollout starts, even if Pods later become unavailable. Availability changes affect the Available condition, not Progressing.
Available
Available=True with reason MinimumReplicasAvailable means availableReplicas is at least replicas - maxUnavailable. Available=False with MinimumReplicasUnavailable means you have dropped below that floor. Note that “minimum availability” is defined by your rolling update strategy, so a Deployment with maxUnavailable: 25% and 3 replicas is Available only when all 3 are available (floor(0.75) = 0).
ReplicaFailure
ReplicaFailure=True with reason FailedCreate (or FailedDelete) surfaces errors the ReplicaSet controller hit when creating Pods. The documentation’s example is a ResourceQuota violation:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
ReplicaFailure True FailedCreate
...
message: 'Error creating: pods "web-7f9d6b8c5-" is forbidden: exceeded quota:
object-counts, requested: pods=1, used: pods=3, limited: pods=2'
This is the single most useful condition on the exam, because the message field tells you why: quota, a LimitRange rejecting the container spec, a missing ServiceAccount, a forbidden securityContext under Pod Security Admission. Fix the cause (our resource management guide covers the quota and LimitRange cases) and the controller completes the rollout on its own; you do not need to re-apply.
progressDeadlineSeconds: Detecting a Stuck Rollout
.spec.progressDeadlineSeconds defaults to 600. It is the number of seconds the controller waits for the rollout to make progress (create the ReplicaSet, scale up, scale down, see Pods become available) before it flips Progressing to False with reason ProgressDeadlineExceeded. The clock measures time since the last progress, not since the rollout started, and it is not evaluated while the Deployment is paused.
The part candidates get wrong: Kubernetes takes no action on a stalled Deployment other than setting the condition. No automatic rollback. The documentation says higher-level orchestrators (a CD tool, a script) can act on the condition. The controller keeps retrying; the deadline is also ignored once the rollout completes.
Causes of a stuck rollout, straight from the docs: insufficient quota, readiness probe failures, image pull errors, insufficient permissions, limit ranges, application runtime misconfiguration. Each one leaves a signature somewhere else (ReplicaFailure, Pod STATUS of ImagePullBackOff or CrashLoopBackOff, a Pod stuck Running but not Ready), and the application troubleshooting guide walks through locating them.
What you will see is kubectl rollout status give up:
kubectl rollout status deployment/web --timeout=2m
# Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
# error: deployment "web" exceeded its progress deadline
echo $?
# 1
rollout status returns 0 when the rollout completes and non-zero when the progress deadline is exceeded (or its own --timeout fires), which is why CI pipelines gate on it. --watch=false prints the current state and exits; --revision=N pins to a revision and aborts if another rollout supersedes it. For the exam task “the rollout should be considered failed if it does not progress within 2 minutes”, the answer is progressDeadlineSeconds: 120, not a --timeout flag.
Revisions, Annotations and What Rollback Really Does
Each ReplicaSet carries deployment.kubernetes.io/revision, incremented each time the Pod template changes. kubectl rollout history lists those numbers together with CHANGE-CAUSE, which is copied from the Deployment’s kubernetes.io/change-cause annotation onto the ReplicaSet when the revision is created:
kubectl annotate deployment/web kubernetes.io/change-cause="image updated to 1.27"
kubectl rollout history deployment/web
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 image updated to 1.27
kubectl rollout history deployment/web --revision=2 # shows that revision's Pod template
The old --record flag that populated this automatically is deprecated; annotate explicitly.
Rollback reuses the old ReplicaSet and renumbers it
kubectl rollout undo deployment/web (previous revision) or --to-revision=2 does one thing: it copies that ReplicaSet’s Pod template back into the Deployment’s .spec.template. Because the template hashes to the same value as before, the controller finds the existing ReplicaSet (Progressing=True/FoundNewReplicaSet) and scales it back up with the normal RollingUpdate rules. A DeploymentRollback event is recorded: Rolled back deployment "web" to revision 2.
The counter-intuitive part is numbering. Revision numbers only ever increase, so the reused ReplicaSet does not become revision 2 again; it becomes revision 4 if you were on 3, and rollout history now shows 1, 3, 4 with revision 2 gone. The ReplicaSet records where it came from in deployment.kubernetes.io/revision-history (for example "2", or "1,3" after several rollbacks), an internal annotation you should not edit. If an exam task says “roll back to the revision that used image X”, check rollout history --revision=N for the template rather than trusting the number you remember.
Two more rules from the docs: “You cannot rollback a paused Deployment until you resume it”, and only the template is restored. Replica count, strategy and other non-template fields stay as they are.
revisionHistoryLimit
.spec.revisionHistoryLimit (default 10) is how many old ReplicaSets with zero replicas the controller keeps. Setting it to 0 deletes every old ReplicaSet as soon as the rollout completes, which means the Deployment cannot be rolled back; a new rollout still creates a new ReplicaSet before the old one is removed. Clean-up runs only after a Deployment reaches the complete state, so a Deployment that never completes (crash-looping Pods through several template changes) can accumulate more ReplicaSets than the limit.
Proportional Scaling: Scaling in the Middle of a Rollout
If you (or an HPA) change .spec.replicas while a RollingUpdate rollout is in progress or paused, the controller does not put all the new replicas in the new ReplicaSet. It spreads them across the active ReplicaSets in proportion to their current sizes, bigger share to bigger ReplicaSets, leftovers to the largest, and never scales a zero-replica ReplicaSet. The docs’ example: 10 replicas, maxSurge: 3, maxUnavailable: 2, a rollout stuck on an unresolvable image with the new ReplicaSet at 5 (0 ready) and the old at 8. Scale to 15, and the controller adds 3 to the old ReplicaSet (now 11) and 2 to the new (now 7).
The point is risk: if the new image is broken, proportional scaling keeps most of the extra capacity on the version that works. It is also why a Deployment mid-rollout can report CURRENT 18 against DESIRED 15.
Pausing: Batch Several Template Changes Into One Rollout
kubectl rollout pause deployment/web sets .spec.paused: true. The documentation defines the effect narrowly: “any changes into the PodTemplateSpec of the paused Deployment will not trigger new rollouts as long as it is paused.” You can set image, set resources, add an environment variable, then kubectl rollout resume, and get one new ReplicaSet and one revision instead of three. While paused, the progress deadline is not evaluated, existing Pods keep serving, and you cannot roll back. Pausing partway through a rollout freezes the current ReplicaSet sizes, which is a crude but legitimate canary: our deployment strategies guide compares it with the two-Deployment canary pattern the docs recommend.
Status Fields You Should Be Able to Read in Ten Seconds
kubectl get deploy web -o yaml | yq '.status'
| Field | Meaning | Exam use |
|---|---|---|
observedGeneration | The .metadata.generation the controller has processed | If it lags metadata.generation, the controller has not seen your change yet |
replicas | Non-terminating Pods matching the selector | Total across all ReplicaSets |
updatedReplicas | Pods with the current template | Equals replicas when the rollout is done |
readyReplicas | Pods with Ready=True | |
availableReplicas | Ready for ≥ minReadySeconds | What the rolling update is paced by |
unavailableReplicas | Pods still needed for 100% availability, running-but-not-available or not yet created | Non-zero mid-rollout or when quota blocks creation |
terminatingReplicas | Pods with a deletionTimestamp that have not reached a terminal phase | Beta (on by default) since v1.35 behind DeploymentReplicaSetTerminatingReplicas; explains why node Pod counts exceed replicas during long terminations |
collisionCount | Hash collisions resolved when naming ReplicaSets | Almost always absent or 0 |
conditions | Progressing / Available / ReplicaFailure | Where the diagnosis lives |
A complete rollout satisfies three statements: all replicas updated, all replicas available, no old replicas running. kubectl rollout status is just a watch on those statements.
ReplicaSet Behaviours That Leak Through
The CKAD does not ask you to author ReplicaSets, but Deployment behaviour inherits theirs:
- Adoption. A ReplicaSet acquires any Pod that matches its selector and has no controller
ownerReference. The owner reference it writes hascontroller: trueandblockOwnerDeletion: true. Create a bare Pod with matching labels and it will be adopted, then possibly deleted to satisfy the replica count. - Isolating a Pod. Change a misbehaving Pod’s labels so they no longer match the selector; the ReplicaSet replaces it, and you keep the original for debugging. This is the documented debugging trick and a plausible exam task.
- Orphaning.
kubectl delete rs <name> --cascade=orphan(orpropagationPolicy: Orphanthrough the API) removes the ReplicaSet and leaves its Pods running; a new ReplicaSet with the same selector adopts them but will not rewrite their spec. The same flag applied to a Deployment is how you recreate a Deployment with a different selector without downtime. - Scale-down ordering. When a ReplicaSet scales down, the controller ranks candidates: unscheduled Pods first, then
PendingbeforeUnknownbeforeRunning, then not-Ready before Ready, then lowercontroller.kubernetes.io/pod-deletion-costfirst (default 0, best-effort, beta since 1.22), then Pods that share a node with more replicas, then those Ready for less time, then higher restart counts, then newer Pods. You rarely need this on the exam, but it explains why the newest, not-yet-Ready Pod is the first to go when you scale down mid-rollout. - Overlapping selectors fight. Two controllers whose selectors match the same Pods will each try to enforce their own replica count. Kubernetes does not stop you; plan label sets per workload.
Label Selector Immutability
In apps/v1, .spec.selector is immutable after creation, and it must match .spec.template.metadata.labels or the API rejects the object. Neither kubectl apply, kubectl edit, kubectl patch nor a Helm upgrade can change it; the error is a validation failure, not a rollout. The documented options are to delete and recreate the Deployment (with --cascade=orphan if the Pods must keep running) and to understand the three cases: adding a selector key or changing its value orphans the old ReplicaSet and creates a new one; removing a key is an overlapping change that keeps existing ReplicaSets but leaves the stale label on old Pods until the next rollout. If an exam task appears to require a selector change, re-read it; it almost certainly wants a template label change (which is allowed, as long as the selector still matches) or a new Deployment.
Exam Drill: Diagnosis in Under Two Minutes
| Symptom | Where to look | Likely cause and fix |
|---|---|---|
rollout status hangs; READY 2/3, UP-TO-DATE 1 | kubectl get pods: new Pod ImagePullBackOff | Wrong image tag; kubectl set image to a valid tag or rollout undo |
New Pod Running but never Ready; old Pods not scaled down | kubectl describe pod: readiness probe failing | Fix probe path/port or app config; the controller resumes automatically |
Progressing False ProgressDeadlineExceeded | Conditions block | Rollout stalled > progressDeadlineSeconds; fix the root cause, nothing rolls back by itself |
ReplicaFailure True FailedCreate ... exceeded quota | Conditions message | Raise quota or lower requests/replicas; see also maxSurge: 0 to avoid surging past quota |
AVAILABLE lags READY by exactly N seconds | .spec.minReadySeconds | Working as designed |
| Rollout completes with brief downtime on a 1-replica Deployment | maxUnavailable: 1 or Recreate | Set maxUnavailable: 0 so the controller surges first |
| Rollout exceeds the namespace Pod quota | maxSurge default 25% rounds up to ≥ 1 | Set maxSurge: 0, maxUnavailable: 1 |
rollout undo fails: “cannot rollback a paused deployment” | .spec.paused | kubectl rollout resume first |
rollout undo fails: no revision found | kubectl get rs shows no old ReplicaSets | revisionHistoryLimit: 0 or history pruned; redeploy the old spec from your manifest |
rollout history shows revisions 1, 3, 4 | Revision renumbering after rollback | Normal; check --revision=N templates to find the one you want |
kubectl apply of a selector change fails with “field is immutable” | spec.selector | Delete and recreate (--cascade=orphan to keep Pods) or change only template labels |
CURRENT exceeds DESIRED during a scale-up mid-rollout | Proportional scaling + surge | Normal; converges when the new ReplicaSet becomes healthy |
Pod count on nodes exceeds replicas for minutes after a scale-down | .status.terminatingReplicas | Long terminationGracePeriodSeconds; see our lifecycle hooks and graceful shutdown guide |
| Three separate rollouts when you meant one | No pause | rollout pause, make changes, rollout resume |
| Need to restart Pods without changing anything | kubectl rollout restart deploy/web | Stamps kubectl.kubernetes.io/restartedAt on the template, creating a revision |
Exam-Day One-Liners
# Strategy knobs without opening an editor
kubectl patch deploy web -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
kubectl patch deploy web -p '{"spec":{"minReadySeconds":10,"progressDeadlineSeconds":120}}'
kubectl patch deploy web -p '{"spec":{"revisionHistoryLimit":3}}'
kubectl patch deploy web -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
# Trigger, watch, inspect
kubectl set image deploy/web web=nginx:1.27 && kubectl rollout status deploy/web --timeout=120s
kubectl get deploy web -o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'
kubectl get rs -l app=web -o custom-columns='NAME:.metadata.name,REV:.metadata.annotations.deployment\.kubernetes\.io/revision,DESIRED:.spec.replicas,READY:.status.readyReplicas'
# History and rollback
kubectl annotate deploy/web kubernetes.io/change-cause="bump to 1.27" --overwrite
kubectl rollout history deploy/web --revision=2
kubectl rollout undo deploy/web --to-revision=2
kubectl rollout undo deploy/web --dry-run=server # preview
# Pause, batch, resume
kubectl rollout pause deploy/web
kubectl set image deploy/web web=nginx:1.28 && kubectl set env deploy/web LOG_LEVEL=debug
kubectl rollout resume deploy/web
Note that switching to Recreate requires nulling out rollingUpdate, because the API rejects a rollingUpdate block when the type is Recreate. When patching strategy fields, the strategic merge patch rules described in our kubectl patch guide apply: strategy is a struct, so fields merge; setting rollingUpdate: null deletes the key.
Common Mistakes
- Expecting
progressDeadlineSecondsto roll back. It only sets a condition and makesrollout statusexit non-zero. Rollback is your job or your CD tool’s. - Thinking a scale changes the revision. It does not. Neither do strategy or
minReadySecondsedits. - Confusing Ready with Available. With
minReadySecondsset, the rollout is paced by availability, not readiness. - Applying “rounds down” to both fields.
maxSurgerounds up,maxUnavailablerounds down. On 3 replicas the defaults are surge 1, unavailable 0. - Trusting the revision number after a rollback. The reused ReplicaSet gets a new higher number; the old number disappears from history.
- Setting
revisionHistoryLimit: 0“to save etcd space” and then being asked to roll back. - Trying to change
spec.selector. Immutable inapps/v1; recreate the Deployment. - Hand-scaling or deleting a Deployment’s ReplicaSet. The controller reverts it.
- Rolling back while paused. Resume first.
- Reading
kubectl get deploywithoutkubectl describe. The Conditions block andReplicaFailuremessage are where the two-minute diagnosis lives.
Practice Rollouts Until the Math Is Reflex
On the CKAD, Deployment tasks are worth doing fast because they are predictable: set a strategy, trigger a rollout, verify, roll back, with a twist such as a broken image or a quota. The twist is where time disappears if you have to reason out the surge math or hunt for the right status field. Sailor.sh’s CKAD Mock Exam Bundle runs these as timed, browser-based lab tasks against real clusters, with a validator that checks the resulting objects the same way the exam’s grader does, so stuck rollouts, ProgressDeadlineExceeded conditions and renumbered revisions stop being surprises. If you are organising your preparation, the CKAD study plan sequences Application Deployment early because it feeds every later domain, and the exam tips guide covers the aliases and --dry-run=client -o yaml habits that make the one-liners above second nature.
Frequently Asked Questions
Does changing replicas create a new revision?
No. Only a change to .spec.template creates a new ReplicaSet and revision. Scaling resizes existing ReplicaSets (proportionally if a rollout is in progress).
Why does my 3-replica Deployment never drop below 3 Pods during an update?
Because maxUnavailable: 25% rounds down to 0 for 3 replicas, while maxSurge: 25% rounds up to 1. The controller must surge a fourth Pod and wait for it to be available before removing an old one.
What is the difference between readiness and minReadySeconds?
Readiness is a point-in-time signal from the kubelet’s probe. minReadySeconds makes the Deployment controller require a Pod to stay Ready, with no container crash, for that many seconds before counting it as available and continuing the rollout.
Does Kubernetes roll back automatically when progressDeadlineSeconds is exceeded?
No. It sets Progressing=False with reason ProgressDeadlineExceeded and keeps retrying. kubectl rollout status exits non-zero. Rolling back is a manual or external-tool action.
Why does kubectl rollout history skip a number after a rollback?
A rollback reuses the old ReplicaSet (same template, same pod-template-hash) and assigns it the next revision number. The number it previously held is recorded in deployment.kubernetes.io/revision-history and no longer appears in the list.
Can I change a Deployment’s selector?
Not in apps/v1; it is immutable. Delete and recreate the Deployment, optionally with --cascade=orphan so the existing Pods keep running and are adopted by the new ReplicaSet if the selector still matches them.
What does kubectl rollout restart actually change?
It sets the annotation kubectl.kubernetes.io/restartedAt on the Pod template, which is a template change, so the controller performs a normal rolling update and records a new revision.
What is pod-template-hash and can I set it myself?
It is a label the Deployment controller adds to every ReplicaSet it manages and to their Pods, derived from a hash of the Pod template, so ReplicaSets of the same Deployment never select each other’s Pods. The documentation says not to change it, and you should never set it on Pods you create by hand.
Conclusion
A Deployment rollout is a small, deterministic machine. The controller hashes the Pod template, finds or creates a ReplicaSet named after that hash, and moves replicas between ReplicaSets under two constraints: at least replicas - maxUnavailable Pods available (rounded down from a percentage, and available means Ready for minReadySeconds), and at most replicas + maxSurge Pods in total (rounded up). Progress is reported through the Progressing, Available and ReplicaFailure conditions; stalling for progressDeadlineSeconds flips Progressing to False and makes rollout status fail, but nothing rolls back on its own. Revisions live in the old ReplicaSets, which is why revisionHistoryLimit bounds rollback and why rolling back reuses a ReplicaSet under a new number. Internalise those rules and every Deployment task on the CKAD, from “ensure zero downtime” to “the rollout is stuck, fix it”, becomes a matter of reading two status blocks and setting the right field.