0%
Containers and Kubernetes: From cgroups to Operators
What a Container Actually Is
Working with Docker
Kubernetes Architecture
Workloads
Scheduling and Resources
Networking and Storage
Admission Control and Cluster Policy
Every write to the cluster passes through a fixed pipeline before it is persisted, and admission control is the stage where an object can be changed or refused. Knowing the order of that pipeline is what lets you predict whether a rule will fire, what it can see, and why a rejection message says what it does.
The stages, in order
One sequence governs every write.
A request is authenticated, establishing who is asking. It is authorised, establishing whether they may perform this verb on this resource. It passes through mutating admission, where plugins and webhooks may modify the object. It is validated against the schema, which includes type checking and defaulting. It passes through validating admission, where plugins and webhooks may refuse it. Then it is persisted, and only then do controllers see it.
Two facts fall out immediately. Mutation happens before schema validation, so a mutating plugin can produce an invalid object and the rejection will name the schema rather than the plugin. And all admission happens before persistence, so an admission rule can prevent an object existing but cannot do anything about objects that already exist.
What admission cannot do
One limitation shapes every policy design.
Admission runs on writes. It never sees an existing object that nobody is writing, so a rule added today does not evaluate yesterday's objects, and a workload already running is unaffected until something touches its object.
So any policy rollout has two parts: admission to stop new violations and a separate audit of existing state to find the ones already there. Teams routinely do the first and announce the problem solved, and the characteristic discovery weeks later is a long tail of non-compliant workloads that nobody has updated since the policy landed.
Which object admission actually sees
One detail decides whether a rule works at all.
For a create, admission sees the object being created. For an update, it sees both the new object and the old one, which is what makes immutability rules possible. For a delete, it sees the object being deleted and no new object.
The important case is pods created by a controller. A deployment's pods are created by a controller, so admission sees the pod, not the deployment. A rule rejecting a pod produces a deployment whose replicas never appear, with the real reason buried in the replica set's events. Policies that validate the controller object as well as the pod give far better messages, and that is worth doing for anything a user will hit.
The built-in plugins
One set of plugins does more than most clusters realise.
A distribution ships plugins that run inside the interface server, compiled in and enabled by flag. They include the one that enforces resource quota, the one that applies limit ranges, the one that denies requests to a terminating namespace, the one that attaches default service account tokens, the one that enforces pod security standards, and the priority plugin.
These run with no network call and cannot fail independently of the interface server, which makes them structurally safer than webhooks. Where a built-in plugin covers a need, using it rather than a webhook removes a failure mode entirely, and the plugins that enforce quota and limit ranges in particular cover a surprising amount of what people write custom policy for.
Resource quota as policy
One built-in plugin is the most underused.
Quota limits the aggregate of a resource within a namespace: total processor and memory requests and limits, counts of objects by kind, total claimed storage, and counts per storage class. A request that would exceed the quota is rejected at admission with a message naming what was exceeded.
It is the right tool for a large class of problems people write webhooks for, because it is enforced in process, cannot fail independently, and has a comprehensible message. The one thing to know is that quota on processor or memory requires every pod in the namespace to declare them, since the plugin cannot account for an unbounded pod, so enabling quota makes requests effectively mandatory.
Limit ranges and defaulting
One plugin sets values rather than refusing them.
A limit range in a namespace can set default requests and limits for containers that declare none, enforce minimum and maximum values, and constrain the ratio between limit and request.
That combination is how a platform makes a namespace safe without every team learning resource management: defaults appear for anyone who omits them, and extremes are refused. It is also the cheapest answer to the problem that quota requires declared requests, since the default supplies them. The subtlety is that defaults are applied at pod creation, so changing a limit range does not affect existing pods.
Pod security admission
One built-in plugin replaced a whole generation of policy webhooks.
Three standards, privileged, baseline and restricted, are enforced per namespace through labels, each selecting a mode: enforce, which rejects; audit, which records in the audit log; and warn, which returns a warning to the client.
The design is deliberately simple and that is its strength: three levels, three modes, set by two labels, with no custom logic. It covers the great majority of what pod security policy webhooks used to do. Any cluster that does not set these labels on every namespace is leaving the most valuable piece of policy on the table, and the usual rollout is warn and audit at restricted, then enforce at baseline, then enforce at restricted where possible.
Where webhooks become necessary
One honest boundary.
Built-in plugins cover resource accounting, defaults, pod security and a handful of other things. They do not cover rules specific to your organisation: required labels, approved image registries, naming conventions, mandatory annotations for cost attribution, ingress hostname ownership.
Those need either a webhook or the expression-based policy facility, and the choice between them is largely a question of whether the rule can be expressed as an expression over the object. If it can, that is the better answer, because it runs in process and has no availability cost. Reaching for a webhook should follow a check that neither a built-in plugin nor an expression can do the job.
Dry run and what it tells you
One flag exercises the pipeline without persisting.
A request may be marked dry run, in which case it passes through the entire pipeline, including all admission, and is discarded instead of persisted. Webhooks are told it is a dry run and must not perform side effects.
That makes it the correct way to test whether an object would be accepted, and the correct way to see what mutation would do to it, since the returned object is the mutated one. It is also why webhooks must declare whether they have side effects, and why a webhook that writes to an external system during a dry run is broken in a way that only shows up when someone tests a manifest.
Reading a rejection message
One short skill saves a great deal of time.
A rejection names its source. A message from a built-in plugin names the plugin and what was exceeded. A message from the schema names the field and the constraint. A message from a webhook is whatever the webhook returned, prefixed by its configuration name, which is why naming webhook configurations after their purpose matters.
So the first question on an unexpected rejection is which of the three it is, and the prefix answers it. A message with no recognisable prefix and no field reference is almost always a webhook that returned a bare string, which is a quality problem in the policy rather than in the manifest.
What the pipeline implies for policy design
One principle follows from the ordering.
Prefer the earliest stage that can express the rule. Authorisation if it is really about who may do something. A built-in plugin if one covers it. The schema, including expression-based validation rules, for a custom resource's own invariants. Expression-based policy for cluster rules over built-in kinds. A webhook only when none of those suffices.
Each step down that list adds a failure mode, adds latency to every matching request, and adds something to operate. The ordering is not a style preference: it is the difference between policy that is invisible when healthy and policy that becomes the reason the cluster stops accepting writes.
A strong answer places admission precisely: after authentication and authorisation, mutation before schema validation, validation after it, and all of it before persistence. The consequence worth volunteering is that admission only sees writes, so a new policy never evaluates existing objects, and any rollout needs a separate audit of current state alongside it or the non-compliant workloads simply persist.