Skip to content
Aleix Raventós
~6 min read

Rolling Updates and Rollbacks in Kubernetes

A common mental model for a Deployment is "it keeps X pods running." That's true, but it leaves out the part that matters most when something goes wrong: Kubernetes doesn't manage your pods directly. It manages ReplicaSets, and that distinction matters.

A ReplicaSet is a controller with one job: ensure that exactly N identical pods are running at any given time. A Deployment is a controller one level above that, managing the lifecycle of ReplicaSets. This two-layer setup is what makes zero-downtime updates and instant rollbacks possible. The Deployment doesn't recreate anything. It just switches which ReplicaSet is in charge and adjusts the scale.

When you update a Deployment (change a container image, for example), Kubernetes doesn't kill all your old pods and start fresh. That would cause downtime. Instead, it creates a new ReplicaSet with the updated pod spec and starts a controlled handover: new pods spin up while old pods scale down. Your application keeps serving traffic the whole time.

The Deployment never recreates pods. It scales ReplicaSets up and down.

How fast does it go? By default, you won't have more than 25% extra pods running (maxSurge), and at least 75% of your desired pods stay online (maxUnavailable). Both ReplicaSets scale at the same time: the old one down, the new one up. For a 4-pod Deployment, 25% works out to 1 pod, so you get at most 5 pods total and at least 3 available throughout the rollout. The rounding rule is asymmetric and worth knowing: maxSurge rounds up (more headroom is safer), maxUnavailable rounds down (more availability is safer). At 10 pods that means up to 13 total (maxSurge = 3) and at least 8 available (maxUnavailable = 2). Both are tunable in the Deployment's strategy field if you need faster updates or tighter guarantees.

This is the RollingUpdate strategy, and it's the default.

What happens to the old ReplicaSet when the new one takes over? It doesn't disappear. Kubernetes keeps it around, scaled to zero, ready to take back over if needed. That's what makes rollbacks fast, they're not rebuilds. Under the hood, rolling back rewrites the Deployment's pod template to match an older revision, and because the old ReplicaSet still has that exact template, the controller just scales it back up instead of creating a new one.

So what actually triggers a new rollout? Any change to the pod template inside the Deployment spec creates a new ReplicaSet. Bump the image, change an env var, adjust resource limits, and a rollout begins. Changing things outside the pod template (like the Deployment's own labels or annotations) won't trigger one, because those don't affect what runs inside the pods. Same Deployment, new ReplicaSet, gradual pod swap.

Kubernetes tracks each rollout as a numbered revision, stored as a deployment.kubernetes.io/revision annotation on the ReplicaSet (and mirrored on the Deployment itself, pointing at the current revision). By default it keeps the last 10 revisions around, which you can tune with revisionHistoryLimit on the Deployment. You can inspect the history with:

kubectl rollout history deployment/my-app

The output shows revision numbers, but "CHANGE-CAUSE" stays empty unless you explicitly annotated each deploy with kubernetes.io/change-cause. That annotation is largely superseded by kubectl rollout history --revision=N, which prints the full pod template for any past revision.


Watching a rollout happen

To trigger an update, you'd run:

kubectl set image deployment/my-app my-app=nginx:1.17.0

Then poll the status:

kubectl rollout status deployment/my-app

This prints a running tally of updated, ready, and available replicas as the rollout progresses, finishing with successfully rolled out once the new ReplicaSet has fully taken over.

In Kunobi, you can watch the ReplicaSet handover happen directly through the resource drilldown. Open the Deployments view, select my-app, and drill in. The breadcrumb at the top makes the chain explicit: Deployments=my-app / ReplicaSets=… / Pods=…. In steady state the table shows the 4 pods of the single active ReplicaSet.

Now step back up to the ReplicaSets level and run the kubectl command. A new ReplicaSet row appears in the same view almost immediately. The old one's Replicas column starts dropping while the new one's climbs. Both rows stay visible the whole time, so the handover is something you watch happen, not something you reconstruct from logs after the fact.

When the rollout finishes, the old ReplicaSet stays in the list with 0/0. It's still revision 1, still in the cluster, ready to take back over.


Rolling back

To rollback, select the Deployment and press Shift+B. A dialog opens listing every revision Kubernetes still has, each with its revision number, ReplicaSet name, image tag, and timestamp. The current one is tagged.

Pick a revision and confirm. Kubernetes rewrites the Deployment's pod template to match the chosen revision; the old ReplicaSet (the one whose template you picked) scales back up while the current one scales down. It's the same handover as a rollout, just pointing backwards. Worth noting: the rolled-back ReplicaSet gets bumped to a new revision number in the history, even though its pod template is unchanged.

Every ReplicaSet stays in the list after rollback. You could roll forward again if you change your mind. Rollback isn't destructive; no pods are rebuilt, just rewired.


One more thing: Recreate

One aside before we close: rolling update isn't the only strategy. The other option is Recreate, which kills all old pods before starting new ones. It guarantees a brief window of downtime, but it's the right call when your application can't run two versions simultaneously, like some database schema migrations. For anything that serves traffic continuously, rolling update is what you want.


Wrapping up

The mental model that makes Kubernetes deployments click is the two-layer one: Deployments don't move pods around, they move ReplicaSets. Once you internalize that, everything else falls into place: rollouts are just a controlled handover between two ReplicaSets, and rollbacks are the same handover pointed the other way. The old ReplicaSets stick around scaled to zero precisely so that flip is cheap.

We used Kunobi here because seeing both ReplicaSets in the same table while the rollout is happening makes the model concrete in a way that polling kubectl rollout status doesn't. But the underlying mechanism is the same regardless of how you observe it.

$ tail -f /dev/blog

Cluster updates, in your inbox.

Kubernetes deep dives, GitOps field notes, and platform-engineering essays from the team building Kunobi. Two posts a month. No fluff.

$ also availableThe Kunobi desktop app. Every cluster, one window.
Try Kunobi now
Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi