Admission Basics
This page walks through the concrete objects and commands for admission control: the built-in gate, the two webhook configurations, and the in-process CEL policy. Each example is a small, runnable starting point you can adapt.
Prerequisites
- Kubernetes 1.36.2 cluster with cluster-admin access (kind, minikube, or a managed cluster).
kubectlmatching your cluster minor.- A TLS certificate and Service for any external webhook (cert-manager makes this easy).
- Basic familiarity with
kubectl applyand namespaces.
Quick check that admission plug-ins are active:
kubectl api-resources | grep -i admission
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurationsBasic Examples
1. See a built-in admission controller reject a Pod
Pod Security Admission is a built-in validating controller. Label a namespace restricted and it rejects privileged Pods.
kubectl create namespace demo
kubectl label namespace demo \
pod-security.kubernetes.io/enforce=restricted- The label activates the built-in
PodSecurityadmission controller for that namespace. - No webhook or external service is involved; this ships with the API server.
enforceblocks;warnandauditonly report.- Try creating a root Pod next and watch it fail.
2. Trigger a rejection
Apply a Pod that violates the restricted profile and read the error.
kubectl -n demo run bad --image=nginx --restart=Never- The API server returns a
forbiddenerror describing the violated fields. - The object is never written to etcd, so
kubectl get pod badshows nothing. - The rejection happens in the validating admission phase.
- This proves admission acts on object content, not just identity.
3. Inspect a ValidatingWebhookConfiguration
Webhook configurations are cluster-scoped objects that route writes to an endpoint.
kubectl get validatingwebhookconfigurations -o yaml | head -40webhooks[].ruleslists theapiGroups,resources, andoperationsintercepted.clientConfigpoints to a Service or URL plus the CA bundle.failurePolicydecides behavior when the endpoint is unreachable.namespaceSelectorandobjectSelectornarrow the match.
4. Read the failurePolicy and timeout
These two fields control availability risk. Know them before you deploy any webhook.
webhooks:
- name: validate.example.com
failurePolicy: Fail # Fail = reject on error; Ignore = allow
timeoutSeconds: 5 # max 30
sideEffects: None
admissionReviewVersions: ["v1"]failurePolicy: Failis stricter but can wedge writes if the webhook is down.timeoutSecondscaps how long the API server waits per call.sideEffects: Nonetells the API server the webhook does not mutate external state.admissionReviewVersionsmust includev1.
5. Scope a webhook so it does not match system namespaces
Excluding control-plane namespaces is the single most important safety step.
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "kube-node-lease"]- Every namespace carries the immutable
kubernetes.io/metadata.namelabel. - Excluding
kube-systemkeeps core controllers writable during a policy outage. - Combine with
objectSelectorto skip specific labeled objects. - Test the selector logic before rollout with a dry-run apply.
6. Write a ValidatingAdmissionPolicy with CEL
The in-process path needs no external service. Define a policy and bind it to namespaces.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-limits
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: >-
object.spec.template.spec.containers.all(c,
has(c.resources.limits))
message: "every container must set resources.limits"matchConstraintsselects which writes the policy evaluates.validations[].expressionis CEL that must return true to allow.- The API server runs this itself, so there is no webhook latency or downtime.
- A policy does nothing until a binding attaches it (next example).
7. Bind the policy to a namespace
A ValidatingAdmissionPolicyBinding connects the policy to a scope and enforcement action.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-limits-binding
spec:
policyName: require-limits
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
team: paymentsvalidationActionscan beDeny,Warn, orAudit.matchResourcesnarrows the binding to labeled namespaces.- Start with
WarnorAuditto measure impact before switching toDeny. - Multiple bindings can reuse one policy with different scopes.
Intermediate Examples
8. Mutate defaults with a mutating webhook
Mutating webhooks patch objects. This snippet shows the configuration side; the endpoint returns a JSONPatch.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: add-default-labels
webhooks:
- name: mutate.example.com
reinvocationPolicy: IfNeeded
failurePolicy: Ignore
sideEffects: None
admissionReviewVersions: ["v1"]
clientConfig:
service:
name: policy-webhook
namespace: policy-system
path: /mutate
caBundle: <base64-ca>
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]- Mutating webhooks run before validating ones.
reinvocationPolicy: IfNeededre-runs the webhook if another mutation changes the object.- The
caBundlelets the API server trust the endpoint's TLS cert. - Keep mutations small and idempotent to avoid conflicts.
9. Deploy a policy engine and confirm it registered
Kyverno and Gatekeeper install as webhooks. After a Helm install, verify the configurations exist.
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
kubectl get validatingwebhookconfigurations | grep kyverno
kubectl get pods -n kyverno- The engine creates its own webhook configurations at startup.
- If the engine Pods are not Ready, writes may be delayed or blocked depending on
failurePolicy. - Excluding the engine's own namespace from its webhooks prevents deadlock.
- From here you author policies as engine-specific custom resources.
10. Dry-run a manifest against the cluster
Server-side dry-run runs the full admission chain without persisting the object.
kubectl apply -f deployment.yaml --dry-run=server--dry-run=serverexecutes mutating and validating admission, then discards the result.- It is the fastest way to see whether a real cluster's policies would accept a manifest.
- Use it in code review and pre-merge checks.
- Unlike
--dry-run=client, it reflects the actual policies installed.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).