Dev & EngARTICLE

Pod Security Admission Replaced PodSecurityPolicy in Kubernetes: What to Reconfigure Before the Next Upgrade

PodSecurityPolicy was removed from Kubernetes in an update to the project's most recent versions, and Pod Security Admission, stable since v1.25, is the native mechanism that took its place. Platform teams that postponed the migration risk silently breaking workloads on the next cluster upgrade.

Pod Security Admission Replaced PodSecurityPolicy in Kubernetes: What to Reconfigure Before the Next Upgrade
Image: Rafael Oliveira
Article · Dev & Eng
Reading
6 min
Tables
1
Code
1

PodSecurityPolicy was removed from Kubernetes in an update to the project's most recent versions, and Pod Security Admission, stable since v1.25, is the native mechanism that took its place. Platform teams that postponed the migration risk silently breaking workloads on the next cluster upgrade.

Editor's note: this article states that many production clusters in Brazil ran for years on older versions, kept alive by managed cloud providers, and are only now being forced to migrate due to end of support. This market-context claim could not be confirmed against any available source and should be read as hypothetical background, not verified data. Likewise, details about the inner workings of the old PodSecurityPolicy (binding to ClusterRole and RoleBinding, exceptions configurable at runtime) do not appear in the technical documentation consulted on Pod Security Admission and could not be verified against an official source. The technical content about Pod Security Admission itself, modes, labels, metrics, and version 1.25, was confirmed in the official Kubernetes documentation.

PodSecurityPolicy was removed from Kubernetes in an update to the project's most recent versions, and Pod Security Admission, stable since v1.25, is the native mechanism that took its place. Platform teams that postponed the migration risk silently breaking workloads on the next cluster upgrade.

PodSecurityPolicy was removed from Kubernetes in an update to the project's most recent versions, and Pod Security Admission is the only native mechanism that took its place. Platform teams that postponed the migration risk silently breaking workloads on the next cluster upgrade.

An Old Removal That Still Catches People Off Guard in Today's Upgrade

The PodSecurityPolicy (PSP) was deprecated and, in newer versions of Kubernetes, no longer exists in the control plane, as the project's own official documentation confirms when guiding those who are "running an older version of Kubernetes and want to upgrade to a version that no longer includes PodSecurityPolicies." In its place, the project's official documentation (kubernetes.io/docs/concepts/security/pod-security-admission) describes Pod Security Admission as a controller that has been stable since version 1.25.

This isn't new news, but it has become a current headache: many production clusters in Brazil ran for years on older versions, kept alive by managed cloud providers, and are only now being forced to upgrade because those versions went out of support. When the upgrade happens, PSP simply no longer exists in the control plane, and any manifest or automation that still depended on it fails without a clear warning.

The difference in model is what most catches platform teams off guard. PSP was a cluster-wide resource, bound to ClusterRole and RoleBinding, with fine granularity per spec field. Pod Security Admission works at a different level: it applies one of three standardized profiles (privileged, baseline, restricted) per namespace, via labels. There is no native custom policy; anyone who needs rules more specific than the three profiles defined by the Pod Security Standards needs a separate admission webhook, such as Kyverno or Gatekeeper.

How Pod Security Admission Decides What to Block

The controller reads labels applied directly on the namespace. Each operating mode has its own label, and a namespace can combine all three at once:

pod-security.kubernetes.io/<MODE>: <LEVEL>
pod-security.kubernetes.io/<MODE>-version: <VERSION>

Here MODE is enforce, audit, or warn, and LEVEL is one of the three profiles. The -version label makes it possible to pin the policy to the version that shipped with a specific Kubernetes minor release (for example v1.37) or to use latest, which matters a lot for anyone who wants to upgrade the cluster without the enforcement behavior changing on its own in the same upgrade.

ModeWhat happens when the pod violates the policy
enforceThe pod is rejected at creation
auditThe violation becomes an annotation in the audit log, but the pod still comes up
warnThe user gets a warning in the terminal, but the pod still comes up

In short: only enforce actually blocks anything. audit and warn exist to run in parallel before locking anything down in production, exactly the kind of gradual rollout that SRE teams tend to prefer over a single, one-shot policy change.

The Trap That Breaks Things Silently

Kubernetes documentation points to a detail that changes the practical outcome of the migration: audit and warn are applied both to the workload (Deployment, Job, StatefulSet) and to the resulting Pod, but enforce only applies to the generated Pod, never to the workload object itself. In practice, this means a kubectl apply of a Deployment can go through cleanly, with no error at all in the command's output, and only the ReplicaSet behind it fails when trying to create the actual Pods. Anyone who only looks at the apply's return doesn't see the problem; anyone who doesn't monitor ReplicaSet events only discovers the breakage when the expected pod never shows up running.

This reinforces a rule that already applies to any admission controller: it isn't enough to apply the label and move on. You need to run first in audit and warn, observe the volume of violations, and only then promote to enforce namespace by namespace. Without that intermediate step, the cluster upgrade turns into a test in production.

Editor's note: the code in this section was not run in a real environment. Validate before using it in production.

Example of a Namespace-by-Namespace Migration

A reasonable roadmap for anyone moving from PSP to Pod Security Admission follows this order:

  1. Survey all namespaces in the cluster and the workloads running in them, identifying which ones depended on permissive PSP bindings (privileged, hostPath, root).
  2. For each namespace, first apply pod-security.kubernetes.io/audit: restricted and pod-security.kubernetes.io/warn: restricted, leaving enforce still at baseline or privileged.
  3. Run the workloads normally for one deploy cycle and review the audit events and the warnings returned by kubectl apply.
  4. Fix manifests that generate violations (remove privileged: true, unnecessary hostPath mounts, extra capabilities) or flag the namespace as a deliberate exception.
  5. Raise enforce to the target level only after audit stops flagging new violations.

Exemptions: Stricter, and on Purpose

Pod Security Admission allows exemptions by authenticated username, by RuntimeClassName, or by entire namespace, but all of them need to be configured statically in the admission controller's configuration, not at runtime as was possible with PSP. The documentation calls out a common mistake: exempting a controller service account, such as system:serviceaccount:kube-system:replicaset-controller, in practice exempts any user who has permission to create the corresponding workload, because it's the controller that materializes the Pod. Security teams that replicate PSP exemptions without understanding this difference end up opening a bigger hole than they had before.

What to Watch After the Switch

kube-apiserver exposes three Prometheus metrics specifically for anyone who needs to audit admission behavior after the migration: pod_security_errors_total (evaluation failures, which can cause the controller to fall back to the most recent restricted profile for safety), pod_security_evaluations_total (the volume of evaluations performed), and pod_security_exemptions_total (how many requests went through via exemption). Wiring these metrics into a dashboard before promoting any namespace to enforce is how you confirm, with numbers rather than confidence, that the new policy won't take down a workload that matters.

When Migration Alone Isn't Enough

Pod Security Admission handles the common case well: restricting container privilege by namespace with three levels already defined by the community. It doesn't handle fine-grained business rules, like "only containers with this signed image may run" or "block if the registry isn't the internal one." For that kind of policy, the answer is still an external admission webhook, Kyverno or OPA Gatekeeper, running alongside native Pod Security Admission, not in its place. Teams that try to force the three standard profiles to cover a specific compliance rule tend to end up with namespaces full of exceptions, which cancels out the simplicity gain that motivated PSP's removal.

Translated from the Brazilian Portuguese original · Read the original