Back to Blog

How a Kubernetes Deployment Rollout Actually Works for the CKAD Exam: ReplicaSets & the pod-template-hash, maxSurge/maxUnavailable Rounding Math, minReadySeconds & Availability, progressDeadlineSeconds & Deployment Conditions, Revision Annotations, Proportional Scaling, Rollback Internals and Paused Deployments

A practitioner's guide to the Deployment controller for the Certified Kubernetes Application Developer (CKAD) exam: how a Deployment owns ReplicaSets through the pod-template-hash label and collisionCount, what does and does not create a revision, how maxSurge (rounds up) and maxUnavailable (rounds down) turn into exact Pod counts with worked examples, why a Ready Pod is not yet Available under minReadySeconds, the Progressing/Available/ReplicaFailure conditions and their reasons, how progressDeadlineSeconds surfaces a stuck rollout without rolling back, proportional scaling during a rollout, the deployment.kubernetes.io/revision and revision-history annotations, what kubectl rollout undo really changes, revisionHistoryLimit, Recreate semantics, label selector immutability, ReplicaSet adoption and scale-down ordering, plus how to read status fields quickly in the exam.

By Sailor Team , October 31, 2026

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 revisionHistoryLimit controls how far back you can roll.
  • Scaling and rolling out are different operations. Changing .spec.replicas resizes 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:

ChangeNew ReplicaSet / revision?
kubectl set image, kubectl set env, kubectl set resourcesYes
kubectl rollout restart (adds kubectl.kubernetes.io/restartedAt to the template)Yes
Editing .spec.template.metadata.labels or annotationsYes
kubectl scale, HPA changes to .spec.replicasNo
Changing strategy, minReadySeconds, progressDeadlineSeconds, revisionHistoryLimit, pausedNo
Changing the Deployment’s own metadata.labels/annotationsNo

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 least replicas - maxUnavailable available Pods at all times.
  • maxSurge: “The absolute number is calculated from the percentage by rounding up.” It caps how many Pods may exist above replicas, so the total never exceeds replicas + maxSurge.
  • Both cannot be 0 at the same time; the API rejects it.

Worked examples with the 25% defaults:

replicasmaxUnavailable 25% → floormaxSurge 25% → ceilMinimum available during rolloutMaximum total Pods
1floor(0.25) = 0ceil(0.25) = 112
3floor(0.75) = 0ceil(0.75) = 134
4floor(1.0) = 1ceil(1.0) = 135
10floor(2.5) = 2ceil(2.5) = 3813
100252575125

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:

RequirementSetting
”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 fieldCounts Pods that…
replicasmatch the selector and are not terminating
readyReplicashave the Ready condition true
availableReplicashave 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

StatusReasonMeaning
TrueNewReplicaSetCreatedThe controller just created the new ReplicaSet
TrueFoundNewReplicaSetAn existing ReplicaSet already matches the template (typical after rollback)
TrueReplicaSetUpdatedScaling up the new or down the old ReplicaSet is in progress
TrueNewReplicaSetAvailableRollout complete: all replicas updated and available, no old replicas running
FalseProgressDeadlineExceededNo progress for progressDeadlineSeconds; the rollout is considered failed
FalseReplicaSetCreateErrorThe 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'
FieldMeaningExam use
observedGenerationThe .metadata.generation the controller has processedIf it lags metadata.generation, the controller has not seen your change yet
replicasNon-terminating Pods matching the selectorTotal across all ReplicaSets
updatedReplicasPods with the current templateEquals replicas when the rollout is done
readyReplicasPods with Ready=True
availableReplicasReady for ≥ minReadySecondsWhat the rolling update is paced by
unavailableReplicasPods still needed for 100% availability, running-but-not-available or not yet createdNon-zero mid-rollout or when quota blocks creation
terminatingReplicasPods with a deletionTimestamp that have not reached a terminal phaseBeta (on by default) since v1.35 behind DeploymentReplicaSetTerminatingReplicas; explains why node Pod counts exceed replicas during long terminations
collisionCountHash collisions resolved when naming ReplicaSetsAlmost always absent or 0
conditionsProgressing / Available / ReplicaFailureWhere 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 has controller: true and blockOwnerDeletion: 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 (or propagationPolicy: Orphan through 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 Pending before Unknown before Running, then not-Ready before Ready, then lower controller.kubernetes.io/pod-deletion-cost first (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

SymptomWhere to lookLikely cause and fix
rollout status hangs; READY 2/3, UP-TO-DATE 1kubectl get pods: new Pod ImagePullBackOffWrong image tag; kubectl set image to a valid tag or rollout undo
New Pod Running but never Ready; old Pods not scaled downkubectl describe pod: readiness probe failingFix probe path/port or app config; the controller resumes automatically
Progressing False ProgressDeadlineExceededConditions blockRollout stalled > progressDeadlineSeconds; fix the root cause, nothing rolls back by itself
ReplicaFailure True FailedCreate ... exceeded quotaConditions messageRaise quota or lower requests/replicas; see also maxSurge: 0 to avoid surging past quota
AVAILABLE lags READY by exactly N seconds.spec.minReadySecondsWorking as designed
Rollout completes with brief downtime on a 1-replica DeploymentmaxUnavailable: 1 or RecreateSet maxUnavailable: 0 so the controller surges first
Rollout exceeds the namespace Pod quotamaxSurge default 25% rounds up to ≥ 1Set maxSurge: 0, maxUnavailable: 1
rollout undo fails: “cannot rollback a paused deployment”.spec.pausedkubectl rollout resume first
rollout undo fails: no revision foundkubectl get rs shows no old ReplicaSetsrevisionHistoryLimit: 0 or history pruned; redeploy the old spec from your manifest
rollout history shows revisions 1, 3, 4Revision renumbering after rollbackNormal; check --revision=N templates to find the one you want
kubectl apply of a selector change fails with “field is immutable”spec.selectorDelete and recreate (--cascade=orphan to keep Pods) or change only template labels
CURRENT exceeds DESIRED during a scale-up mid-rolloutProportional scaling + surgeNormal; converges when the new ReplicaSet becomes healthy
Pod count on nodes exceeds replicas for minutes after a scale-down.status.terminatingReplicasLong terminationGracePeriodSeconds; see our lifecycle hooks and graceful shutdown guide
Three separate rollouts when you meant oneNo pauserollout pause, make changes, rollout resume
Need to restart Pods without changing anythingkubectl rollout restart deploy/webStamps 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

  1. Expecting progressDeadlineSeconds to roll back. It only sets a condition and makes rollout status exit non-zero. Rollback is your job or your CD tool’s.
  2. Thinking a scale changes the revision. It does not. Neither do strategy or minReadySeconds edits.
  3. Confusing Ready with Available. With minReadySeconds set, the rollout is paced by availability, not readiness.
  4. Applying “rounds down” to both fields. maxSurge rounds up, maxUnavailable rounds down. On 3 replicas the defaults are surge 1, unavailable 0.
  5. Trusting the revision number after a rollback. The reused ReplicaSet gets a new higher number; the old number disappears from history.
  6. Setting revisionHistoryLimit: 0 “to save etcd space” and then being asked to roll back.
  7. Trying to change spec.selector. Immutable in apps/v1; recreate the Deployment.
  8. Hand-scaling or deleting a Deployment’s ReplicaSet. The controller reverts it.
  9. Rolling back while paused. Resume first.
  10. Reading kubectl get deploy without kubectl describe. The Conditions block and ReplicaFailure message 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.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now