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

NamespaceLifecycle

Blocks creating objects in a namespace that is terminating or missing.

LimitRanger

Applies LimitRange defaults and bounds (Resource Management).

ResourceQuota

Rejects requests that would exceed a namespace quota.

ServiceAccount

Sets the default ServiceAccount and projected token volume.

DefaultStorageClass

Fills in the default StorageClass on PVCs (Persistent Volumes and Storage Classes).

PodSecurity

Enforces the Pod Security Standards (Pod Security).

MutatingAdmissionWebhook, ValidatingAdmissionWebhook, MutatingAdmissionPolicy, ValidatingAdmissionPolicy

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 (ClusterPolicy and newer policy kinds), supporting validate, mutate, generate and image-verification rules, and can also emit CEL-based policies.

  • OPA Gatekeeper evaluates Rego in ConstraintTemplate / Constraint resources 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.

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.