Admission Control and Policy
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
After a request is authenticated and authorized (see Authentication, Authorization and RBAC), it still has to pass admission control: code inside or attached to the API server that may mutate the object (inject defaults, sidecars, labels) and validate it (reject non-compliant objects) before it is persisted to etcd. Admission applies to create, update and delete, but not to reads. This page is written against Kubernetes 1.37; see the section index for the version baseline.
Built-in admission controllers
The API server ships dozens of compiled-in controllers, enabled by default or by flag (--enable-admission-plugins).
Several you have already met under another name:
| Controller | Behaviour |
|---|---|
|
Blocks creating objects in a namespace that is terminating or missing. |
|
Applies |
|
Rejects requests that would exceed a namespace quota. |
|
Sets the default ServiceAccount and projected token volume. |
|
Fills in the default StorageClass on PVCs (Persistent Volumes and Storage Classes). |
|
Enforces the Pod Security Standards (Pod Security). |
|
The extension points for your own logic, described below. |
Mutating controllers run first, then object schema validation, then validating controllers, so validating policies see the final mutated object.
kubectl get validatingadmissionpolicies,validatingadmissionpolicybindings
kubectl get mutatingadmissionpolicies,mutatingadmissionpolicybindings
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
ValidatingAdmissionPolicy
ValidatingAdmissionPolicy (VAP, GA in Kubernetes 1.30) lets you write validation rules in CEL (Common Expression
Language) that run inside the API server — no webhook service to deploy, secure or keep available. A policy defines what
to match and the rules; a ValidatingAdmissionPolicyBinding decides where it applies (namespaces, label selectors)
and what action to take (Deny, Warn, Audit). The same policy can therefore be rolled out as warn-only first.
The Bookshelf rule: every container in a Deployment must declare a memory limit (a CPU limit is deliberately not required; see Resource Management).
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-resource-limits
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: >-
object.spec.template.spec.containers.all(c,
has(c.resources) && has(c.resources.limits) && 'memory' in c.resources.limits)
message: "every container must set a memory limit"
reason: Invalid
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-resource-limits-bookshelf
spec:
policyName: require-resource-limits
validationActions: ["Warn", "Audit"] # switch to ["Deny"] once workloads comply
matchResources:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: bookshelf
Policies can be parameterised with paramKind (a ConfigMap or custom resource holding thresholds), use variables and
matchConditions to keep expressions short, and report structured audit annotations. Use kubectl apply --dry-run=server
to see the verdict without persisting anything.
MutatingAdmissionPolicy
MutatingAdmissionPolicy (MAP) is the declarative counterpart for mutation: a CEL-based patch applied in-process. It
became stable in Kubernetes 1.36 (alpha 1.30, beta 1.34), and is enabled by default from 1.36 on the
admissionregistration.k8s.io/v1 API; verify the group version on older clusters. Patches use either an ApplyConfiguration (a
server-side-apply-style merge) or a JSONPatch.
Example: add default labels to every Deployment in the bookshelf namespace.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: default-labels
spec:
failurePolicy: Fail
reinvocationPolicy: IfNeeded
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["deployments"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
Object{
metadata: Object.metadata{
labels: {"app.kubernetes.io/part-of": "bookshelf"}
}
}
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
name: default-labels-bookshelf
spec:
policyName: default-labels
matchResources:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: bookshelf
Admission webhooks
When rules need external data or complex logic, register a webhook: the API server sends an AdmissionReview over HTTPS
to a service, which answers allowed/denied (validating) or returns a patch (mutating). Configure it with a
ValidatingWebhookConfiguration or MutatingWebhookConfiguration, including rules, clientConfig (service plus
caBundle), namespaceSelector, failurePolicy (Fail or Ignore), sideEffects and timeoutSeconds.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: bookshelf-policy
webhooks:
- name: policy.bookshelf.example.com
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Ignore
timeoutSeconds: 5
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "policy-system"]
rules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
clientConfig:
service:
namespace: policy-system
name: policy-webhook
path: /validate
caBundle: <base64-ca-bundle>
A slow or unavailable webhook can block every matching API request. The good-practices guide advises: prefer
built-in policy (VAP/MAP) where CEL suffices; keep webhooks idempotent, fast and side-effect free; use narrow rules and
a namespaceSelector that excludes the webhook’s own namespace and kube-system to avoid deadlock; set short timeouts;
choose failurePolicy deliberately; run multiple replicas with a PodDisruptionBudget
(see Scheduling and Disruptions); and monitor latency and
rejection metrics.
Policy engines: Kyverno and OPA Gatekeeper
Two popular projects package admission policy as a product:
-
Kyverno expresses policies as Kubernetes YAML (
ClusterPolicyand newer policy kinds), supporting validate, mutate, generate and image-verification rules, and can also emit CEL-based policies. -
OPA Gatekeeper evaluates Rego in
ConstraintTemplate/Constraintresources and adds an audit function.
Both typically run as webhooks, so the good practices above apply. With VAP and MAP now in the core API, use built-in policies for simple rules and a policy engine when you need reporting, generation, exception handling or a rich library. Both can enforce the Pod Security Standards as an alternative to PSA (see Pod Security).
Multi-tenancy
Kubernetes has no first-class "tenant"; you compose one from namespaces plus the controls in this section:
-
Namespaces as the unit of ownership, with RBAC bindings per team (Authentication, Authorization and RBAC).
-
ResourceQuota and LimitRange so one team cannot starve others (Resource Management).
-
NetworkPolicies defaulting to deny (Network Policies).
-
Pod Security and admission policy to keep tenants from weakening isolation.
-
Optionally sandboxed runtimes, dedicated nodes (taints) or entirely separate clusters.
Soft multi-tenancy assumes tenants are trusted-but-fallible teams in one organisation; namespaces and policy are adequate. Hard multi-tenancy assumes hostile tenants; a shared kernel and control plane are then considered insufficient, and stronger boundaries (virtual clusters, per-tenant clusters, sandboxed runtimes) are needed.
Source: Kubernetes Docs — Multi-tenancy.
API Priority and Fairness
Admission decides what is allowed; API Priority and Fairness (APF) decides how an overloaded API server queues requests.
Requests are classified by FlowSchema into priority levels (PriorityLevelConfiguration), each with its own concurrency
share and fair-queuing, so a runaway controller cannot starve kubelets or leader election. Rejected requests return
HTTP 429. Inspect it with kubectl get flowschemas and kubectl get prioritylevelconfigurations; tuning is rarely
needed on small clusters, but it matters for controllers that list large collections.