Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Kyverno, the Kubernetes policy engine in YAML and CEL

By updated

Improve this page

Kubernetes logo

Kyverno is a Kubernetes policy engine that can validate, mutate, generate resources and verify image signatures, at the moment they are submitted to the API server. A CNCF Graduated project since March 2026, it is written in YAML and in CEL, the Kubernetes expression language. This guide is for a DevOps engineer who wants richer guardrails than the Pod Security Standards. Everything has been replayed on Kyverno 1.19.1 and Kubernetes 1.37: installation in 40 seconds, rejection of an unsigned image, and a cluster that rejects every write when the Kyverno webhook becomes unreachable.

A point of timeline to know before writing your first policy, because it decides which syntax to learn. The CEL policy types arrived in two steps: ValidatingPolicy and ImageValidatingPolicy in v1.14, then MutatingPolicy, GeneratingPolicy and DeletingPolicy in v1.15. They moved to beta in 1.16, then to the stable policies.kyverno.io/v1 API in v1.17, the release that also declared the historical ClusterPolicy and CleanupPolicy CRDs deprecated. v1.19 makes this deprecation visible: the kyverno.io CRDs, Policy included, carry deprecated: true and the API returns a warning on every apply. Their removal is announced for v1.20.

The practical consequence: if you start today, write your policies in CEL. The historical YAML DSL remains in this guide because most production clusters still use it and you need to be able to read it, not because you should write new ones.

You do not even have to remember it: the API tells you on every apply.

Output
Warning: kyverno.io/v1 ClusterPolicy is deprecated and will be removed in a
future release; migrate to ValidatingPolicy, MutatingPolicy, GeneratingPolicy
or ImageValidatingPolicy (policies.kyverno.io), see
https://kyverno.io/docs/guides/migration-to-cel/

The resource is created anyway and works normally: it is a warning, not a rejection. The policies.kyverno.io/v1 group also offers more types than the five listed here, each with a namespace-scoped variant: NamespacedValidatingPolicy, NamespacedMutatingPolicy and so on. Useful when a team must set its own rules without touching the whole cluster.

A word on the positioning relative to the CKS: the certification curriculum does not list Kyverno as a required skill. It mentions Pod Security Standards, permitted registries, signing and validating artifacts and static analysis. Kyverno is a practical tool to implement these requirements on a real cluster, but it is not an exam topic in itself.

What you will learn

  • When to use Kyverno (and when not to)
  • Install Kyverno and understand its operational complexity, webhook failure included
  • Create security-oriented validation policies
  • Verify image signatures (Supply Chain Security)
  • Test a policy outside the cluster with the Kyverno CLI
  • Diagnose violations with PolicyReports

Compatibility and versions

Kyverno follows a release cycle aligned with Kubernetes. Each major release supports several Kubernetes versions, but the available features vary from one combination to another: it is the Kyverno and Kubernetes pair that you need to check, not either one in isolation.

KyvernoHelm chartNotes
1.19.x3.9.xCurrent branch, the one in this guide; deprecation warnings on the kyverno.io API
1.18.x3.8.xPrevious branch
1.17.x3.7.xEnd of support

Community support lasts about three months per minor branch: the release of version n+1 ends the fixes for version n. The project states that it tests 1.19 on Kubernetes 1.33 to 1.35. Versions outside this range, including the 1.36 and 1.37 of this course, may work, but they are not tested by the project: the examples below were replayed here, which is not a guarantee of support.

The examples in this guide were replayed on Kyverno v1.19.1, installed by chart 3.9.1, on a Kubernetes 1.37 cluster. The installation takes about forty seconds and deploys four controllers: admission, background, cleanup and reports. It is therefore not a minor add-on, which is one more reason to measure its impact before switching it to blocking mode.

When to use Kyverno

Each tool has its area of excellence. This table helps you choose according to your real need, not the popularity of the tool.

NeedRecommended toolWhy
Simple validation, no dependencyVAPNative, GA since K8s 1.30
Standard security profiles (runAsNonRoot...)Pod Security Admission3 ready-made profiles
Simple mutation (defaults)MutatingAdmissionPolicyNative and enabled since K8s 1.36
Advanced mutation (sidecars, foreach, context)KyvernoRicher than CEL alone
Automatic generation (NetworkPolicies)KyvernoUnique feature
Image signature verificationKyvernoCosign/Sigstore integration
Existing OPA/Rego cultureGatekeeperRego ecosystem

Simple decision rule: if you need generation, signature verification or a mutation that CEL cannot express, Kyverno is probably the right choice. Otherwise, start with the native solutions: PSA, ValidatingAdmissionPolicy, and MutatingAdmissionPolicy since 1.36.

What Kyverno does better

This split has shifted along the way, and your cluster version decides. Up to Kubernetes 1.35, the API server could reject a resource with a ValidatingAdmissionPolicy, but not modify it. Since 1.36, MutatingAdmissionPolicy is GA and enabled by default: simple CEL mutation has become native, and Kyverno 1.19 supports it too.

That leaves three capabilities with no native equivalent: generating a resource in response to another, querying a registry to verify a signature, and producing PolicyReports. Add to that a mutation richer than CEL, with patchStrategicMerge, context and foreach. This is what still justifies adding a dependency to the control plane.

  • Rich mutation: inject sidecars, apply a rule to every container with foreach, rely on external context. Simple defaults, on the other hand, now belong to MutatingAdmissionPolicy
  • Generation: automatically create NetworkPolicies, Secrets, ResourceQuotas
  • Image verification: Cosign, Sigstore, SLSA attestations
  • PolicyReports: native compliance audit

When NOT to use Kyverno

What these situations have in common is the benefit versus operational debt ratio. Every webhook added to the cluster is a component to maintain, to update at the pace of Kubernetes and to monitor. If the need can be covered by a mechanism already present in the API server, adding Kyverno costs more than it brings.

  • Simple validation without mutation → prefer VAP (native, no dependency)
  • Simple defaults to set → prefer MutatingAdmissionPolicy, native since 1.36
  • Standard security profiles → prefer Pod Security Admission (simpler)
  • Cluster without HA → Kyverno is an admission webhook, a sensitive component
  • Team already trained in Rego (in French) → prefer Gatekeeper

Operational complexity

Installing Kyverno means inserting a component between the API server and every resource write in the cluster. This position explains most of its operational constraints: its availability governs that of deployments, its TLS certificates have a limited lifetime, and its RBAC rights cover a large part of the cluster. Before writing a single policy, decide how the cluster behaves when Kyverno is unavailable.

The diagram shows the path of a write. The API server calls the Kyverno webhook, which mutates then validates the resource before it is stored in etcd. The bottom exit is the one people forget: if the webhook does not answer, the API server rejects the write.

Path of a write with Kyverno: kubectl apply reaches the API server, which calls Kyverno for mutation then validation and signature verification; a compliant resource is stored in etcd, a non-compliant resource is rejected with the policy message, and if the webhook is unreachable the write is rejected.compliantnon-compliantwebhook failurekubectl applyAPI serverKyvernomutationKyvernovalidation and signatureetcdobject storedRejected with thepolicy messageKyverno unreachable:write rejected
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

This is not theoretical. The chart's resource webhooks carry failurePolicy: Fail. Measured by making the Kyverno service unreachable: even a compliant pod is rejected.

Output
Error from server (InternalError): Internal error occurred: failed calling webhook
"mutate.kyverno.svc-fail": failed to call webhook: Post
"https://kyverno-svc.kyverno.svc:443/mutate/fail?timeout=10s": dial tcp
10.96.174.50:443: connect: connection refused

In production, deploy with HA:

Terminal window
helm install kyverno kyverno/kyverno --version 3.9.1 \
--set admissionController.replicas=3 \
--set backgroundController.replicas=2 \
-n kyverno --create-namespace

If Kyverno blocks the cluster, deleting its webhooks is not enough as long as its admission controller is running: measured, it recreates kyverno-resource-validating-webhook-cfg in under 20 seconds. You must stop it first. A clean shutdown removes the resource webhooks by itself; manual deletion remains the safety net if a crashed controller left them in place.

Terminal window
# 1. Stop the admission controller
kubectl -n kyverno scale deployment kyverno-admission-controller --replicas=0
# 2. Delete the webhooks that would remain in place
kubectl delete validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg
kubectl delete mutatingwebhookconfiguration kyverno-resource-mutating-webhook-cfg

Installation

Two methods coexist, and the choice is not cosmetic. The Helm chart exposes the production settings, number of replicas per controller, resources, namespace exclusions, and allows a controlled upgrade. The install.yaml manifest deploys a fixed configuration, handy for a throwaway cluster but hard to customise afterwards. In both cases, Kyverno must live in a dedicated namespace: the project explicitly forbids co-locating it with other applications, kube-system included.

Terminal window
# Add the Helm repository
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
# Install Kyverno with HA, pinned chart (Kyverno 1.19.1)
helm install kyverno kyverno/kyverno --version 3.9.1 \
-n kyverno --create-namespace \
--set admissionController.replicas=2 \
--wait --timeout 5m

Check the installation:

Terminal window
kubectl get pods -n kyverno
NAME READY STATUS RESTARTS AGE
kyverno-admission-controller-769b8f7647-pqjbn 1/1 Running 0 37s
kyverno-admission-controller-769b8f7647-xfbbk 1/1 Running 0 37s
kyverno-background-controller-86d8df7447-wvdxj 1/1 Running 0 37s
kyverno-cleanup-controller-86c886ffff-nlnkk 1/1 Running 0 37s
kyverno-reports-controller-57c7978d69-z2d5z 1/1 Running 0 37s

Example 1: Requiring runAsNonRoot (CKS-ready)

This example maps directly to the CKS objective "Use appropriate pod security standards". Both tabs produce the same rejection, through very different paths: the CEL version evaluates a boolean expression on the submitted object, whereas the historical version compares the resource with a YAML pattern applied recursively. The CEL version wins on the readability of complex conditions, in particular the exclusion of system namespaces, which fits here in a single matchConditions line.

apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-run-as-non-root
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
matchConditions:
- name: exclude-system-ns
expression: >-
!(['kube-system','kyverno'].exists(ns, object.metadata.namespace == ns))
validations:
- expression: >-
object.spec.containers.all(c,
has(c.securityContext) &&
has(c.securityContext.runAsNonRoot) &&
c.securityContext.runAsNonRoot == true
)
message: "Tous les conteneurs doivent avoir runAsNonRoot: true."

Replayed on Kubernetes 1.37, the CEL version shows READY true in kubectl get validatingpolicy within a few seconds, then rejects an nginx pod without a securityContext with the policy message:

Output
Error from server: admission webhook "vpol.validate.kyverno.svc-fail-finegrained-require-run-as-non-root"
denied the request: Policy require-run-as-non-root failed: Tous les conteneurs doivent avoir runAsNonRoot: true.

Example 2: Rejecting hostPath and hostNetwork

These three rules close the doors that give the container direct access to the node: a hostPath volume mounts a host directory into the pod, hostNetwork puts the pod in the node's network namespace, hostPID and hostIPC give it visibility of the host's processes and shared memory. Each rule is independent and fails separately, which makes the diagnosis readable: Kyverno reports the exact path that triggered the rejection.

Note the =(volumes) anchor: it makes the check conditional. A pod without any volume passes the rule instead of failing on a missing field. Measured: a pod that mounts the host's /etc is rejected with rule deny-hostpath failed at path /spec/volumes/0/hostPath/, a pod with hostNetwork: true with rule deny-host-network failed at path /spec/hostNetwork/.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: deny-host-access
spec:
background: true
rules:
- name: deny-hostpath
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "Les volumes hostPath sont interdits"
pattern:
spec:
=(volumes):
- X(hostPath): "null"
- name: deny-host-network
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "hostNetwork n'est pas autorisé"
pattern:
spec:
=(hostNetwork): false
- name: deny-host-pid-ipc
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "hostPID et hostIPC sont interdits"
pattern:
spec:
=(hostPID): false
=(hostIPC): false

Example 3: Restricting the allowed registries

This control links Kyverno to the Supply Chain Security domain of the CKS. The foreach loop goes through every container of the pod, which avoids the classic blind spot of a rule that would only check the first one. Watch out for the trap of the image without an explicit registry: a manifest that asks for busybox:1.37 matches none of the allowed patterns and is rejected, even though the image does come from Docker Hub. Measured: the same image written docker.io/library/busybox:1.37 passes the rule. This is the intended effect, provided you explain it in the error message.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
background: true
rules:
- name: validate-registries
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Enforce
message: "Images autorisées uniquement depuis gcr.io ou docker.io/library"
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
all:
- key: "{{ element.image }}"
operator: AnyNotIn
value:
- "gcr.io/*"
- "docker.io/library/*"

Image signature verification (Supply Chain)

Verifying a signature at admission moves the control to the last useful moment: whatever happened in the build chain, an image not signed with the expected key does not enter the cluster. Kyverno resolves the image reference with the registry, fetches the associated Cosign signature and validates it against the public key declared in the policy. A consequence to anticipate: the admission controller needs outbound network access to the registry, and a private registry requires giving it read credentials.

With the new CEL policy types (1.17+)

ImageValidatingPolicy separates two concerns that the historical syntax mixed up. The attestors block declares who is allowed to sign; the validations block describes what is required of the image once the signature is verified. That is where you would add a condition on an SLSA attestation or on the content of an SBOM.

Two details cost an hour of debugging if you ignore them, both measured on Kyverno 1.19.1. The public key does not live in a Secret: the key field accepts data, plain-text PEM, kms or expression, never secretRef, and the API rejects the policy with unknown field "spec.attestors[0].cosign.key.secretRef". And the name of an attestor must not contain a dash, because it is referenced from a CEL expression: my-cosign-key reads as a subtraction there and returns undeclared reference to 'key'.

The policy below was replayed with the test image that the Kyverno project publishes, ghcr.io/kyverno/test-verify-image, whose signed variant carries a Cosign signature and whose unsigned variant carries none. This signature was not recorded in Sigstore's public transparency log (Rekor), hence the ctlog block that exempts it from that check; an image signed by cosign sign with a key, which records the signature in Rekor by default, does without it.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: verify-image-signature
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
matchImageReferences:
- glob: "ghcr.io/kyverno/test-verify-image*"
attestors:
- name: mycosignkey
cosign:
key:
data: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
ctlog:
insecureIgnoreTlog: true
validations:
- expression: >-
images.containers.map(image,
verifyImageSignatures(image, [attestors.mycosignkey])).all(e, e > 0)
message: "Image must be signed with our Cosign key."

The result, measured on Kubernetes 1.37: the signed variant is admitted, the unsigned variant is rejected with the policy message, and an image outside the matchImageReferences pattern, such as busybox, is not affected.

Output
$ kubectl run signed --image=ghcr.io/kyverno/test-verify-image:signed@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105
pod/signed created
$ kubectl run unsigned --image=ghcr.io/kyverno/test-verify-image:unsigned@sha256:74a98f0e4d750c9052f092a7f7a72de7b20f94f176a490088f7a744c76c53ea5
Error from server: admission webhook "ivpol.validate.kyverno.svc-fail" denied the request:
Policy verify-image-signature failed: Image must be signed with our Cosign key.

The call to verifyImageSignatures is not decorative: it is what triggers the verification. Measured by replacing it with the expression "true": the attestors are then evaluated by nobody, and every image matching the pattern is rejected, the signed variant included, with image ... is not verified: no policy performed a signature or attestation check on it. A validation must therefore always call verifyImageSignatures, or verifyAttestationSignatures for an attestation.

With the historical ClusterPolicy

The verifyImages rule writes the public key in clear in the policy, which remains acceptable, a public key not being a secret, but it complicates rotation: changing it means editing every policy involved. The background: false is not a detail: verifying signatures requires network calls to the registry, an operation too costly to be replayed as a background task on every existing resource. This example was not replayed: it is there to help you recognise this syntax in an existing cluster, the CEL version above being the one to write.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-cosign
spec:
background: false
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "gcr.io/my-project/*"
failureAction: Enforce
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----

Mutation: adding defaults

Mutation lets Kyverno automatically modify a resource before it is stored. It is a mature Kyverno feature, and the policy below adds three security settings to every container that does not declare them.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-security-defaults
spec:
background: false # Mutation = no background
rules:
- name: add-security-context
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
securityContext:
+(runAsNonRoot): true
+(allowPrivilegeEscalation): false
+(readOnlyRootFilesystem): true

Mutation operators

The Kyverno mutation syntax uses special operators to control its behaviour. Understanding these operators is essential to write effective mutations.

OperatorEffectUse
(key)Conditional anchorTarget specific containers
+(key)Add if absentNon-intrusive defaults
<(key)Global anchorCondition on another part of the resource

Concrete example: if you want to add runAsNonRoot: true only to pods that do not already have that value, use +(runAsNonRoot): true. This avoids overriding an explicit configuration set by the developer.

A security mutation can break what it protects. Measured on Kubernetes 1.37: a busybox pod does receive the three fields, then stays in CreateContainerConfigError, because its image starts as root and runAsNonRoot: true now forbids it. Before generalising such a mutation, check that your images run as non-root and support a read-only file system.

Mutation only knows these three anchors. There is no removal anchor: to remove a field, you go through patchesJson6902 and its remove operation. The =() equality, ^() existence and X() negation anchors belong to validation, not to mutation, and that is where you meet them earlier in this guide.

Generation: automatic NetworkPolicies

Generation answers a recurring problem: a security resource must exist in every namespace, and nobody thinks of creating it. Here, creating a namespace triggers the creation of a NetworkPolicy that blocks all incoming and outgoing traffic by default. The synchronize: true field changes the contract: Kyverno does not merely create the resource, it restores it if someone modifies or deletes it. This is what sets generation apart from a simple initialisation script.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-deny
spec:
rules:
- name: create-network-policy
match:
any:
- resources:
kinds:
- Namespace
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny
namespace: "{{request.object.metadata.name}}"
synchronize: true
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

Effect: measured, every new namespace receives a default-deny NetworkPolicy within a few seconds. Namespaces that existed before the policy do not receive it: for that you need to enable generateExisting: true in the rule.

failureAction and gradual rollout

Deploying a policy straight in blocking mode (Enforce) is risky. Kyverno allows a gradual rollout, with two modes and per-namespace overrides. Measured with the policy below: a pod without an app label is rejected in production and created in another namespace, where the violation goes to a PolicyReport.

ValueEffectPhase
EnforceBlocks the requestProduction
AuditLogs into PolicyReportsDiscovery

Recommended strategy:

  1. Start with Audit: violations are recorded in the PolicyReports. Look at them to understand the impact.
  2. Add overrides: switch to Enforce on the critical namespaces, production, while staying in Audit elsewhere.
  3. Generalise Enforce: once all workloads are compliant, switch to blocking mode globally.

Gradual strategy, with an override for production:

spec:
rules:
- name: require-labels
match:
any:
- resources:
kinds:
- Pod
validate:
failureAction: Audit # Permissive by default
failureActionOverrides:
- action: Enforce
namespaces:
- production # Strict in production
message: "Le label 'app' est obligatoire"
pattern:
metadata:
labels:
app: "?*"

Kyverno CLI (shift-left)

The CLI lets you test policies locally before deployment, without a cluster, which makes it usable in a pipeline job. Two commands cover the essentials: kyverno apply evaluates a policy against one or more manifests and shows the pass / fail / warn / error / skip count, while kyverno test runs a suite of cases described in a kyverno-test.yaml file. The apply command also serves as a syntax check: a malformed policy comes up as error before it even reaches the cluster.

Terminal window
# Install through Krew (the plugin follows version 1.19.1)
kubectl krew install kyverno
# Test a policy against a manifest
kyverno apply ./policy.yaml --resource ./pod.yaml
# Run the test suite described in ./policy-tests/kyverno-test.yaml
kyverno test ./policy-tests/

Here is the actual output of kyverno apply, measured with CLI 1.19.1 on a pod that violates the policy of example 1. The command exits with code 1, which is enough to fail a CI job:

Applying 1 policy rule(s) to 1 resource(s)...
policy require-run-as-non-root -> resource equipe-web/Pod/nginx-sans-securite failed:
1 - Tous les conteneurs doivent avoir runAsNonRoot: true.
pass: 0, fail: 1, warn: 0, error: 0, skip: 0

A test suite describes the expected result for each resource. The kyverno create test command generates a skeleton, but the 1.19.1 one carries a namespace field that kyverno test then rejects (unknown field "namespace"). The accepted format, measured, names the resource as namespace/name:

policy-tests/kyverno-test.yaml
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
name: require-run-as-non-root
policies:
- policy.yaml
resources:
- pod.yaml
results:
- policy: require-run-as-non-root
resources:
- equipe-web/nginx-sans-securite
kind: Pod
result: fail

kyverno test ./policy-tests/ then returns Test Summary: 1 tests passed and 0 tests failed: the test succeeds because the policy fails on this pod, as expected.

A frequent reflex is to look for a kyverno validate command to check the syntax: it does not exist. CLI 1.19.1 exposes apply, completion, create, docs, jp, migrate, test and version; fix and oci only appear with the KYVERNO_EXPERIMENTAL=true variable. To detect an invalid policy, go through kyverno apply.

The Kyverno CLI is part of the syllabus of the Kyverno Certified Associate certification, delivered by the Linux Foundation. For the CKS, it is not required but remains an asset: it lets you test a policy before deploying it, precisely the reflex the exam rewards.

Diagnosing policies

When a Kyverno policy "does not work", the symptom never says which of the two problems you have. Either the policy is rejected or inactive, and nothing is evaluated; or it works but does not select the resources you thought you were targeting, and its silence looks like a failure.

This distinction structures the whole diagnosis that follows, and it is settled in one command: an accepted rule shows Ready: True in its status. If so, look at the match and the enforcement mode, never at the installation.

Essential commands

The diagnosis follows two distinct tracks that must not be confused. The first concerns the policy itself: is it accepted by Kyverno? The answer is in kubectl describe, in the status.conditions field. The second concerns the evaluated resources: which violations were found? They live in the PolicyReports, with one report per evaluated object, fed only if the policy is in Audit mode or if background: true is active.

Terminal window
# List CEL policies (READY column) and historical ones
kubectl get validatingpolicies,imagevalidatingpolicies
kubectl get clusterpolicies,policies -A
# Details of a policy (errors, conditions)
kubectl describe clusterpolicy <name>
# PolicyReports (one report per evaluated object)
kubectl get policyreport -A
# Violation details
kubectl describe policyreport -n <namespace>
# Quick summary
kubectl get policyreport -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.summary}{"\n"}{end}'

Common errors

Here are the most frequent problems and how to solve them quickly, from symptom to fix.

SymptomLikely causeSolution
Policy "Ready: False"Syntax errorkubectl describe clusterpolicy
No violation in Auditbackground: falseAdd background: true
Mutation not appliedPod already existingRecreate the pod
Webhook timeoutKyverno overloadedIncrease replicas

Tip: the status.conditions field of a ClusterPolicy holds the detailed error messages. Look at it first.

Key points

  1. ClusterPolicy and CleanupPolicy are deprecated since 1.17, 1.19 shows a warning on every apply, and their removal is announced for v1.20
  2. validationFailureAction is deprecated: use validate.failureAction at rule level
  3. Sensitive component: an unreachable webhook rejects every write; deploy in HA, and stop the controller before deleting its webhooks
  4. Best CKS bridge: image verification (Supply Chain Security)
  5. For simple validation → prefer VAP (native, no dependency)
  6. Unique features: generation and mature mutation
  7. kyverno validate does not exist: the CLI is checked with kyverno apply and kyverno test
  8. An ImageValidatingPolicy must call verifyImageSignatures: with "true", every image is rejected, signed ones included

Knowledge check

These questions cover this guide only: the positioning of Kyverno against native solutions, the syntax of validation and mutation rules, and reading PolicyReports.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
8 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn