Kubernetes / workloads / rollout
maxUnavailable Counts Available Pods, Not Working Ones
That `maxUnavailable: 25%` guarantees at least 75% of your capacity is serving traffic throughout a rollout. It guarantees nothing about traffic. The Deployment controller's scale-down arithmetic is `minAvailable := *(deployment.Spec.Replicas) - maxUnavailable` compared against `GetAvailableReplicaCountForReplicaSets(allRSs)`, and a Pod counts as available when it has been Ready for minReadySeconds, which defaults to 0. The Kubernetes probe documentation states that "If a container does not provide a particular probe, the kubelet always considers the result as Success" — so a Pod with no readinessProbe is Ready as soon as its containers are running, and the controller will delete old Pods to make room for new ones that are not yet serving anything. Two smaller errors ride along: the fenceposts round in opposite directions (`ResolveFenceposts` calls `GetScaledValueFromIntOrPercent(maxSurge, desired, true)` and the same for maxUnavailable with `false`), so 25% of 3 replicas is a maxSurge of 1 and a maxUnavailable of 0; and if both round to zero the controller silently forces maxUnavailable to 1.
A Deployment owns ReplicaSets and never touches a Pod; a rolling update is a loop that alternately scales the new ReplicaSet up to replicas + maxSurge and scales the old ones down to replicas - maxUnavailable, where both fenceposts are computed from .spec.replicas with maxSurge rounded up and maxUnavailable rounded down, and where the availability the loop counts is .status.availableReplicas — Ready plus minReadySeconds — which for a Pod with no readinessProbe is true the moment its containers are running.
Where this is already explained
- The HPA Does Nothing Until You Are 10% Past Target
That a HorizontalPodAutoscaler with `averageUtilization: 80` keeps the workload near 80%, so a workload sitting above 80% and not scaling means the metrics pipeline is broken. The controller applies a dead band: `tolerances.isWithin(usageRatio)` returns the current replica count unchanged whenever the ratio of current to target is inside 1.0 +/- 0.1, so at a target of 80 nothing happens until the average crosses 88%, and nothing scales down until it falls under 72%. Worse, the second half of the same function re-adds every pod that has no metric yet at a usage of 0% whenever the answer would have been a scale-up, which routinely pushes the recomputed ratio back inside the dead band — so an HPA that has just added pods will refuse to add more until those pods start reporting, no matter how hot the running ones are.
- A High PriorityClass Does Not Get Your Pod Scheduled
That a high PriorityClass is a guarantee — the scheduler will evict whatever it must to run your pod. It is not, for two separate reasons that produce the same Pending pod. First, preemption is per-node and all-or-nothing: SelectVictimsOnNode removes every eligible victim from one node and re-runs the filters, and if the pod still does not fit, that node is dropped. Free capacity spread across several nodes is never combined, so a 6-core pod on 4-core nodes stays Pending with 'Preemption is not helpful for scheduling' while the cluster holds 20 cores of batch work. Second, preemption frees space, it does not reserve it: the successful result is a nominatedNodeName written to the pod's status, which is a hint for the next scheduling cycle, not a lock, and the victims still take their full terminationGracePeriodSeconds to leave. The related belief that a PodDisruptionBudget protects a pod from preemption is also false — the scheduler prefers victims that do not violate a PDB and prefers nodes with fewer violations, but it will violate one rather than give up, which the Kubernetes documentation states as 'PodDisruptionBudget is supported, but not guaranteed'.
- How kube-scheduler Places a Pod
That a pod goes Pending because the cluster is out of capacity, so the fix is a bigger or extra node. The scheduler never reads utilisation: it compares the pod's requests against allocatable minus the sum of every already-placed pod's requests, so a node running at 12% CPU can be 100% requested and refuse everything. Worse, because placement is greedy and per-pod with no backtracking, the default LeastAllocated scoring spreads small pods evenly and leaves every node with a hole too small for the next big one — the cluster has the CPU free, just never in one place.
3 published lessons depend on this concept, which is what moves it up the writing queue. Nothing is hidden behind this page — it has not been written.