Right, so you’ve got your shiny new Pod Security Standards. You know what Restricted means, you’re nodding along with Baseline. But knowing the rules is useless if your cluster isn’t actually enforcing them. That’s where Pod Security Admission (PSA) comes in. Think of PSA as the no-nonsense bouncer for your namespaces, checking every Pod’s ID against the guest list you provide. It’s built directly into the Kubernetes API server, so it’s not some external thing you have to manage; it’s just there, waiting for you to tell it what to do.

The entire system revolves around one brilliantly simple, yet powerful, idea: you label your namespaces. That label tells the PSA controller exactly what policy to apply to every Pod that gets created or updated in that namespace. No complex webhooks to deploy, no new services to monitor. It’s Kubernetes-native configuration at its finest.

The Three Modes: A Spectrum of Strictness

PSA doesn’t just have an “on” and “off” switch. It gives you three modes of operation, which is where most of the initial confusion (and power) comes from. You’ll use these as values for the enforce label.

  • enforce: The Iron Fist. This mode will straight-up reject any Pod that doesn’t meet the standard. No warnings, no apologies. It’s the production-grade, “thou shalt not pass” setting. Use this when you’re dead serious about a namespace’s security posture.
  • audit: The Tattletale. This mode is brilliant for your pre-production or staging namespaces. Pods that violate the policy are allowed to run, but a warning event is recorded in the audit logs. This is how you find all the sketchy things your developers are trying to do without actually breaking their builds. It’s your canary in the coal mine.
  • warn: The Nagging Parent. Similar to audit, but instead of just logging a warning, it immediately returns a terrifying (but non-blocking) warning message to the user running kubectl apply. It’s great for giving developers immediate feedback, like “Hey, I see you trying to run as root. You shouldn’t. I’ll let it slide this time, but don’t make a habit of it.”

You’ll often use warn or audit in lower environments and enforce in production. The beauty is you can set different modes for different standards on the same namespace. Let’s make this concrete.

Labeling a Namespace: A Worked Example

Let’s say you have a namespace for a legacy application that you can’t quite refactor yet. You want to warn everyone that it’s horrible and should be fixed, but you can’t break it. You’d use warn mode with the restricted policy.

apiVersion: v1
kind: Namespace
metadata:
  name: the-bad-place
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # But wait, we don't want to actually break things yet...

Wait, that would enforce it! Let’s fix that. We want to warn instead.

apiVersion: v1
kind: Namespace
metadata:
  name: the-bad-place
  labels:
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest
    # Optional: Audit as well to get it in the logs
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest

Now, apply it and try to run something naughty:

kubectl create ns the-bad-place
kubectl apply -f -n the-bad-place -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: naughty-pod
spec:
  containers:
  - name: root-runner
    image: nginx
    securityContext:
      runAsUser: 0 # Oh dear, running as root!
EOF

What happens? The pod creates successfully. But if you look closely at your kubectl apply output, you’ll see a glaring warning message telling you exactly which policy you violated and why. It’s instant, actionable feedback.

For your shiny new, greenfield microservice namespace, you’d bring down the hammer:

apiVersion: v1
kind: Namespace
metadata:
  name: security-showcase
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest

Now, that same naughty Pod definition will be unceremoniously rejected by the API server. It never even gets scheduled.

The Version Label: A Critical Detail

You absolutely must not ignore the -version label. The Pod Security Standards evolve. What restricted means in Kubernetes 1.25 is different from what it means in 1.28. The PSA designers, in a moment of sheer brilliance, decided to pin the policy definition to a specific Kubernetes version to avoid this ambiguity.

You have two main choices:

  • latest: Uses the rules from the version of the API server you’re currently running. It’s convenient but can cause surprises during cluster upgrades if new checks are added.
  • A specific version (v1.27): Pins the policy definition exactly to that release. This is the safe, predictable choice for production. Your enforcement policy doesn’t change just because you upgraded the control plane.

My strong recommendation? Pin your enforce mode to a specific version. You can let warn and audit use latest to sniff out upcoming changes.

Exemptions: The Necessary Evil

Here’s the part where the designers acknowledged reality: sometimes you need to break the rules. The system administrator’s Pod needs to run as root. A DaemonSet *needs` to use the host network. PSA has a built-in exemption system for these edge cases.

You don’t configure this on the namespace; you configure it when you start the API server (or via flags on your Kubernetes distribution). The exemptions are based on usernames, group memberships, or, most usefully, runtime class names. The typical pattern is to create a special runtime class (e.g., privileged) and exempt it from PSA checks. Then, any Pod that absolutely must break the rules can use that runtime class.

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: privileged
handler: runc
# This pod will use the 'privileged' runtime class and bypass PSA checks
apiVersion: v1
kind: Pod
metadata:
  name: exempted-pod
spec:
  runtimeClassName: privileged # The magic key
  containers:
  - name: system-thing
    image: docker.io/library/ubuntu
    command: ["/bin/sleep", "infinity"]
    securityContext:
      runAsUser: 0
      privileged: true

This is powerful, so wield it carefully. Exemptions are a gaping hole in your security fence. The best practice is to treat the process of getting a Pod into an exempted runtime class as a full-blown, peer-reviewed exception process. It should be rare and documented.