KubernetesIII · Kubernetes APIKubernetes API
Admission control — mutating, validating, and policy enforcement
What you'll learn
- Distinguish mutating admission from validating admission and identify built-in admission controllers
- Configure Pod Security Standards, LimitRanger, ResourceQuota, and other built-in admission for a namespace
- Design webhook admission with HA, failurePolicy, and observability
- Use ValidatingAdmissionPolicy (CEL-based) to enforce declarative policies
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Admission control is the layer of the API server pipeline that enforces policy on objects, not on identities. Where authorisation answers “is this identity allowed to do this?”, admission answers “is this object safe to admit?”. This lesson covers built-in admission controllers, custom webhooks, and the new ValidatingAdmissionPolicy system.
Where admission fits
flowchart LR
R[Request] --> AUTHN[Authentication]
AUTHN --> AUTHZ[Authorisation]
AUTHZ --> MA[Mutating Admission]
MA --> VA[Validating Admission]
VA --> SV[Schema validation]
SV --> P[Persistence]
Admission runs after authentication and authorisation but before persistence. This order means admission only processes requests that are already authenticated and authorised — admission’s job is to inspect and possibly modify the object, then either accept or reject.
Built-in admission controllers
The API server runs a set of built-in admission controllers as part of the chain. The defaults vary by version; production clusters explicitly enable the ones they want.
PodSecurity (Pod Security Standards)
Enforces privileged, baseline, or restricted Pod
Security Standards at the namespace level. Configured via
namespace labels:
kubectl label namespace team-a-prod \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
The three modes per namespace:
enforce— reject Pods that violate the levelwarn— allow but emit a warning eventaudit— allow but emit an audit log entry
Production: restricted is the strictest; baseline is the
common compromise. Run restricted for new namespaces; allow
older namespaces to migrate.
LimitRanger
Applies LimitRange defaults to Pods in the namespace.
Configured by:
apiVersion: v1
kind: LimitRange
metadata:
name: default
namespace: team-a-prod
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: 2
memory: 4Gi
min:
cpu: 50m
memory: 32Mi
LimitRanger also validates Pods against the LimitRange
(rejecting if max is exceeded).
ResourceQuota
Enforces ResourceQuota for the namespace:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-prod
namespace: team-a-prod
spec:
hard:
pods: "200"
requests.cpu: "64"
requests.memory: 128Gi
limits.cpu: "128"
limits.memory: 256Gi
persistentvolumeclaims: "50"
requests.storage: 1Ti
services: "100"
secrets: "100"
configmaps: "100"
count/deployments.apps: "50"
ResourceQuota rejects Pods that would push the namespace
over the quota. The count/... syntax counts objects of a
specific kind.
ServiceAccount admission
Injects the default ServiceAccount token if spec. serviceAccountName is not set; configures automountServiceAccountToken
defaults.
ImagePolicyWebhook (deprecated)
Older webhook for image allowlists. Superseded by custom webhooks (next section) and ValidatingAdmissionPolicy.
EventRateLimit (deprecated)
Rate-limits API requests to protect against runaway controllers
or attackers. Disabled by default in production (the API
server’s --max-requests-inflight is the modern mechanism).
DefaultStorageClass, DefaultTolerationSeconds, Priority
Defaults new PVCs to the default StorageClass; sets default
toleration seconds; applies Pod priority from
PriorityClass defaults.
Custom admission webhooks
For policies that built-in admission does not cover, the cluster operator configures admission webhooks. Two kinds:
- MutatingWebhookConfiguration — runs before schema validation; can modify the object
- ValidatingWebhookConfiguration — runs after schema validation; can only accept or reject
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-registry-allowlist
webhooks:
- name: registry.example.com
sideEffects: None
admissionReviewVersions: ["v1"]
clientConfig:
service:
name: image-policy
namespace: policy
path: /validate
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
resources: ["pods"]
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "policy"]
The webhook receives an AdmissionReview with the object,
decides allow/deny, and returns. Production webhooks must be:
- HA — at least 2 replicas; failure of one doesn’t stop admission
- Fast — under 1 second p99; admission is on the hot path
- Observable — metrics on admission latency, error rate, allow/deny counts
- Auditable — logs every decision with full request context
ValidatingAdmissionPolicy (CEL-based)
Since Kubernetes 1.30, ValidatingAdmissionPolicy provides a
declarative admission mechanism using CEL (Common Expression
Language):
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-image-digest
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "object.spec.containers.all(c, c.image.contains('@sha256:'))"
message: "All container images must be pinned by digest (@sha256:...)"
The expression is a CEL expression evaluated against the
object. If it returns false, the request is rejected with the
message.
This is a powerful pattern: declarative, no webhook to operate, no failure mode from webhook outage. Production in 2026 uses ValidatingAdmissionPolicy for many policy needs where it used to require a webhook.
# Example: deny privileged containers
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-privileged
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "object.spec.containers.all(c, !has(c.securityContext) || !c.securityContext.privileged)"
message: "Privileged containers are not allowed"
How to test admission
For webhooks, use kubectl apply --dry-run=server:
kubectl apply -f manifest.yaml --dry-run=server
The API server runs the full pipeline (authn, authz, admission) and returns the result without persisting. This is the standard way to test admission before committing changes.
For ValidatingAdmissionPolicy, the same --dry-run=server
applies; the CEL evaluator runs and reports violations.
How to debug admission failures
A 422 Unprocessable Entity response is admission rejection.
The response body names the webhook or built-in:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "admission webhook \"deny-privileged\" denied the request: Privileged containers are not allowed",
"code": 422
}
For built-in admission:
Error from server (Forbidden): error when creating "pod.yaml":
admission webhook "pod-security.kubernetes.io" denied the request:
pods "web-7c8" is forbidden: violates PodSecurity "restricted:latest"
...
Production debugging steps:
- Read the message — it names the policy that rejected.
- If a webhook, check its health (
kubectl get pods -n policy), its recent logs, and its response time. - If a built-in (PodSecurity, ResourceQuota, LimitRanger), inspect the namespace’s configuration.
- If
failurePolicy: Failand the webhook is timing out, either restore the service or temporarily switch tofailurePolicy: Ignoreto unblock the cluster.
Admission observability
Admission contributes to API server latency. The relevant metrics:
apiserver_admission_controller_admission_duration_seconds— admission duration by controller and operationapiserver_admission_webhook_admission_duration_seconds— webhook-specific durationapiserver_admission_webhook_rejection_count— count of rejections by webhook
A spike in webhook latency or rejection count is the early indicator of webhook trouble.
Cross-course references
- The Linux course part
XXIX-Linux-Hardeningcovers least-privilege principles that map onto admission policy. - The Observability course part
V-Observability-PromArchitecturecovers the metrics the API server exposes about admission. - The Linux course part
XXVIII-Linux-MACcovers mandatory access controls; admission policies are the cluster’s MAC. - The Docker course part
XXXVIII-Docker-Secretscovers secret management patterns that interact with admission (imagePullSecretsinjection).
Quiz
Knowledge check · 4 questions
Q1. Which built-in admission controller enforces Pod Security Standards at the namespace level?
Q2. A `failurePolicy: Ignore` webhook that becomes unreachable will cause Pods to be rejected with 422.
Q3. A developer reports they cannot create a new Deployment. The error is `admission webhook "resourcequota.admission.k8s.io" denied the request: exceeded quota: team-a-prod, requested: requests.cpu=16, used: 56, limited: 64`. Diagnose and remediate.
ResourceQuota (team-a-prod): ```yaml spec: hard: pods: "200" requests.cpu: "64" requests.memory: 128Gi limits.cpu: "128" limits.memory: 256Gi ``` Current usage: ``` $ kubectl describe resourcequota team-a-prod -n team-a-prod Name: team-a-prod Used Hard ------ ---- pods 200 200 requests.cpu 56 64 requests.memory 98Gi 128Gi limits.cpu 90 128 limits.memory 180Gi 256Gi ``` The Deployment: ```yaml spec: template: spec: containers: - name: web image: nginx:1.27.1 resources: requests: cpu: 16 memory: 4Gi ``` The Deployment has 1 replica.
Q4. Explain ValidatingAdmissionPolicy. When would you use it instead of a webhook?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Run PodSecurity at
restrictedfor new namespaces; allow older namespaces to migrate gradually. - Set
ResourceQuotaandLimitRangeon every namespace. Without them, any Pod can request unbounded resources. - For custom policies, prefer ValidatingAdmissionPolicy for declarative rules; use webhooks only for policies that need external data.
- Every webhook must be HA, fast, observable, and auditable. A flaky webhook blocks the cluster.
- Test every policy with
kubectl apply --dry-run=serverbefore committing to a namespace-wide rollout.