Definition
A rolling update is Kubernetes' default Deployment strategy.
- Rolling update – replaces old Pods with new ones gradually, governed by
maxSurge(extra Pods above desired count) andmaxUnavailable(Pods missing during the update). - Readiness gate – Kubernetes only counts a new Pod as available once its readiness probe passes, so a broken version stalls the rollout instead of taking down the service.
- Rollback –
kubectl rollout undoreverts the Deployment to a previous ReplicaSet revision by scaling the old ReplicaSet back up. - Limitation – a rollback only restores the Pod template (image, env, resources); it does NOT revert ConfigMaps, Secrets, or database schemas that changed alongside the deployment.
Imagine the National Bank of India is replacing the teller software on 10 counters. A rolling update upgrades one or two counters at a time while the rest keep serving Rajesh and Priya. If the new software is buggy, rollout undo tells every counter to go back to yesterday's version. But if yesterday's upgrade also rewrote the shared ledger format in the vault, rolling back the counter software does NOT un-rewrite the ledger. That permanent change outlives the rollback.
Understanding the Concepts
How does a rolling update work?
When you change a Deployment's Pod template, such as updating the image tag, Kubernetes creates a new ReplicaSet and scales it up while scaling the old ReplicaSet down. The two knobs that control the pace are maxSurge and maxUnavailable.
# deployment.yaml - rolling update strategy for the transfer service
apiVersion: apps/v1
kind: Deployment
metadata:
name: transfer-service
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # allow 1 extra Pod during the update
maxUnavailable: 0 # never drop below 4 available Pods
selector:
matchLabels:
app: transfer-service
template:
metadata:
labels:
app: transfer-service
spec:
containers:
- name: transfer
image: nbi-registry.example.com/transfer-service:2.0.0
ports:
- containerPort: 8080
# Trigger a rolling update by changing the image
kubectl set image deployment/transfer-service \
transfer=nbi-registry.example.com/transfer-service:2.1.0
# Watch the rollout progress
kubectl rollout status deployment/transfer-service
# Waiting for deployment "transfer-service" rollout to finish:
# 1 out of 4 new replicas have been updated...
# 2 out of 4 new replicas have been updated...
# deployment "transfer-service" successfully rolled out
What do maxSurge and maxUnavailable control?
maxSurge sets how many Pods may exist above the desired count during the transition. A value of 25% on 4 replicas allows 1 extra Pod, meaning up to 5 Pods may run at once. maxUnavailable sets how many of the desired Pods may be missing at once. A value of 25% on 4 replicas means at least 3 must stay serving.
Setting maxUnavailable: 0 with maxSurge: 1 gives zero-downtime, one-at-a-time ramp, which is the right setting for a banking payment service.
| Setting | Behaviour | Use case |
|---|---|---|
| maxSurge: 1, maxUnavailable: 0 | One new Pod up before one old Pod down | Zero-downtime banking service |
| maxSurge: 25%, maxUnavailable: 25% | Faster rollout, slight capacity dip | Internal batch services |
| maxSurge: 0, maxUnavailable: 1 | No extra Pods, one less at a time | Resource-constrained clusters |
# Zero-downtime setting for the payment service
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# Fast rollout for internal tools
strategy:
rollingUpdate:
maxSurge: "50%"
maxUnavailable: "25%"
How does readiness protect the rollout?
Readiness probes are essential during a rolling update. Kubernetes only counts a new Pod as available once its readiness probe passes. If the new version has a bug and never becomes ready, the rollout stalls instead of killing all old Pods. You can bound this with progressDeadlineSeconds, after which the Deployment is marked failed.
spec:
progressDeadlineSeconds: 120 # mark rollout failed after 2 minutes of no progress
template:
spec:
containers:
- name: transfer
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
failureThreshold: 3
# A broken version stalls the rollout
kubectl rollout status deployment/transfer-service
# Waiting for deployment... deadline exceeded
# error: deployment "transfer-service" exceeded its progress deadline
How do you roll back?
Kubernetes keeps a history of ReplicaSet revisions, limited by revisionHistoryLimit. Rolling back simply scales the chosen old ReplicaSet back up and the current one down. Rollback is itself a rolling operation that respects the same surge and unavailability rules.
# View rollout history
kubectl rollout history deployment/transfer-service
# REVISION CHANGE-CAUSE
# 1 initial deployment
# 2 image update to 2.0.0
# 3 image update to 2.1.0
# Roll back to the immediately previous revision
kubectl rollout undo deployment/transfer-service
# Roll back to a specific revision
kubectl rollout undo deployment/transfer-service --to-revision=1
# Verify the rollback
kubectl rollout status deployment/transfer-service
Why might a rollback not restore the previous state?
A Deployment revision snapshots only the Pod template. If version 6 shipped alongside a new ConfigMap value, a new Secret, or a forward database migration, then rollout undo restores the old container image but leaves the ConfigMap, Secret, and database schema at their new values. If the old image cannot read the new schema, rollback makes things worse.
# Scenario: version 2.1.0 adds a NOT NULL column to the trades table
# Rollback restores version 2.0.0, but the column still exists
# The old code does not populate the column -> every INSERT fails
kubectl rollout undo deployment/transfer-service
# Pod template is now 2.0.0
# But the database still has the NOT NULL column from 2.1.0
# Result: 500 errors on every transfer
How do you make rollbacks safe?
The discipline is to make deployments rollback-safe by construction. Database migrations follow an expand-then-contract pattern. Configuration is shipped as a new immutable ConfigMap per release.
# WRONG - mutate a shared ConfigMap in place
# Rollback reverts the image but NOT the ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: transfer-config # same name, changed data
data:
fee.percentage: "0.5" # was 0.25 before this release
# CORRECT - immutable ConfigMap per release, referenced from the Pod template
apiVersion: v1
kind: ConfigMap
metadata:
name: transfer-config-v7 # new name per release
data:
fee.percentage: "0.5"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: transfer-service
spec:
template:
spec:
containers:
- name: transfer
envFrom:
- configMapRef:
name: transfer-config-v7 # part of Pod template -> rollback reverts it
-- WRONG - breaking migration: add NOT NULL column
ALTER TABLE trades ADD COLUMN fee_type VARCHAR(20) NOT NULL;
-- Old code does not know about fee_type -> rollback fails
-- CORRECT - expand-then-contract: add nullable first
-- Step 1 (release N): add column as nullable
ALTER TABLE trades ADD COLUMN fee_type VARCHAR(20);
-- Step 2 (release N): new code writes to it, old code ignores it
-- Step 3 (release N+1): make it NOT NULL after all old code is gone
ALTER TABLE trades ALTER COLUMN fee_type SET NOT NULL;
What is progressDeadlineSeconds?
This field sets a deadline for the rollout to make progress. If no new Pods become ready within this window, Kubernetes marks the Deployment's condition as ProgressDeadlineExceeded. It does not automatically roll back, but it gives your pipeline a signal to trigger a rollback.
spec:
progressDeadlineSeconds: 180 # 3 minutes to make progress
revisionHistoryLimit: 10 # keep 10 old ReplicaSets for rollback
# Check the condition
kubectl get deployment transfer-service -o jsonpath='{.status.conditions[?(@.type=="Progressing")].reason}'
# ProgressDeadlineExceeded
What should a senior engineer know?
A senior engineer always designs database migrations to be backward compatible. The expand-then-contract pattern means both the old and new code can run against the same schema during and after a rollout. This makes rollback safe at every point.
For configuration, avoid mutating a ConfigMap in place. Create a new immutable ConfigMap per release and reference it from the Pod template, so that a template rollback automatically points back at the old config.
The --atomic flag in Helm automatically rolls back if an upgrade fails, combining the progress deadline with automatic rollback.
# Helm upgrade with automatic rollback on failure
helm upgrade --install transfer-service ./chart \
-f values-prod.yaml \
--atomic \
--timeout 5m
Visual Overview
Rolling Update Flow (maxSurge: 1, maxUnavailable: 0, replicas: 4)
==================================================================
Step 1: Create 1 new Pod (surge)
Old RS: [Pod-A] [Pod-B] [Pod-C] [Pod-D] (4 running)
New RS: [Pod-E starting...] (0 ready)
Total: 5 Pods (4 available)
Step 2: New Pod passes readiness, retire 1 old Pod
Old RS: [Pod-B] [Pod-C] [Pod-D] (3 running)
New RS: [Pod-E ready] (1 ready)
Total: 4 Pods (4 available)
Step 3: Create next new Pod
Old RS: [Pod-B] [Pod-C] [Pod-D] (3 running)
New RS: [Pod-E] [Pod-F starting...] (1 ready)
Total: 5 Pods (4 available)
... repeat until all old Pods are replaced ...
Step 7: Complete
Old RS: (scaled to 0, kept for rollback)
New RS: [Pod-E] [Pod-F] [Pod-G] [Pod-H] (4 ready)
Rollback: scale old RS up, new RS down (same rolling process)
Key Takeaway
A rolling update is the default way Kubernetes replaces Pods, and it is controlled by two knobs. The surge setting is how many Pods may temporarily run above the desired count, and the unavailable setting is how many may be missing. For a banking payment service, setting surge to one and unavailable to zero means Kubernetes adds one new Pod, waits for its readiness probe to pass, and only then retires one old Pod, so capacity never dips.
Rolling back is fast because Kubernetes keeps a history of previous ReplicaSets. Running rollout undo simply scales the old ReplicaSet back up and the current one down, respecting the same surge and unavailability rules. You can roll back to the immediately previous revision or target a specific revision number.
The subtlety that separates a junior from a senior answer is understanding what rollback does not do. A Deployment revision only snapshots the Pod template. It does not capture ConfigMaps, Secrets, or database schemas. If a release shipped a new ConfigMap value or a forward migration, rolling back the image leaves that external state at its new value. The fix is to design deployments to be rollback-safe: database changes follow expand-then-contract, and configuration is shipped as immutable ConfigMaps referenced from the Pod template.
Remember one sentence: rollout undo reverts the Pod template instantly, but it never undoes ConfigMaps, Secrets, or database migrations, so design every release to be rollback-safe by construction.
---