No Autoscaler Is Faster Than a Step Function
Two outage classes share one shape: a supporting tier that reacts only after the burst lands, and a certificate that renewed correctly while nothing reloaded it.
Resolver search-list expansion sets the packet count
A default pod ships options ndots:5 and three search domains. Resolving api.stripe.com gives two dots, below the threshold, so the resolver walks the search list first: three failed lookups, then the absolute name. Double that for A and AAAA records and one name resolution costs roughly eight packets. Application call rate stays flat while packets per resolution reach eight.
The second-order failure is the expensive one. When UDP DNS saturates, parallel A and AAAA queries leaving the same socket hit the classic conntrack race, and that arrives at application teams as unexplained five-second latency cliffs. The latency alert pages the application team. The cause sits in CoreDNS's UDP path, which that team does not own, which is how this class of incident survives days of debugging.
Then the controller problem. Plenty of clusters do run DNS autoscaling: the node-proportional autoscaler, keyed on node and core count. A burst driven by pod density on existing nodes moves that signal by exactly zero. A config review passes with autoscaling enabled while the cluster still holds the configuration that took Buildkite down.
| Control | Trigger signal | Reaction time | Fails when |
|---|---|---|---|
| Static replicas (default) | None | Never | Any burst above provisioned QPS |
| Node-proportional autoscaler | Node and core count | ~15s after node change | Burst comes from pod density |
| HPA on CoreDNS CPU | CoreDNS CPU | 30-90s (scrape, stabilize, start) | Disturbance arrives as a step function |
| Static headroom with pause pods | Pre-provisioned | Instant | Sustained growth past headroom |
| NodeLocal DNSCache (DaemonSet) | Reduces demand, not a loop | Instant at node level | Cold cache, node-local failure |
The answer is a stack, not a choice: NodeLocal DNSCache to collapse demand and kill the conntrack race, ndots tuning or trailing-dot FQDNs to stop the amplification at source, an HPA for sustained growth, and static headroom, because the burst is a step function and no control loop beats one.
The serving certificate needs its own check
Renewal and delivery are separate state machines. A cert-manager Secret rotating is not an Envoy reload, and it is not a sidecar that read the file at boot, a CDN origin config, a Java keystore, or an mTLS trust bundle picking it up. Monitoring a CA's records or Kubernetes Secrets validates renewal, which is the half that rarely fails.
The check has to be external, and it is cheap: a blackbox probe per public SNI per edge, asserting both notAfter and the expected leaf fingerprint, plus an alert when the serving certificate is older than the most recently issued one. That last condition catches a renewal that succeeded and that nobody picked up. Shrinking maximum certificate lifetimes and rising renewal cadence raise its value every cycle.
Both failures are the same control problem
Lorin Hochstein's separate analysis of the Buildkite report supplies the through-line: a regulator needs at least as much variety and speed as the disturbance it regulates. A node-count autoscaler lacks the variety to regulate pod-density demand. An on-call engineer lacks the speed to regulate an agent running a sub-second loop. The decision-model ecosystem puts numbers on that: a voice browser acting on partial transcripts at roughly 300ms, and a trading bot posting live limit orders every ~300ms block. Neither ships the described risk controls.
In both cases the fix has one shape: constrain the disturbance or constrain the actuator. A faster controller is not on the list. Pre-provisioned headroom and ndots tuning constrain the disturbance. Rate limits, blast-radius thresholds, and a kill switch that acts faster than the decision cycle constrain the actuator.
A node-proportional autoscaler keyed on node and core count moves by exactly zero when the burst comes from pod density on nodes that already exist.
What to do
Run kubectl get hpa -n kube-system and kubectl get deploy coredns -n kube-system -o yaml today; if no HPA exists or scaling keys on node count, ship NodeLocal DNSCache as a DaemonSet and set ndots to 1-2 for external calls this week.
Burst your largest workload tier 10x in staging this sprint and record coredns_dns_request_duration_seconds, SERVFAIL rate, cache hit ratio, and node-level conntrack drops.
Add a blackbox TLS probe per public SNI per edge PoP this week that asserts notAfter plus the expected leaf fingerprint, and alert when the serving cert is older than the latest issued cert.