Incident replay — the webhook that froze the cluster (Jetstack)
The real incident
Jetstack (the cert-manager company — people who know Kubernetes cold) took a
GKE cluster outage from a single admission webhook. A node auto-repair
recycled the nodes running the webhook's backend pods. The webhook was
configured failurePolicy: Fail with broad scope — so with its backend
briefly unreachable, the API server did what that policy commands: rejected
every CREATE/UPDATE it was asked to admit. Including the writes needed to
reschedule the webhook's own pods. The cluster had locked itself out of its own
recovery.
Source: How a simple admission webhook lead to a cluster outage — Jetstack
Admission webhooks sit in the synchronous write path of the API server:
every matching write pauses while the API server calls your webhook. That gives
them veto power over the entire control plane. failurePolicy decides what
happens when the call fails:
Fail— no answer = reject the write. "Secure", and a live grenade if the backend can ever be unavailable.Ignore— no answer = allow the write. Fails open; the policy isn't enforced during the outage, but the cluster keeps breathing.
This cluster, right now
policy-guard is a ValidatingWebhookConfiguration with failurePolicy: Fail,
resources: ["*"], pointed at a Service with no pods behind it. The
kubelings namespace is labeled into its scope. Result: every write to
kubelings is rejected — try one and read the error.
kubectl -n kubelings create configmap test --from-literal=a=b
# Error ... failed calling webhook "policy-guard...": ... no endpoints available
kubectl get validatingwebhookconfiguration policy-guard -o yaml | grep -A3 failurePolicy
┌───────────┐
│API server │
│ │
└───────────┘
│
must consult
│
▼
┌─────────────────────┐
│webhook, Fail policy │
│ │
└─────────────────────┘
│
backend unreachable
│
▼
┌────────────────┐
│writes rejected │
│ │
└────────────────┘
Your task
Restore writes to kubelings and leave the webhook config in a safe state
(the check rejects leaving it Fail with a dead backend):
- Flip
failurePolicytoIgnore(fail-open) so an unreachable webhook can't block the cluster, or delete/rescope the broken configuration. - Confirm you can create objects in
kubelingsagain.
kubectl get validatingwebhookconfiguration policy-guard -o yaml | grep -A6 -i 'failurePolicy\|namespaceSelector'
Hint
Fastest safe fix — fail open:
kubectl patch validatingwebhookconfiguration policy-guard --type=json -p '[
{"op":"replace","path":"/webhooks/0/failurePolicy","value":"Ignore"}
]'
Or, if the webhook is simply broken and unowned, remove it:
kubectl delete validatingwebhookconfiguration policy-guard
Solution
Root cause chain (Jetstack's, generalized)
- Webhook backend becomes unreachable (node recycle / crash / scale-to-zero).
failurePolicy: Fail→ API server rejects all matching writes.- Scope is broad (
*resources) and includes namespaces needed for recovery. - Recovery requires writes → writes are blocked → deadlock.
Every link is individually reasonable. The outage is their product — the same "multiplication, not one bug" shape as the Zalando DNS incident.
Building webhooks that can't do this
failurePolicy: Ignoreunless you can prove the backend is more available than the thing it guards. Most policy checks should fail open; a bypassed check during a rare outage beats a global write freeze.- Scope tightly: real
rules(specific apiGroups/resources), and anamespaceSelector/objectSelectorthat excludes kube-system and the webhook's own namespace — so it can never block its own recovery.["*"]on everything is the config equivalent ofchmod -R 777. timeoutSecondslow (1–5s): a slow webhook shouldn't add latency to every write in the cluster.- HA + PDB for the backend, and keep it off nodes it might itself gate.
Two-way-door habit
Before shipping any webhook, answer: "if this backend is down for 10 minutes,
what still works?" If the answer is "nothing", you've built the Jetstack
outage. Also know the manual escape hatch for 3 a.m.:
kubectl delete validatingwebhookconfiguration <name> (or
mutatingwebhookconfiguration) — cluster-scoped, no admission on the webhook
configs themselves, so it works even when everything else is frozen.
Why Security, not Internals
The mechanism is pure control-plane internals (you'll trace the admission chain in Module 7). But it's here because webhooks are how you enforce security policy — and the #1 way security tooling causes the outage it was meant to prevent. Powerful guard, footgun safety off by default.