Skip to content
Hogin Hogin
Go back

Kyverno vs OPA Gatekeeper: when to pick which

7 мин чтения

Kyverno and OPA Gatekeeper solve the same problem: block a bad resource before it lands in etcd. Both are CNCF-graduated, both operate as admission webhooks, both ship ready-made policy libraries. The difference is not quality — Kyverno works great inside a single cluster, while OPA is the right choice if your policy also guards Terraform and CI pipelines.

Table of contents

Open Table of contents

What admission webhooks do and why you need a policy engine

Every kubectl apply passes through several layers inside the API Server: authentication, RBAC authorization, schema validation — and only then reaches the admission webhook chain. A ValidatingAdmissionWebhook can reject the request; a MutatingAdmissionWebhook can patch it. Only after both do anything get written to etcd.

Kyverno and OPA Gatekeeper live in that chain: they intercept creation and updates of Pods, Deployments, Namespaces, ServiceAccounts, and any other resource, and apply your rules before anything changes.

Request path through the Admission Webhook

The meaningful difference starts at the next step — how you write the rules.

Policy language: YAML vs Rego

Kyverno expresses policies as native Kubernetes Custom Resources. ClusterPolicy (cluster-wide) and Policy (namespace-scoped) are ordinary YAML manifests you ship with kubectl apply. Logic is built from match blocks combined with validate, mutate, or generate actions. Since Kyverno 1.17 CEL expressions — the same language as native Kubernetes ValidatingAdmissionPolicy — reached v1 status, so complex logic now fits in a single expression field.

Here is what banning :latest tags looks like in Kyverno:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-pinned-tag
      match:
        any:
          - resources:
              kinds: [Pod]
      validate:
        cel:
          expressions:
            - expression: >
                object.spec.containers.all(c,
                  !c.image.endsWith(':latest'))
              message: "Tag :latest is not allowed. Use a specific tag or SHA digest."

OPA Gatekeeper uses Rego, the functional language of Open Policy Agent. A policy has two objects: a ConstraintTemplate (contains the Rego logic and defines the CRD) and a Constraint (an instance with parameters and scope). Rego is expressive for complex logic, but has its own learning curve — variables are immutable and control flow is non-linear.

The same policy on Gatekeeper:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowlatestimage
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowLatestImage
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowlatestimage
        violation[{"msg": msg}] {
          c := input.review.object.spec.containers[_]
          endswith(c.image, ":latest")
          msg := sprintf("Container %v uses the :latest tag", [c.name])
        }
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowLatestImage
metadata:
  name: disallow-latest
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: [Pod]

Twice as many files and twice as many new concepts. That is the core decision point: if everything is Kubernetes, Kyverno lets you write policy in the same YAML you use for everything else. If the same check must run in Terraform and GitHub Actions too, OPA Rego is reusable across all of them without rewriting.

Capabilities: validation, mutation, generation

Kyverno does three things that OPA Gatekeeper cannot do out of the box:

Validation — both support it. Reject a non-compliant resource. Audit mode lets you observe violations without blocking first; switching to Enforce is a one-line change.

Mutation — rewrite the resource before saving. Kyverno has a stable, feature-rich DSL: add labels, set securityContext.runAsNonRoot: true, remove hostNetwork. Gatekeeper shipped mutation in v3.14 and still keeps it in beta with noticeable syntax limitations.

Generation — create companion resources on a trigger. Create a Namespace? Kyverno automatically generates a NetworkPolicy, ResourceQuota, and LimitRange for it. OPA does not support this at all — it is outside its model entirely.

Kyverno vs OPA Gatekeeper capabilities

ValidatingAdmissionPolicy (k8s 1.30+): who integrates better

Kubernetes 1.30 stabilized ValidatingAdmissionPolicy (VAP) — a built-in CEL-based policy mechanism that does not require a third-party webhook. The network hop disappears and admission latency drops. VAP cannot mutate or generate, so it is a complement, not a replacement.

Kyverno 1.17 can automatically sync a ClusterPolicy to native VAP resources. Enable generateValidatingAdmissionPolicy: true at install time and Kyverno writes the ValidatingAdmissionPolicy object for you. Validation runs through Kubernetes natively; the webhook is only called for mutation and generation. From the operator’s point of view there is one policy object, and Kyverno picks the right execution path.

Gatekeeper v3.22 exposes VAP via enforcementAction: denyAction on a Constraint, routing validation through native VAP without a webhook call. The Rego logic stays the same — only the execution path changes.

Comparison table

FeatureKyvernoOPA Gatekeeper
Policy languageYAML + CELRego
Validation✅ stable✅ stable
Mutation✅ stable⚠️ beta
Generation
ValidatingAdmissionPolicy✅ auto-generate✅ via denyAction
Policy outside Kubernetes✅ Terraform, CI, API
Ready-made library✅ Kyverno Policies✅ Gatekeeper Library
Learning curveLow (YAML)Medium (Rego)
Audit mode

What you need to run Kyverno

Requirements: Kubernetes ≥ 1.25, Helm 3.

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --set features.generateValidatingAdmissionPolicy.enabled=true

Apply a ready-made policy from the official library:

kubectl apply -f \
  https://raw.githubusercontent.com/kyverno/policies/main/best-practices/disallow-latest-tag/disallow-latest-tag.yaml

Test it:

kubectl run test --image=nginx:latest --restart=Never
# Error from server: admission webhook denied the request:
# Tag :latest is not allowed. Use a specific tag or SHA digest.

Check the audit report for existing resources:

kubectl get policyreport -A
# NAMESPACE   NAME                            PASS   FAIL   WARN
# default     cpol-disallow-latest-tag        12     3      0

What you need to run OPA Gatekeeper

Requirements: Kubernetes ≥ 1.25, Helm 3.

helm repo add gatekeeper \
  https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system --create-namespace

Apply the ConstraintTemplate and Constraint from the example above:

kubectl apply -f constraint-template.yaml
kubectl apply -f constraint.yaml

Test it:

kubectl run test --image=nginx:latest --restart=Never
# Error from server ([disallow-latest] Container test uses the :latest tag)

Check violations via the Constraint status:

kubectl get k8sdisallowlatestimage disallow-latest \
  -o jsonpath='{.status.violations}' | jq .

When a hybrid setup makes sense

Large platform teams often run both. Kyverno handles mutation and generation inside the cluster: it stamps required labels, sets securityContext, and creates a NetworkPolicy for every new Namespace. OPA handles validation — and the same Rego logic runs in Kubernetes, in GitHub Actions via conftest, and in Terraform via OPA CLI.

The split is clear: Kyverno mutates and generates inside the cluster; OPA validates everywhere. The main risk of the hybrid is duplicating validation rules in both engines, which quickly leads to drift and confusion. If you run both, strictly define ownership from day one — and resist the temptation to “just add a quick Kyverno check” when OPA already owns that rule.

Hybrid setup: Kyverno + OPA

Bottom line

Kyverno is the right pick for teams that live in YAML and stay inside Kubernetes: fewer new concepts, stable mutation and generation out of the box, automatic VAP integration. OPA Gatekeeper is the right pick if your policy extends beyond the cluster into Terraform or CI — one language covers everything. The hybrid (Kyverno mutates, OPA validates) works when you need both, but demands strict boundary enforcement from the start, or you end up maintaining two competing copies of the same rules.


Share this post:

Next Post
Flux in one evening: GitOps for a single small cluster