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 is everything.
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% rounds up to 1 pod, so you get at most 5 pods total and at least 3 available throughout the rollout. (The math doesn't scale linearly: 25% of 10 pods rounds differently for surge and unavailable, so always check the rounding for your size.) 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. You're just telling Kubernetes to flip back to the previous ReplicaSet, which still has its original pod template and never needed to be recreated.
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 — and 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=my-app:v2
Then poll the status:
kubectl rollout status deployment/my-app
This shows percentage progress as the rollout happens.
In Kunobi, you can watch the ReplicaSet handover happen directly through the resource drilldown. Open the Deployments view, select my-app, and drill into its ReplicaSets. The breadcrumb at the top makes the relationship explicit — Deployments=my-app / ReplicaSets=my-app — and in the steady state, the table below has exactly one row: one ReplicaSet, 4/4 ready.
Now run the kubectl command. A second 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 — and the current one tagged.
Pick a revision and confirm. Kubernetes scales the previous ReplicaSet back up and the current one back down — the same handover as a rollout, just pointing backwards.
Every ReplicaSet stays in the list after rollback. You could roll forward again if you change your mind. Rollback isn't destructive; it's a pointer flip.
One more thing: Recreate
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 — some database schema migrations, for instance. 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.
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.

