Skip to content

CONCEPT Cited by 1 source

Validating admission webhook

Definition

A validating admission webhook is a Kubernetes extension point (admissionregistration.k8s.io/v1ValidatingWebhookConfiguration) that lets a cluster operator plug custom allow/deny logic into the API server's write path. For every matching CREATE / UPDATE / DELETE operation on configured resources, the API server sends an AdmissionReview JSON payload to the webhook over HTTPS; the webhook returns allow or deny (optionally with a message); only allowed objects are persisted to etcd. It runs after mutating webhooks and after built-in object validation, so by the time it sees an object, the object is already syntactically valid Kubernetes.

See the Kubernetes admission-controllers documentation for the full admission pipeline.

Where it sits in the API server

client
  ↓  kubectl apply
API server
  1. Authentication
  2. Authorization
  3. Mutating admission webhooks
  4. Built-in object validation (OpenAPI schema)
  5. Validating admission webhooks  ←  this concept
  6. Write to etcd

Because validating webhooks are the last gate before etcd, they are the last opportunity to reject an object without ever having it propagate to any controller, informer, or data-plane consumer.

What they are good at

  • Enforcing rules that built-in Kubernetes validation cannot express. Built-in validation checks the OpenAPI schema of the resource — "is spec.replicas an integer". It cannot check "does the predicate referenced inside this annotation exist in the Skipper runtime," because the annotation is just an opaque string to the API server.
  • Keeping enforcement at the write path. Once etcd has the object, every controller in the cluster that watches this resource will try to act on it. Catching a bad object at admission means one rejection message to one user, rather than N reconcile-loop error logs fanned out across N watchers.
  • Symmetric validation between admission and runtime. If the webhook calls the same validator the data-plane controller uses at reconcile time, the two answers cannot drift. This is the architectural move Zalando makes in the Skipper case — see patterns/reuse-runtime-logic-on-admission-path.

What they are bad at

  • Blast radius is on the write path, not on traffic. A bad validating webhook does not drop customer requests (failurePolicy: Fail denies new writes; existing objects keep working). But it blocks CI/CD, kubectl apply, and operator writes fleet-wide if the validator is wrong — and that has its own blast radius (concepts/control-plane-change-blast-radius).
  • failurePolicy is a real trade-off. Fail (default in practice for strict validators) means webhook-unavailability blocks writes — a webhook outage freezes the control plane. Ignore means webhook-unavailability permits writes — invalid objects leak through. Neither is free.
  • Latency added to every matching write. The webhook is in the synchronous write path; its P99 latency and timeout budget (default 10 s in Kubernetes) are now part of kubectl apply latency.
  • Cannot validate state across the cluster. A webhook sees one object at a time; cross-object invariants (quota aggregation, leader uniqueness) require separate machinery because the webhook has no guarantee that two concurrent requests serialize past it in any particular order.

Seen in

  • sources/2026-04-08-zalando-rejecting-invalid-ingress-routes-at-apply-time — canonical wiki instance. Zalando deploys a validating admission webhook (ingress-admitter.teapot.zalan.do) that calls Skipper's own runtime filter registry, predicate specs, route validator, and backend parsers on the AdmissionReview for Ingress and RouteGroup resources. An Ingress with a non-existent predicate like NonExistingPredicate() is rejected at kubectl apply with a Skipper-specific error — "invalid 'zalando.org/skipper-predicate' annotation: unknown_predicate: predicate 'NonExistingPredicate' not found" — instead of being accepted, written to etcd, propagated to 300 Skipper replicas per cluster, and only surfacing as a broken route when a real request hits it. Shipped behind an -enable-advanced-validation Skipper flag (concepts/feature-flag-rollback-for-validator) so a false-positive rejection is a flag-off away from reverting, rolled out tier-by-tier across 250+ clusters, guided by skipper_route_invalid{route_id, reason} (concepts/invalid-route-observability-metric). Zalando's canonical architectural move: treat the production validator as a library and call it from the admission path so the two answers cannot drift. Available in open-source Skipper v0.24.18+.
Last updated · 507 distilled / 1,218 read