The router finds out last: taking a server out of service without dropping requests
How production systems deliberately remove a serving instance (deploys, scale-downs, drains, proxy reloads) without dropping requests, and why the coordination gap between the kill path and the routing plane keeps producing 502s.
Reconstructs the five-step removal sequence that Google, Kubernetes, AWS, Envoy and gRPC all converge on, and shows that implementations differ on exactly one axis: whether the drain closes the loop by subscription, half-closes it by polling, or runs open-loop on a timer. A reader leaves able to classify their own drain design, size its wait from measured propagation instead of folklore, and test the crash path that one in five removals takes anyway.
After a decade of deploy-time 502s, upstream Kubernetes codified the sleep itself as the fix (KEP-3960, GA v1.34) while the proposal to sequence SIGTERM behind load-balancer deregistration (#106476) stayed accepted-but-frozen; and Borg reports the graceful notice is only delivered about 80% of the time, so even at Google the drain is an optimisation layered on crash-safety.
What you get out of it
- Removal is a three-party act with no shared transaction: the kill signal arrives in microseconds while the membership update replicates in seconds to minutes, and no acknowledgement flows back; every deploy-time 502 in the corpus is that gap observed from a different angle.
- Published drain designs differ on one axis, how the waiter learns it is safe: Google closes the loop by broadcast (1-2 RTT), health-check systems poll (probe interval times threshold, computable in advance per KEP-1669), and the industry default is an open-loop timer (preStop sleep, 300 s ALB deregistration delay, Envoy's 600 s drain).
- No fetched incident blames the drain sequence executing as designed; the outages come from the membership state being wrong (Slack 2020, GitLab 2022) or from tooling bypassing the drain contract entirely (an untested ASG cycler), so the risk lives in the state and the callers, not the mechanism.
- Persistent connections change the problem from a timer into a protocol feature: servers must rotate connections (GOAWAY plus bounded connection age, gRFC A9) because a connection that outlives the drain window pins its client to the dying server, as Slack's hours-long HAProxy zombie processes showed.
- Crash-safety is the floor, not an alternative: Borg delivers the SIGTERM notice about 80% of the time, so roughly one termination in five is ungraceful even at Google, and a fleet should be regression-tested at that rate.
Scope
Why this, now. KEP-3960 went stable in v1.34 (2025), making the community's decade-old preStop sleep workaround a first-class Kubernetes API field, which is the clearest statement yet that the platform's answer to the termination race is a timer, not a barrier.
What it does not cover. Failure detection (deciding an unresponsive peer is dead), stateful failover and session migration, batch-worker draining, and conference talks, which were unreachable from this run's network and are absent from the evidence wall for that stated reason.
Other field guides
A backup is a claim, a restore is the proof
GitLab lost six hours of production data in 2017 because five backup mechanisms produced zero working restores; the same failure class returned in Ju…
25 sources · 8 organisations · 6 postmortemsWhen the queue becomes the outage
The buffer that absorbs your bursts is also the mechanism that sustains your worst outages. This guide reconstructs, from postmortems at GitHub, Hone…
27 sources · 24 organisations · 3 postmortemsDeciding a server is dead
Reconstructs the failure-detection plane behind health checks from six published incidents, five production codebases, four papers and the Kubernetes…
30 sources · 24 organisations · 6 postmortems