Rolling Updates That Don't Lie to You: Tuning maxSurge, maxUnavailable, and Rollback Depth in Kubernetes Deployments
Kubernetes rolling-update defaults encode assumptions about replica count and cluster headroom that may not match your workload. How to tune maxSurge, maxUnavailable, and revisionHistoryLimit deliberately.
19 Jun 2026, 00:15 UTC

A Deployment with default rolling-update settings will happily report "rollout complete" while your users just sat through a brown-out. The defaults — maxSurge: 25%, maxUnavailable: 25%, revisionHistoryLimit: 10 — are reasonable starting points, but they encode assumptions about replica count, cluster headroom, and how far back you might need to roll. If those assumptions don't match your workload, the rollout machinery works exactly as designed and still hurts you.
The thesis here is simple: treat the rolling-update fields as a capacity and recovery budget, not as boilerplate. A few minutes of arithmetic per Deployment beats debugging a stalled rollout at 2 a.m.
What the two percentage fields actually control
During a rolling update, the Deployment controller creates a new ReplicaSet and scales it up while scaling the old one down. Two fields bound that overlap:
maxSurge— how many Pods above your desired replica count may exist at once. Surge Pods consume real cluster resources: CPU and memory requests must be schedulable somewhere.maxUnavailable— how many Pods may be not ready at any moment. This is your availability floor during the update.
Both can be integers or percentages, and percentages round differently: maxUnavailable rounds down, maxSurge rounds up. That rounding detail matters at small replica counts. With 3 replicas and the default 25%, maxUnavailable computes to 0 (rounded down) and maxSurge to 1 (rounded up) — so Kubernetes will never take a Pod down until its replacement is ready. That's actually safe. But with 4 replicas, 25% unavailable rounds to exactly 1, meaning one quarter of your capacity can vanish mid-rollout. If your service already runs hot at 4 replicas, that's your brown-out.
A worked example: a latency-sensitive API with 6 replicas
Say you run an API Deployment with 6 replicas, and load testing showed you need at least 5 ready Pods to stay within latency SLOs during peak. You also know your nodes have limited spare capacity, so you can't afford more than 2 extra Pods' worth of requests.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
replicas: 6
revisionHistoryLimit: 5
progressDeadlineSeconds: 300
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 1
template:
spec:
containers:
- name: api
image: registry.example.com/payments-api:1.4.2
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
failureThreshold: 6Using integers instead of percentages removes the rounding guesswork: at most 8 Pods total, at least 5 ready at all times. The readiness probe does the real safety work — the controller only counts a new Pod toward availability once the probe passes, so a slow-starting JVM or a cache-warming init phase won't cause premature scale-down of the old ReplicaSet. Set failureThreshold and periodSeconds so their product comfortably exceeds your worst-case startup time; a probe that gives up too early is the most common cause of rollouts that stall and then hit progressDeadlineSeconds.
After applying, watch the update behave the way you intended:
kubectl rollout status deployment/payments-api
kubectl get pods -l app=payments-api -wRun these from any machine with cluster credentials; read-only RBAC on deployments and pods is enough for observation. You should never see fewer than 5 Pods in Ready state. If you do, your probe or your arithmetic is wrong — not Kubernetes.
Rollback depth is a deliberate choice too
revisionHistoryLimit defaults to 10, meaning Kubernetes keeps up to 10 old ReplicaSets (scaled to zero, but still in etcd) so kubectl rollout undo can target them. Two failure modes hide here:
- Too low. If you set it to 1 and deploy twice in quick succession — say, a bad release followed by a bad hotfix — the revision you actually want to return to may already be garbage-collected.
kubectl rollout undo deployment/payments-api --to-revision=Nwill fail for a revision that no longer exists. - Too high. Every retained ReplicaSet is an object in etcd, and on clusters with hundreds of frequently-updated Deployments, bloated history adds noise to
kubectl get rsand incremental etcd pressure. It's rarely a crisis, but there's no reason to keep 50 revisions of a chatty CI-deployed service.
A practical default: 5 for services deployed many times a day, 10–15 for services deployed weekly where "last known good" might be several releases back. And remember that rollout undo restores the Pod template, not your database schema or ConfigMap contents — rollback depth doesn't save you from irreversible data migrations.
Pausing: the underused escape hatch
kubectl rollout pause deployment/payments-api freezes a rollout mid-flight. The genuinely useful pattern is a manual canary: pause the Deployment, update the image, resume briefly so one surge Pod comes up, pause again, and watch your metrics before resuming fully. It's a poor man's progressive delivery with zero extra tooling. The catch: a paused Deployment stays paused across whatever else happens until someone resumes it, and nothing in the API will remind you. If you adopt this pattern, alert on Deployments stuck in a paused state, or you'll ship a half-rolled-out fleet and wonder why two versions are serving traffic on Monday morning.
The trade-off you can't tune away
Faster rollouts and safer rollouts are the same knob turned in opposite directions. High surge plus zero unavailability is fast and safe for users but demands real cluster headroom — on a tightly packed cluster, surge Pods sit Pending and the rollout stalls against progressDeadlineSeconds (default 600s), after which the Deployment marks the rollout as failed (though it keeps trying; it doesn't roll back for you). Low surge with higher unavailability works on packed clusters but dips capacity. There is no configuration that is fast, safe, and cheap; pick which two you're buying and write the choice down in the manifest.
Where to start
Audit one production Deployment this week. Run kubectl get deployment <name> -o yaml, check the strategy fields and revisionHistoryLimit, and compare maxUnavailable against the minimum ready capacity your load tests say you need. Then practice the failure path in staging: deploy a deliberately broken image, watch the rollout stall, and run kubectl rollout undo. A rollback you've never executed is a hope, not a plan.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.