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

maxSurgemaxUnavailableBehaviourRisk
10One new pod at a time, never below desiredSlowest, safest
25%25%Default — up to 25% more and 25% fewer simultaneouslyBalanced
replicas0All new pods start before any old ones removedFastest, 2× resources temporarily
01Remove 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