Rolling Update Flow
Overview
Traces what happens internally when a Deployment's image tag is updated — from the PATCH request to the old ReplicaSet scaling down and the new one scaling up, with all PodDisruptionBudget and readiness checks honoured.
Rolling Update Sequence
kubectl/Argo CD API Server Deployment Ctrl ReplicaSet Ctrl kubelet
│ │ │ │ │
│ PATCH deployment│ │ │ │
│ image:sha-new ─►│ │ │ │
│ │──WRITE──────────►│(etcd) │ │
│◄── 200 OK ──────│ │ │ │
│ │ │ │ │
│ │──WATCH event ───►│ │ │
│ │ (Deployment │ │ │
│ │ Modified) │ │ │
│ │ │ │ │
│ │ ┌──── Deployment controller reconcile ────┐ │
│ │ │ Desired: 3 pods with sha-new │ │
│ │ │ Current: RS-old (3 pods, sha-old) │ │
│ │ │ │ │
│ │ │ Create RS-new (sha-new, replicas=0) │ │
│ │◄─CREATE RS│ Annotate RS-old for scale-down │ │
│ │──WRITE ──►│ (etcd) │ │
│ │ └─────────────────────────────────────────┘ │
│ │ │ │
│ │ ┌──── Rolling step 1: scale up RS-new by maxSurge ──┐│
│ │ │ maxSurge=1 → RS-new replicas: 0→1 ││
│ │◄─┤PATCH RS-new │ ││
│ │ └──────────────────────────────────────────────────┘│
│ │ │ │ │
│ │──WATCH event ──────────────────────►│ │
│ │ (RS-new pod needed) │ │
│ │ │ Creates pod │
│ │ │ with sha-new │
│ │ pod scheduled ──────►
│ │ │
│ │ [new pod starts, runs readinessProbe]
│ │ [readinessProbe passes] │
│ │◄──────────────────────── PATCH pod status Ready ──────│
│ │ │
│ ┌──── Rolling step 2: scale down RS-old by maxUnavailable ──────────┐ │
│ │ maxUnavailable=1 → RS-old replicas: 3→2 │ │
│ │ (only if total Ready pods ≥ desired - maxUnavailable) │ │
│ │ Check PodDisruptionBudget: minAvailable not violated │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ... repeat until RS-new = desired, RS-old = 0 ... │
│ │ │
│ Deployment controller marks rollout complete: │
│ status.conditions: [{type:Available, status:True}, │
│ {type:Progressing, status:True, │
│ reason:NewReplicaSetAvailable}] │
Key Parameters
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # max pods ABOVE desired during rollout (absolute or %)
maxUnavailable: 1 # max pods BELOW desired during rollout (absolute or %)
minReadySeconds: 30 # pod must be Ready for this long before counted
progressDeadlineSeconds: 600 # fail rollout if not complete in 10 min
Effect of Parameters on Rollout Speed
| maxSurge | maxUnavailable | Behaviour | Risk |
|---|---|---|---|
| 1 | 0 | One new pod at a time, never below desired | Slowest, safest |
| 25% | 25% | Default — up to 25% more and 25% fewer simultaneously | Balanced |
| replicas | 0 | All new pods start before any old ones removed | Fastest, 2× resources temporarily |
| 0 | 1 | Remove one old, add one new (resource-neutral) | No surge capacity |
ReplicaSet Lifecycle
Before rollout:
RS-v1 (sha-old): replicas=3, desired=3
RS-v2 (sha-new): does not exist
Step 1: Create RS-v2
RS-v1: replicas=3, desired=3
RS-v2: replicas=0, desired=0
Step 2: maxSurge=1 → scale up RS-v2
RS-v1: replicas=3
RS-v2: replicas=1 ← new pod starting
Step 3: new pod becomes Ready → scale down RS-v1
RS-v1: replicas=2
RS-v2: replicas=1
... repeat ...
Final state:
RS-v1: replicas=0 ← kept for rollback history
RS-v2: replicas=3
The old ReplicaSet is NOT deleted after rollout. It is kept for rollback (controlled by revisionHistoryLimit, default 10).
Rollout Commands
# Watch rollout progress
kubectl rollout status deployment/payments-api -n production
# Pause a rollout (stop after current step — useful for canary-lite)
kubectl rollout pause deployment/payments-api -n production
# Resume
kubectl rollout resume deployment/payments-api -n production
# Rollback (switches RS-v2 back to RS-v1 — instant)
kubectl rollout undo deployment/payments-api -n production
# Rollback to specific revision
kubectl rollout undo deployment/payments-api --to-revision=3 -n production
# View rollout history
kubectl rollout history deployment/payments-api -n production
kubectl rollout history deployment/payments-api --revision=3 -n production
# Force restart (useful when image tag unchanged, e.g., mutable :latest tag)
kubectl rollout restart deployment/payments-api -n production
Graceful Termination During Rollout
When RS-old scales down, pods receive SIGTERM. The correct shutdown sequence:
1. Pod receives SIGTERM
2. Pod removed from Service endpoints (readinessProbe fails or preStop runs)
3. preStop hook executes (if configured)
4. Application handles SIGTERM: finishes in-flight requests, closes connections
5. After terminationGracePeriodSeconds (default 30s): SIGKILL sent if still running
Best practice:
preStop: exec: ["/bin/sleep", "5"] ← wait for endpoint propagation
terminationGracePeriodSeconds: 60 ← enough for in-flight requests to complete
spec:
containers:
- name: payments-api
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # allow iptables rules to propagate
terminationGracePeriodSeconds: 60
PodDisruptionBudget Interaction
PDB: minAvailable: 2 (out of 3 pods)
→ At most 1 pod can be unavailable at any time
Rolling step: maxUnavailable=1
→ Deployment wants to remove 1 pod
API server checks PDB before allowing eviction:
- Currently available: 3 → removing 1 leaves 2 → satisfies minAvailable:2 ✓
- If one pod is already crashing → available: 2 → removing 1 leaves 1 → DENIED ✗
→ rollout pauses until the crashing pod recovers
Related
- 06 — HPA Flow — HPA adjusts replicas during/after rollout
- 05 — Progressive Delivery — Argo Rollouts for canary
- 05 — Pods — Deployment spec reference