Two windows, maxSurge and maxUnavailable, bound how many extra or missing pods a rollout may have, so a release can proceed under capacity control.

Replace a few at a time

A rolling update does not swap the workload in one cut; it brings up new pods and removes old ones gradually, respecting readiness the whole way. The controller waits for a new pod to become ready before it considers the update moving, so a pod that never becomes ready stalls the rollout rather than silently serving traffic. That is the safety property: at no point does the fleet jump from the old version to the new one with nothing in between. Because readiness gates the transition, you can catch a broken image at the first new pod and stop the rollout with far less blast radius than a full swap.

Surge and drain windows

Two settings bound the shape of the rollout. maxSurge is how many extra pods above the desired count you allow while old ones are still draining; maxUnavailable is how far below the desired count you may fall during the swap. Together they trade speed against headroom: a large surge runs fast but needs spare capacity to land the extra pods, while maxUnavailable: 1 with maxSurge: 0 prefers a steady capacity but proceeds more slowly and can present a maintenance window. Choose them to describe the risk you accept on a release, not a value that looks balanced, because they literally set the ceiling and floor of what the rollout may touch.

Roll back is a redeploy

Because a rollout is just another desired state, rolling back is not a special operation: you apply the image the cluster was running before, and the same bounded, readiness-gated churn happens in reverse. Keep the prior revision available and restartable, and prefer an old known-good image over an ad-hoc patch, so the rollback path is itself a tested path. The audit trail is the same, the pod churn is bounded by the same two windows, and the team knows exactly what ‘rollback’ means, it is a redeploy of the last-known-good spec, nothing more.

Recreate stops everything first

strategy.type has only two values, and the default hides the choice. RollingUpdate overlaps old and new pods inside the surge and unavailable windows; Recreate deletes every old pod first and only then creates the new ones, so minimum availability is zero by construction and the two windows are ignored. Pick Recreate when two versions must never coexist: a single-writer migration the old code cannot read back, an exclusive file lock or singleton worker, or a protocol where old and new disagree. The companion post tunes pace; this one picks shape. Default to RollingUpdate for stateless tiers, and when you choose Recreate, schedule the outage instead of discovering it: measure a cold start, pre-pull the image, and announce the window.

When it breaks

A stateful workload on RollingUpdate is the classic split-brain. Old and new pods overlap for one wave, so two leaders briefly coexist, each writing in its own schema, and the merge afterward is manual. Where versions cannot coexist, overlap is the bug: choose Recreate or add coordination.

The mirror failure is a Recreate whose stop outlasts its window. The team budgets thirty seconds, but the new image pulls slowly and readiness takes minutes, and with minimum availability at zero every extra minute is a full outage. Time a cold start before choosing it.

Third, a RollingUpdate configured into a full stop: four replicas with maxUnavailable: 50% shed two pods at once, one replacement CrashLoops, and the lone survivor melts under full load. Keep maxUnavailable at zero or one on small fleets.

Check it quickly

Save the following as strategy_window.py and run python3 strategy_window.py. It uses only the standard library, deriving each strategy’s floor from replica and window arithmetic.

"""Compare the availability floor of Recreate vs RollingUpdate (stdlib only)."""

def rolling_window(replicas, max_surge, max_unavailable):
    if replicas <= 0:
        raise ValueError("replicas must be positive")
    if max_unavailable >= replicas:
        raise ValueError("maxUnavailable covers the whole fleet: configured full stop")
    floor = replicas - max_unavailable
    ceiling = replicas + max_surge
    return floor, ceiling


def recreate_window():
    return 0, 0


def main():
    replicas, surge, unavailable = 6, 1, 1
    rolling_floor, rolling_ceiling = rolling_window(replicas, surge, unavailable)
    stopped, _ = recreate_window()
    assert rolling_floor == 5, rolling_floor
    assert rolling_ceiling == 7, rolling_ceiling
    assert rolling_floor > 0, "rolling update must keep serving"
    assert stopped == 0, "recreate stops everything first"
    try:
        rolling_window(4, 1, 4)
    except ValueError as error:
        print("caught expected misconfiguration:", error)
    else:
        raise AssertionError("maxUnavailable >= replicas should fail")
    print(f"replicas={replicas} surge={surge} unavailable={unavailable}")
    print(f"rolling: min available={rolling_floor}, max total={rolling_ceiling}")
    print("recreate: min available=0 (full stop, then rebuild)")
    print("PASS: rolling keeps serving; recreate stops everything first")


if __name__ == "__main__":
    main()

Six replicas, a surge of one, and one unavailable keep five serving with at most seven total; Recreate floors at zero. The assertions encode that contract, and a maxUnavailable covering all four replicas must raise instead of returning a floor.

The assertions passed with Python 3.14.4 on macOS. The model checks window arithmetic, not pulls, probes, or scheduling. Before release day, confirm the live strategy.type and time one cold start.

Further reading

Sources