Choosing a Kubernetes Policy Engine#

Kubernetes does not enforce security best practices by default. A freshly deployed cluster allows containers to run as root, pull images from any registry, mount the host filesystem, and use the host network. Policy engines close this gap by intercepting API requests through admission webhooks and rejecting or modifying resources that violate your rules.

The three main options – Pod Security Admission (built-in), OPA Gatekeeper, and Kyverno – serve different needs. Choosing the wrong one leads to either insufficient enforcement or unnecessary operational burden.

Why Policy Enforcement Matters#

Without policy enforcement, misconfiguration is inevitable. A developer who does not set securityContext.runAsNonRoot: true deploys a container running as root. A Helm chart from a public repository uses hostNetwork: true without anyone noticing. A CI pipeline pushes an image to a public registry and the deployment pulls from there instead of the internal registry.

Policy engines prevent these mistakes systematically, at the API server level, before resources are created. They shift security left without relying on code reviews to catch every YAML mistake.

Options at a Glance#

Capability Pod Security Admission (PSA) OPA Gatekeeper Kyverno
Installation required No (built into K8s 1.25+) Yes (Helm chart) Yes (Helm chart)
Policy language Predefined profiles Rego (purpose-built policy language) YAML (Kubernetes-native)
Validation (block bad resources) Yes (profile-level) Yes (fine-grained) Yes (fine-grained)
Mutation (modify resources) No Yes (less intuitive) Yes (first-class support)
Generation (create resources) No No Yes (create NetworkPolicies, quotas automatically)
Audit existing resources Warn mode only Yes (full audit) Yes (policy reports)
Granularity Per-namespace profiles Per-resource, per-field Per-resource, per-field
Custom policies No (three fixed profiles) Unlimited (Rego) Unlimited (YAML)
CNCF status Part of Kubernetes Graduated Incubating
Resource overhead Zero ~500MB RAM, moderate CPU ~200-400MB RAM, moderate CPU
Learning curve Very low High (Rego) Low-Medium (YAML)

Pod Security Admission (PSA)#

PSA is built into Kubernetes 1.25 and later. It replaces the deprecated PodSecurityPolicy and provides three profiles applied at the namespace level:

  • Privileged: No restrictions. Allows everything.
  • Baseline: Prevents known privilege escalations. Blocks hostNetwork, hostPID, hostIPC, and most dangerous capabilities.
  • Restricted: Heavily restricted. Requires running as non-root, drops all capabilities, forbids privilege escalation, requires seccomp profiles.

Each profile can operate in three modes: enforce (reject violations), warn (allow but print warnings), and audit (log violations to audit log).

Choose PSA when:

  • You want a security baseline with zero installation and zero maintenance.
  • Profile-level granularity (privileged/baseline/restricted per namespace) is sufficient.
  • You are on Kubernetes 1.25+ and want to replace deprecated PodSecurityPolicies.
  • Your team is small and custom policies are not needed.

Limitations to understand:

  • Coarse-grained. You cannot say “allow images only from registry.example.com” or “require label app.kubernetes.io/name on all resources.” You get three profiles, take them or leave them.
  • No mutation. PSA cannot inject default security contexts, add labels, or modify resources. If a pod lacks runAsNonRoot: true, PSA rejects it – it does not fix it.
  • No audit of existing resources beyond warn mode. You cannot scan the cluster for violations against the restricted profile.
  • Namespace-scoped only. You cannot set different policies for different workloads within the same namespace.

OPA Gatekeeper#

Gatekeeper brings the Open Policy Agent (OPA) into Kubernetes as an admission controller. Policies are written in Rego, a purpose-built declarative policy language, and packaged as ConstraintTemplate CRDs. Each template defines the policy logic; Constraint CRDs instantiate the template with specific parameters.

Choose Gatekeeper when:

  • You need complex, conditional policy logic that is difficult to express in YAML (cross-resource validation, mathematical conditions, string manipulation).
  • Your organization already uses OPA for non-Kubernetes policy enforcement (Terraform, API gateways, CI pipelines) and wants a unified policy language.
  • You want the backing of a CNCF graduated project with a large community and extensive policy library.
  • Your policy team has the capacity to learn and maintain Rego.

Limitations to understand:

  • Rego has a significant learning curve. It is a declarative language that thinks differently from procedural languages. Developers unfamiliar with logic programming often find it counterintuitive.
  • Higher resource consumption. Gatekeeper typically requires ~500MB RAM and meaningful CPU for policy evaluation, especially with many constraints.
  • Constraint template management adds operational overhead. Each policy requires a ConstraintTemplate (the logic) and one or more Constraints (the parameters).
  • Mutation support exists but is less intuitive than Kyverno’s approach. Gatekeeper uses an Assign CRD with JSON patch semantics.

Kyverno#

Kyverno is a Kubernetes-native policy engine where policies are written entirely in YAML. It uses Kubernetes resource patterns for matching and supports validation, mutation, generation, and image verification as first-class features.

Choose Kyverno when:

  • Your team prefers writing policies in YAML rather than learning a new language.
  • Mutation is a core requirement – you want to automatically inject labels, annotations, default security contexts, sidecar containers, or resource limits.
  • You want to automatically generate resources when other resources are created (for example, create a NetworkPolicy whenever a new namespace is created).
  • Image signature verification (via Sigstore/cosign) is part of your supply chain security strategy.
  • You want policy reports that show compliance status across the cluster as Kubernetes resources.

Limitations to understand:

  • Complex conditional logic is harder to express in YAML than in Rego. Kyverno supports preconditions and JMESPath expressions, but deeply nested logic becomes unwieldy.
  • Resource consumption at scale. With hundreds of policies and high API server throughput, Kyverno’s webhook can become a bottleneck.
  • Younger project than Gatekeeper (CNCF Incubating vs Graduated), though the community is growing rapidly.
  • Cleanup policies and generate rules add complexity – generated resources need lifecycle management.

Feature Comparison: Common Policy Scenarios#

Understanding how each tool handles real-world policies clarifies the differences.

Require labels on all resources:

  • PSA: Cannot do this. PSA only enforces security profiles.
  • Gatekeeper: Write a ConstraintTemplate that checks metadata.labels. Moderate effort, well-documented in the Gatekeeper library.
  • Kyverno: A validate rule matching all resources with a pattern requiring specific labels. Straightforward YAML, no new language.

Block images from untrusted registries:

  • PSA: Cannot do this.
  • Gatekeeper: Rego policy that parses the container image string and validates against an allowed list. Effective but requires Rego string manipulation.
  • Kyverno: A validate rule with an imageReferences match. Clean and readable.

Auto-inject sidecar containers:

  • PSA: Cannot do this.
  • Gatekeeper: Supported via Assign mutation CRD with JSON patch operations. Functional but verbose and less intuitive.
  • Kyverno: A mutate rule with a patchStrategicMerge that adds the sidecar container. This is where Kyverno excels – mutation is a first-class citizen.

Automatically create a NetworkPolicy for every new namespace:

  • PSA: Cannot do this.
  • Gatekeeper: Cannot do this. Gatekeeper validates and mutates but does not generate resources.
  • Kyverno: A generate rule triggered by namespace creation. Unique capability.

Complex conditional logic (e.g., “if namespace has label X and pod does not have annotation Y and container count > 2, then require resource Z”):

  • PSA: Cannot do this.
  • Gatekeeper: Rego excels here. Complex conditional logic is what Rego was designed for.
  • Kyverno: Doable with preconditions and JMESPath expressions, but readability degrades with deeply nested conditions.

Resource Overhead Comparison#

Policy engines run as webhook servers that intercept every relevant API request. Resource consumption matters.

Engine Typical RAM CPU (idle) CPU (under load) Pods Notes
PSA 0 (built-in) 0 0 0 Part of kube-apiserver
Gatekeeper 400-600MB Low Moderate-High 3 (audit, controller, webhook) Scales with constraint count
Kyverno 200-400MB Low Moderate 2-3 (admission, reports) Scales with policy count and API throughput

Layering Strategies#

The tools are not mutually exclusive. The strongest pattern is layering them.

PSA as baseline + Kyverno or Gatekeeper for custom policies: Apply PSA’s baseline or restricted profile across all namespaces for free, zero-install security. Then deploy Kyverno or Gatekeeper for organization-specific policies (image registry restrictions, label requirements, resource limits). PSA catches the obvious security violations; the policy engine handles the custom rules.

PSA in warn mode + Kyverno in enforce mode: Use PSA’s warn mode to surface violations without blocking, while Kyverno’s policies do the actual enforcement. This gives you PSA’s built-in visibility plus Kyverno’s flexibility.

Migration from PodSecurityPolicies#

PodSecurityPolicies (PSP) were removed in Kubernetes 1.25. If you are migrating:

For basic security enforcement: Move to PSA. Map your PSP rules to the closest PSA profile. Accept that PSA is coarser-grained – some nuance from PSPs will be lost.

For feature parity with PSPs: Move to Kyverno or Gatekeeper. Both can replicate every PSP rule and add capabilities that PSPs never had (mutation, resource generation, audit reports).

Recommended migration sequence:

  1. Enable PSA in warn mode on all namespaces to identify violations.
  2. Deploy Kyverno or Gatekeeper alongside existing PSPs.
  3. Create policies that replicate your PSP rules.
  4. Test in audit mode to confirm behavior matches.
  5. Switch to enforce mode.
  6. Remove PSPs.

Decision Summary#

Start with PSA. It is free, built-in, and covers the most common security baselines. Every cluster should have at least PSA baseline enabled.

Add Kyverno if you need mutation (injecting defaults, sidecars, labels), resource generation, image verification, or custom validation policies and your team prefers YAML over learning a new language.

Add Gatekeeper if you need complex conditional policy logic, your organization already uses OPA elsewhere, or your policy team is comfortable with Rego and values its expressiveness.

Use both PSA + one policy engine as the recommended production pattern. PSA for the baseline, Kyverno or Gatekeeper for everything else.