64.1 Rolling updates: replace pods without downtime
Why: the problem
Every team ships new versions all the time. At Swiggy a single frontend may be released many times a day. The default way Kubernetes does this is a rolling update. When you change anything in a Deployment's pod template, such as the image, an environment variable or a resource limit, the Deployment controller creates a new ReplicaSet for the new template. It then scales the new ReplicaSet up and the old one down, a few pods at a time, until all pods run the new version. Users keep getting answers the whole time because some pods are always serving. Two fields control the speed and safety of this dance. maxSurge says how many extra pods may exist above the desired replica count during the update. maxUnavailable says how many of the desired pods may be missing. Both accept a number or a percentage, and both default to 25%. With 4 replicas and the defaults, Kubernetes may run up to 5 pods and must keep at least 3 available. Setting maxUnavailable to 0 and maxSurge to 1 gives the safest rollout: a new pod must be ready before an old one goes away. Setting maxSurge to 0 and maxUnavailable to 1 is useful when the cluster has no spare capacity. "Available" depends on readiness, which you learned in 63.2. A new pod counts only when it is Ready and has stayed Ready for minReadySeconds. If the new pods never become ready, the rollout stops and waits. After progressDeadlineSeconds (600 by default) the Deployment gets a condition Progressing=False with reason ProgressDeadlineExceeded. Kubernetes does not roll back by itself; it simply stops and leaves the old pods serving. You will roll back in 64.3. The other strategy is Recreate. It kills all old pods first and only then starts the new ones. That means downtime, but it is the right choice when two versions must never run together, for example an app that holds an exclusive lock on a disk volume or a database schema change that old code cannot handle. You can also pause a rollout, make several changes and resume it, so the change becomes a single new revision instead of many. In this lesson we run a rolling update under live traffic, compare different surge settings, watch a broken release stall, and try the Recreate strategy to see the gap it causes.
What: every term in plain English
- Rolling update — Default Deployment strategy that replaces pods gradually.
- Recreate — Strategy that deletes all old pods before creating new ones.
- ReplicaSet — Object that keeps a set number of identical pods; each template version gets one.
- pod-template-hash — Label that ties pods to their ReplicaSet.
- maxSurge — Extra pods allowed above replicas during an update; default 25%.
- maxUnavailable — Desired pods allowed to be missing during an update; default 25%.
- minReadySeconds — Time a new pod must stay ready before it counts as available.
- progressDeadlineSeconds — Time after which a stuck rollout is marked failed; default 600.
- ProgressDeadlineExceeded — Reason on the Progressing condition when the deadline passes.
- rollout status — kubectl command that waits for a rollout to finish or fail.
- rollout pause and resume — Stop a rollout, batch several changes, then continue.
- rollout restart — Triggers a new rollout with the same spec by adding an annotation.
How: step by step
- Create kind cluster ch64. Three nodes.
- Deploy version 1 of the shop frontend with four replicas. Four ready pods.
- Start a request loop. Every reply shows the version.
- Update to version 2. Pods replace a few at a time.
- Watch ReplicaSets. Old scales down while new scales up.
- Try different surge settings. See the speed and pod count change.
- Release a broken version. Rollout stalls; old pods serve.
- Try Recreate. See the downtime gap.
- Clean up. Ready for 64.2.
Example 1: Rolling update under live traffic
Goal. Rolling update under live traffic.
kind-ch64.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
shop-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop
namespace: roll
spec:
replicas: 4
minReadySeconds: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: shop
template:
metadata:
labels:
app: shop
spec:
containers:
- name: web
image: hashicorp/http-echo:1.0
args:
- -listen=:8080
- -text=shop v1
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /
port: 8080
periodSeconds: 2
---
apiVersion: v1
kind: Service
metadata:
name: shop
namespace: roll
spec:
selector:
app: shop
ports:
- port: 80
targetPort: 8080
loop.sh
kubectl run loop -n roll --image=busybox:1.36 --restart=Never -- sh -c '
while true; do
r=$(wget -q -T 1 -O- http://shop 2>/dev/null || echo ERROR)
echo "$(date +%T) $r"
sleep 0.2
done'
What each line does
maxSurge 1, maxUnavailable 0— At most 5 pods, never fewer than 4 ready: the safest setting.minReadySeconds 3— Each new pod must stay ready 3 seconds before the next step.http-echo— Tiny server that returns the -text argument, so we can see the version.readinessProbe— Lets the Deployment know when a new pod is really serving.loop.sh— Prints a timestamp and the reply five times a second, or ERROR.
Green output
$ kind create cluster --name ch64 --config kind-ch64.yaml
$ kubectl create ns roll
$ kubectl apply -f shop-v1.yaml
$ bash loop.sh
$ kubectl patch deploy shop -n roll --type=json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/args/1","value":"-text=shop v2"}]'
deployment.apps/shop patched
$ kubectl rollout status deploy/shop -n roll
Waiting for deployment "shop" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
deployment "shop" successfully rolled out
$ kubectl logs -n roll loop --tail=12
10:41:02 shop v1
10:41:02 shop v2
10:41:03 shop v1
10:41:03 shop v2
10:41:04 shop v2
Expected result. During the rollout both versions answer, then only v2. There is no ERROR line, because a new pod was always ready before an old one left.
Example 2: Compare surge settings and watch ReplicaSets
Goal. Compare surge settings and watch ReplicaSets.
fast.yaml
spec:
strategy:
rollingUpdate:
maxSurge: 100%
maxUnavailable: 0
tight.yaml
spec:
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
watch-rs.sh
kubectl get rs -n roll -l app=shop -w -o custom-columns=NAME:.metadata.name,DESIRED:.spec.replicas,READY:.status.readyReplicas
What each line does
maxSurge 100%— Creates all new pods at once, then removes old ones: fast but doubles pods for a moment.maxSurge 0, maxUnavailable 1— No extra pods; one old pod goes away before each new one starts. Good for a full cluster.watch-rs.sh— Shows desired and ready counts of both ReplicaSets live.custom-columns— Keeps the output short and readable.Revision— Each patch here creates a new ReplicaSet with a new pod-template-hash.
Green output
$ kubectl patch deploy shop -n roll --patch-file fast.yaml
$ bash watch-rs.sh &
$ kubectl set env deploy/shop -n roll RELEASE=3
NAME DESIRED READY
shop-5d8f7c9b6 4 4
shop-7b9c6d4f8 4 (none)
shop-7b9c6d4f8 4 4
shop-5d8f7c9b6 0 4
shop-5d8f7c9b6 0 (none)
$ kubectl patch deploy shop -n roll --patch-file tight.yaml
$ kubectl set env deploy/shop -n roll RELEASE=4
shop-7b9c6d4f8 3 3
shop-66c4d9f7b 1 (none)
shop-66c4d9f7b 1 1
shop-7b9c6d4f8 2 2
Expected result. With 100% surge, four new pods appeared together and the old set dropped to zero in one step. With surge 0 the old set shrank first, one pod at a time, and capacity briefly dropped to three.
Example 3: A broken release stalls, and Recreate shows a gap
Goal. A broken release stalls, and Recreate shows a gap.
bad.sh
kubectl patch deploy shop -n roll --type=json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/port","value":9999}]'
kubectl patch deploy shop -n roll -p '{"spec":{"progressDeadlineSeconds":60}}'
kubectl rollout status deploy/shop -n roll --timeout=90s
recreate.yaml
spec:
strategy:
type: Recreate
rollingUpdate: null
What each line does
Port 9999— Nothing listens there, so new pods never become ready.progressDeadlineSeconds 60— Marks the rollout failed after one minute instead of ten.Old pods— Stay up and serve traffic the whole time.Recreate— Kills all old pods, then starts new ones.rollingUpdate null— Must be removed when switching to Recreate, or the API rejects the change.
Green output
$ bash bad.sh
Waiting for deployment "shop" rollout to finish: 1 out of 4 new replicas have been updated...
error: deployment "shop" exceeded its progress deadline
$ kubectl get deploy shop -n roll -o jsonpath='{.status.conditions[?(@.type=="Progressing")].reason}'
ProgressDeadlineExceeded
$ kubectl logs -n roll loop --tail=3
10:52:10 shop v2
10:52:10 shop v2
10:52:11 shop v2
$ kubectl rollout undo deploy/shop -n roll
$ kubectl patch deploy shop -n roll --patch-file recreate.yaml
$ kubectl set env deploy/shop -n roll RELEASE=5
$ kubectl logs -n roll loop --tail=20 | grep -c ERROR
14
Expected result. The broken release never got past one new pod, and users only ever saw v2. Recreate gave about three seconds of ERROR lines, which is why it is used only when two versions must not run at the same time.
Every command, step by step (kind lab with kubectl)
-
Cluster
kind create cluster --name ch64 --config kind-ch64.yamlWhat it does: Three nodes.
Creating cluster "ch64" ... -
Namespace
kubectl create ns rollWhat it does: Lab space.
namespace/roll created -
Deploy v1
kubectl apply -f shop-v1.yamlWhat it does: Four pods.
deployment.apps/shop created service/shop created -
Loop
bash loop.shWhat it does: Request loop.
pod/loop created -
Update
kubectl patch deploy shop -n roll --type=json -p '...'What it does: v2 rolls out.
deployment.apps/shop patched -
Status
kubectl rollout status deploy/shop -n rollWhat it does: Waits.
deployment "shop" successfully rolled out -
ReplicaSets
kubectl get rs -n rollWhat it does: Old at 0.
shop-5d8f7c9b6 0 0 0 shop-7b9c6d4f8 4 4 4 -
Fast
kubectl patch deploy shop -n roll --patch-file fast.yamlWhat it does: 100% surge.
deployment.apps/shop patched -
Tight
kubectl patch deploy shop -n roll --patch-file tight.yamlWhat it does: No surge.
deployment.apps/shop patched -
Broken
bash bad.shWhat it does: Stalls.
error: deployment "shop" exceeded its progress deadline -
Undo
kubectl rollout undo deploy/shop -n rollWhat it does: Back.
deployment.apps/shop rolled back -
Recreate
kubectl patch deploy shop -n roll --patch-file recreate.yamlWhat it does: Gap.
deployment.apps/shop patched -
Clean up
kubectl delete ns rollWhat it does: Ready for 64.2.
namespace "roll" deleted
When it is red 1
Rollout stuck at 1 out of 4 new replicas
Cause. New pods are not ready.
Fix, step by step
- kubectl describe pod for probe errors.
- Fix and re-apply or roll back.
When it is red 2
exceeded its progress deadline
Cause. progressDeadlineSeconds passed.
Fix, step by step
- Check events.
- kubectl rollout undo.
When it is red 3
spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy type is 'Recreate'
Cause. Old rollingUpdate block left in place.
Fix, step by step
- Set rollingUpdate to null in the patch.
When it is red 4
maxUnavailable and maxSurge cannot both be 0
Cause. Rollout could never make progress.
Fix, step by step
- Set one of them to at least 1.
When it is red 5
Pending new pods during rollout
Cause. No room for surge pods.
Fix, step by step
- Lower maxSurge or add nodes.
When it is red 6
Users see errors during deploy
Cause. No readiness probe or no graceful shutdown.
Fix, step by step
- Add readiness and preStop as in 63.2.
Practice tasks
Easy Task 1: Cluster
Why: Setup.
What to do: Create ch64.
- kind config.
Solution
kubectl get nodesEasy Task 2: v1
Why: Deploy.
What to do: Apply shop-v1.yaml.
- Four pods.
Solution
kubectl get pods -n rollEasy Task 3: Loop
Why: Traffic.
What to do: Run loop.sh.
- busybox.
Solution
kubectl logs -n roll loop -fEasy Task 4: v2
Why: Update.
What to do: Change text to v2.
- patch json.
Solution
kubectl rollout status deploy/shop -n rollMedium Task 5: RS watch
Why: Observe.
What to do: Watch ReplicaSets during an update.
- watch-rs.sh.
Solution
kubectl get rs -n rollMedium Task 6: Fast
Why: Surge.
What to do: Use 100% surge.
- fast.yaml.
Solution
kubectl get pods -n rollMedium Task 7: Tight
Why: No surge.
What to do: Use surge 0.
- tight.yaml.
Solution
kubectl get pods -n rollMedium Task 8: Pause
Why: Batch.
What to do: Pause, change image and env, resume.
- rollout pause.
Solution
kubectl rollout history deploy/shop -n rollHard Task 9: Stall
Why: Broken.
What to do: Run bad.sh and time it.
- deadline 60.
Solution
kubectl get deploy shop -n roll -o yamlHard Task 10: Swiggy
Why: Main task.
What to do: Design and prove the 6-replica rollout.
- maxUnavailable 0, maxSurge 2.
Solution
kubectl rollout status deploy/shop -n rollHomework: try at home
For Swiggy's menu frontend with 6 replicas, design a rollout that never drops below 6 ready pods, adds at most 2 at a time and waits 5 seconds per pod. Prove it with loop.sh and a ReplicaSet watch, then show a broken release stopping within 90 seconds.
Recap
- A rolling update replaces pods gradually through a new ReplicaSet.
- maxSurge and maxUnavailable control speed and capacity.
- Readiness and minReadySeconds decide when a new pod counts.
- A broken release stalls; it does not roll back by itself.
- Recreate causes downtime and is for versions that cannot coexist.
FAQ
Q. Does changing replicas trigger a rollout?
No. Only pod template changes create a new revision. Scaling just changes the count of the current ReplicaSet.
Q. Does a ConfigMap change trigger a rollout?
No, unless the ConfigMap is referenced by a hash annotation in the template. Many teams add a checksum annotation, or use kubectl rollout restart.
Q. What does rollout restart actually do?
It adds a kubectl.kubernetes.io/restartedAt annotation to the template, which is a template change, so a normal rolling update follows.
Q. Can I roll out StatefulSets and DaemonSets the same way?
Yes, both support RollingUpdate. StatefulSets update one pod at a time in reverse order, and support partition for staged updates. DaemonSets support maxUnavailable and maxSurge.
Q. Why did my rollout create pods on a node that was full?
Surge pods need resources like any pod. If no node has room they stay Pending and the rollout waits. Lower maxSurge or let the Cluster Autoscaler from 63.7 add a node.
Q. How do I see which pods belong to which version?
kubectl get pods -L pod-template-hash, or kubectl get rs -o wide which shows images per ReplicaSet.
Q. What is a good default for production?
maxUnavailable 0 with maxSurge 25% or 1, a real readiness probe, minReadySeconds of a few seconds, and a PDB from 63.4 for drains.
Q. Is a rolling update enough for database changes?
Only if old and new code both work with the schema during the overlap. Use expand and contract migrations: add columns first, deploy, then remove old ones later.
Interview questions
-
How does a Deployment rolling update work?
Model answer: A template change creates a new ReplicaSet; the controller scales it up and the old one down within maxSurge and maxUnavailable, waiting for readiness.
-
What do maxSurge and maxUnavailable mean?
Model answer: Extra pods allowed above replicas, and desired pods allowed to be missing, during the update.
-
What happens if new pods never become ready?
Model answer: The rollout stops, old pods keep serving, and after progressDeadlineSeconds the Deployment reports ProgressDeadlineExceeded.
-
RollingUpdate vs Recreate?
Model answer: RollingUpdate has no downtime but runs two versions together; Recreate has downtime but never mixes versions.
-
How do you make rollouts zero-downtime?
Model answer: Readiness probes, maxUnavailable 0, graceful shutdown with preStop, PDBs, and backward compatible changes.
Quick quiz
Q1. What is the default strategy?
RollingUpdate.
Q2. Defaults for surge and unavailable?
25% each.
Q3. What creates a new ReplicaSet?
Any pod template change.
Q4. Does Kubernetes roll back a stalled rollout itself?
No.
Q5. When is Recreate right?
When two versions must not run together.
Samajla ka?