CoursesKubernetes administrationRolling updates & rollbacks

Rolling updates & rollbacks

Shipping a new version with no downtime, and undoing it.

Intermediate12 min · lesson 11 of 65
In plain terms
A rolling update is repainting a fence one plank at a time while people still lean on it — never a gap. And because the old paint cans are right there, you can undo the new colour in seconds.

Push a new image to a Deployment and watch the traffic graph. A couple of Pods die, a couple of new ones come up, and the line never dips. Nobody gets paged. That's a rolling update, and a Deployment does it by default every time you change its pod template. A Pod, if the word is new to you, is the smallest thing Kubernetes runs: one or more containers that start and stop together. The rolling part is the trick that lets you swap out every one of them without the app ever going dark.

Think of a busy diner changing shifts. You don't lock the doors, send the whole crew home, and hope the next crew clocks in on time. You swap people out a couple at a time so there's always someone at the grill. Kubernetes does the same with Pods. And the reason this lesson matters is that the two things that actually hurt in production, a release that's quietly broken and a release you need to undo right now, both live right here.

What the Deployment is really moving

Here's the part that trips people up. A Deployment doesn't touch Pods directly. Think of a general contractor: you hand them the plan, they don't pick up a hammer, they put a crew on the job. The Deployment is the contractor, a ReplicaSet is the crew, and the Pods are the workers. A ReplicaSet has exactly one job: keep N identical copies of a Pod running, no more and no fewer. When you change the Deployment's pod template (usually a new image), the deployment controller, a control loop running in the cluster's control plane, stands up a brand-new ReplicaSet for the new template and starts shifting replicas from the old crew to the new one, a batch at a time.

How big a batch is set by two knobs. maxSurge is how many extra Pods above your desired count the rollout may create. maxUnavailable is how many of your desired Pods may be missing at any moment. Both default to 25%. So on a 4-replica Deployment, Kubernetes might add one new Pod and drop one old one at a time, keeping at least three serving the whole way through.

terminal
kubectl set image deploy/web app=registry.local/app:1.5.0
output
deployment.apps/web image updated
terminal
kubectl get rs -l app=web
output
NAME DESIRED CURRENT READY AGE
web-6d4f8b9c7c 0 0 0 9m
web-7f9c5d8b64 4 4 4 40s

Notice the old ReplicaSet is still there, scaled to zero. Kubernetes keeps it on purpose. That empty-but-remembered ReplicaSet is the whole reason a rollback takes seconds instead of a rebuild, which we'll get to.

The readiness probe is the brake

A rolling update is only safe because of one gate: the readiness probe. A readiness probe is a check Kubernetes runs against each new Pod that asks, in effect, "can you take traffic yet?" Until the Pod answers yes, it isn't counted as ready, the Service (the stable internal address that spreads traffic across your Pods) drops it from rotation, and the rollout will not move on to the next batch. It's the new hire who doesn't get put on the floor with real customers until they say they're good to go.

Skip this and the whole safety net is gone. With no readiness probe, a Pod counts as ready the instant its container process starts. So a release that boots, throws an error, and serves 500s to everyone still looks perfectly ready to the controller. The rollout marches to completion and replaces every healthy Pod with a broken one. The mechanics did exactly what you told them. Nothing ever told them the new version was sick.

web-deployment.yaml
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: app
image: registry.local/app:1.5.0
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

With maxUnavailable set to 0, Kubernetes refuses to remove a healthy old Pod until a new one reports ready. So if the new version can't pass /healthz, the rollout doesn't tear anything down. It just stops and waits. It waits until progressDeadlineSeconds runs out (600 seconds by default), then the controller marks the rollout failed and rollout status exits non-zero, which is exactly what you want a deploy pipeline to catch. Here is what that looks like on the release after the one above: 1.5.0 landed fine and web-7f9c5d8b64 is serving all four Pods, and the next image you push, app:1.6.0, is broken.

terminal
kubectl rollout status deploy/web
output
Waiting for deployment "web" rollout to finish: 1 out of 4 new replicas have been updated...
error: deployment "web" exceeded its progress deadline

When status says that, don't guess. Look at the Pods. A stuck rollout is almost always one of three things: the new Pods can't schedule (Pending), the container keeps dying (CrashLoopBackOff), or it runs but never passes readiness (Running, 0/1). kubectl get pods tells you which, and kubectl describe pod names the exact reason down in its events.

terminal
kubectl get pods -l app=web
output
NAME READY STATUS RESTARTS AGE
web-7f9c5d8b64-2xk9p 1/1 Running 0 12m
web-7f9c5d8b64-8mzt4 1/1 Running 0 12m
web-7f9c5d8b64-qv7rn 1/1 Running 0 12m
web-7f9c5d8b64-h4d8s 1/1 Running 0 12m
web-5c7d94b8f6-lp4qd 0/1 CrashLoopBackOff 4 2m

All four old Pods still serving, one new Pod flailing. The old version is carrying every request while the rollout sits politely stuck. Nothing is down. That's the readiness probe and maxUnavailable:0 doing their job together, and it's the calmest kind of failed deploy you can have.

Undoing a bad release

The old paint cans are still on the shelf. Because that previous ReplicaSet is sitting there scaled to zero, undoing a release just scales it back up and scales the new one down, the same rolling mechanics running in reverse. Nothing gets rebuilt, and the old image is almost always still cached on the nodes, so the previous version is back in seconds. Every template change is recorded as a numbered revision, so you can list them and pick a known-good one.

terminal
kubectl rollout history deploy/web
output
deployment.apps/web
REVISION CHANGE-CAUSE
1 <none>
2 <none>
3 <none>
4 <none>

CHANGE-CAUSE is empty because the old --record flag is deprecated (it saved the literal command you ran, which was rarely what you wanted to read back later). If you want a readable history, set it yourself on each change with kubectl annotate deploy/web kubernetes.io/change-cause="...". To reverse the bad release, roll back to a specific revision and confirm it converged.

terminal
kubectl rollout undo deploy/web --to-revision=3 && kubectl rollout status deploy/web
output
deployment.apps/web rolled back
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out
Rollback only rewinds the pod template
A Deployment records a new revision only when its pod template changes. Edit a ConfigMap or Secret that your Pods read, and nothing rolls: no new revision, and kubectl rollout undo has nothing to reverse. Worse, when you do roll the Deployment back, it restores the old image but leaves the changed ConfigMap in place, so old code meets new config. If a release ships config and image together, treat the config as part of the release. Give the ConfigMap a new name or a content hash in its name so a template change actually triggers a rollout, and roll both back together.
Why rollback is instant
Deployment: web (desired 4)
pod template points at app:1.5.0
revision 3, current
ReplicaSet web-7f9 (revision 3)
4 / 4 Ready
serving all traffic right now
ReplicaSet web-6d4 (revision 2)
scaled to 0, kept
undo just scales this back up

surge pods need spare CPU and memory. A full cluster cannot honor maxSurge and then looks hung.

record or annotate every set image. Future you will thank present you.

Abort a bad rollout early. Waiting for all replicas to go unhealthy is how outages get long.

Try this

Trigger a rolling update, watch pods replace gradually, then roll back. Capture rollout history so you can name the revision you restored.

terminal
$ kubectl set image deploy/web app=registry.local/app:1.5.0
$ kubectl get rs -l app=web
$ kubectl rollout status deploy/web
$ kubectl get pods -l app=web
$ kubectl rollout history deploy/web
$ kubectl rollout undo deploy/web --to-revision=2 && kubectl rollout status deploy/web

Takeaway

Ship by rolling; undo by revision. Readiness gates the old pods leaving. If readiness is wrong, rollouts stall or dump bad traffic.

Quick check
01kubectl rollout status prints "exceeded its progress deadline". kubectl get pods shows the new Pods Running but 0/1 READY, and the Deployment has maxUnavailable: 0. Is the app currently down?
Incorrect — No. A failed rollout doesn't mean lost capacity. With maxUnavailable:0, Kubernetes never removed a healthy old Pod, so the previous version is still fully serving.
Correct — Readiness gates both the rollout and Service routing, and maxUnavailable:0 guarantees old Pods stay until new ones pass. The stall is safe.
Incorrect — No. A Pod that is 0/1 READY is pulled out of the Service endpoints entirely, so it receives no traffic at all. Users only ever reach the old, healthy Pods.
Incorrect — You can. 0/1 READY means those Pods are already excluded from the endpoints regardless of the Service, and maxUnavailable:0 keeps the old version live.
02A release changed both the container image and a ConfigMap the pods read. The image is bad, so you run kubectl rollout undo. What state does that leave you in?
Incorrect — A rollback only touches the pod template; a separately edited ConfigMap is not part of any recorded revision.
Incorrect — The undo does revert the image; the real problem is that the ConfigMap is not reverted, not that undo does nothing.
Incorrect — It is the reverse: undo restores the old image while the changed ConfigMap stays in place.
Correct — a Deployment records a revision only when its pod template changes, so undo rewinds the image but leaves the edited ConfigMap; version the ConfigMap (new name or content hash) so config rides along with the template.
03kubectl rollout status reports 'exceeded its progress deadline.' kubectl get pods shows the new pod stuck in Pending while the old pods are still Running. What is the most likely cause?
Correct — Pending means unscheduled, so with maxSurge adding a pod that no node can fit, the rollout cannot advance. This differs from a pod that schedules but crash-loops or never passes readiness.
Incorrect — A crashing container shows CrashLoopBackOff, not Pending; Pending means the pod was never placed on a node.
Incorrect — A failing readiness probe shows the pod Running but 0/1 READY, not Pending, since readiness is only checked once the container runs.
Incorrect — The old ReplicaSet is kept and its pods are still Running; a Pending new pod is a scheduling problem, not a missing old version.

Related