Kyverno Graduates from CNCF: A Production Guide to Policy-as-Code for Kubernetes Governance
On March 24, 2026, at KubeCon + CloudNativeCon Europe in Amsterdam, the CNCF officially announced Kyverno's graduation to the highest project maturity level. Kyverno is a Kubernetes-native policy engine that lets you define policies in standard YAML without learning a specialized language like Rego. It has been battle-tested at enterprise scale — LinkedIn processes over 20,000 admission requests per minute across 230+ clusters with 500K+ nodes, and organizations including Bloomberg, Coinbase, Deutsche Telekom, Spotify, and Vodafone rely on it in production.
Policy-as-Code is no longer optional. As clusters multiply into the hundreds and AI workloads become mainstream, manual security reviews and configuration audits simply cannot keep up. This guide covers Kyverno 1.17's core features, CEL-based policy authoring, production deployment strategies, and practical comparisons with OPA/Gatekeeper.
1. Kyverno's CNCF Graduation — What Changed
CNCF project maturity follows three levels: Sandbox, Incubating, and Graduated. The Graduated status requires production stability, strong community governance, and passing rigorous security audits. Kyverno now stands alongside Kubernetes, Prometheus, Envoy, Helm, and ArgoCD.
| Aspect | Kyverno (Graduated) | OPA/Gatekeeper (Graduated) |
|---|---|---|
| Policy Language | YAML + CEL (low learning curve) | Rego (separate language to learn) |
| Policy Types | Validate, Mutate, Generate, Cleanup, VerifyImages | Validate (Mutation beta in v3.17+) |
| Kubernetes Native | CRD-based, kubectl compatible | CRD-based (ConstraintTemplate) |
| Image Signature Verification | Built-in (Cosign v3, Notary) | Requires external tools |
| Auto Resource Generation | Native Generate policies | Requires separate controllers |
| CLI (CI/CD Integration) | kyverno apply / test | gator test |
| Scale Track Record | LinkedIn: 230+ clusters, 500K+ nodes | Google internal, large-scale validated |
| GitHub Stars | 9,000+ | 14,000+ (OPA total) |
CNCF CTO Chris Aniszczyk stated, "Policy-as-code has become critical for organizations running cloud native at scale." Kyverno co-creator Jim Bugwadia added, "Policy-as-code provides essential guardrails for autonomous governance without stifling innovation."
2. Kyverno 1.17 Core Features — How CEL Changes the Policy Paradigm
Kyverno 1.17, released in February 2026, marks the GA (Generally Available) milestone for CEL-based policy APIs. This aligns perfectly with Kubernetes itself adopting CEL for ValidatingAdmissionPolicy.
2.1 CEL Policy Types Reach GA
The following policy types have been promoted to stable v1 APIs:
# ValidatingPolicy (CEL-based validation) - GA in v1.17
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-resource-limits
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
validations:
- expression: >-
object.spec.containers.all(c,
has(c.resources) &&
has(c.resources.limits) &&
has(c.resources.limits.cpu) &&
has(c.resources.limits.memory))
message: "All containers must have CPU and memory limits defined."
The key advantage of CEL is language unification across the Kubernetes ecosystem. ValidatingAdmissionPolicy, Gateway API HTTPRoute matching, and now Kyverno all use the same CEL syntax.
2.2 Expanded CEL Function Library
v1.17 significantly expands the CEL function library:
# Hash functions for image digest verification
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: verify-image-digest
spec:
validationActions: [Deny]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
validations:
- expression: >-
object.spec.containers.all(c,
c.image.contains("@sha256:"))
message: "All container images must use SHA256 digest, not tags."
# X.509 certificate expiry validation
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: check-tls-cert-expiry
spec:
validationActions: [Audit]
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["secrets"]
operations: ["CREATE", "UPDATE"]
matchConditions:
- name: is-tls-secret
expression: "object.type == 'kubernetes.io/tls'"
validations:
- expression: >-
x509Decode(base64.decode(object.data['tls.crt'])).notAfter >
now() + duration('720h')
message: "TLS certificate must be valid for at least 30 days."
New functions include hashing (md5, sha1, sha256), math operations, X.509 certificate decoding, random string generation, JSON/YAML parsing, and time-based logic. Complex validations that previously required external webhooks can now be handled directly within policies.
2.3 Namespaced Mutation and Generation
Critical for multi-tenancy: namespace owners can now define Mutation/Generation policies within their namespace scope without cluster-wide permissions:
# Namespaced Mutation Policy
apiVersion: policies.kyverno.io/v1
kind: NamespacedMutatingPolicy
metadata:
name: add-team-labels
namespace: team-alpha
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
operations: ["CREATE"]
mutations:
- patchType: JSONPatch
jsonPatch:
expression: >-
[
JSONPatch{op: "add", path: "/metadata/labels/team", value: "alpha"},
JSONPatch{op: "add", path: "/metadata/labels/cost-center", value: "eng-101"}
]
3. Architecture Deep Dive
Kyverno leverages Kubernetes Dynamic Admission Control. When the API server receives a create/update/delete request, it forwards the request to Kyverno's webhook, which either allows, denies, or mutates it based on defined policies.
The core components are:
Admission Controller: Registered as Mutating/Validating Webhooks for real-time request processing. Validate policies deny or audit requests, while Mutate policies modify resources before they reach etcd.
Background Controller: Handles Generate and Cleanup policies asynchronously. When a namespace is created, it automatically provisions NetworkPolicy, ResourceQuota, LimitRange, and more.
Reports Controller: Generates PolicyReport CRDs aligned with the Kubernetes Policy Working Group specification, periodically scanning existing resources for compliance status.
4. Production Deployment Guide
4.1 Installing Kyverno with Helm
# Add Helm repository
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
# Install with production-recommended settings
helm install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace \
--version 3.3.4 \
--set admissionController.replicas=3 \
--set backgroundController.replicas=2 \
--set reportsController.replicas=2 \
--set cleanupController.replicas=2 \
--set admissionController.resources.limits.memory=1Gi \
--set admissionController.resources.limits.cpu=500m \
--set admissionController.resources.requests.memory=512Mi \
--set admissionController.resources.requests.cpu=200m \
--set features.policyExceptions.enabled=true \
--set features.policyExceptions.namespace=kyverno
# Verify installation
kubectl get pods -n kyverno
kubectl get crd | grep kyverno
Production Tip: Always deploy admissionController with 3+ replicas. If the webhook becomes a single point of failure, all resource creation/modification is blocked. Set a PodDisruptionBudget to maintain at least 2 pods during rolling updates.
4.2 Pod Security Standards Enforcement
Implementing Kubernetes Pod Security Standards (PSS) with Kyverno is the most common starting point:
# Pod Security: Baseline profile enforcement
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: pss-baseline-enforce
annotations:
policies.kyverno.io/title: "Pod Security Standards - Baseline"
policies.kyverno.io/category: "Pod Security"
policies.kyverno.io/severity: "high"
spec:
background: true
validationFailureAction: Enforce
rules:
# Block privileged containers
- name: deny-privileged-containers
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Privileged containers are not allowed."
pattern:
spec:
containers:
- securityContext:
privileged: "!true"
initContainers:
- securityContext:
privileged: "!true"
# Block host namespaces
- name: deny-host-namespaces
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Host namespaces (network, PID, IPC) are not allowed."
pattern:
spec:
hostNetwork: "!true"
hostPID: "!true"
hostIPC: "!true"
# Block dangerous capabilities
- name: deny-dangerous-capabilities
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Adding dangerous capabilities (NET_RAW, SYS_ADMIN) is not allowed."
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[].securityContext.capabilities.add[] }}"
operator: AnyIn
value: ["NET_RAW", "SYS_ADMIN", "ALL"]
4.3 Image Signature Verification (Supply Chain Security)
Kyverno's VerifyImages integrates with Sigstore/Cosign v3 to automatically verify container image signatures at admission time:
# Image signature verification policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
annotations:
policies.kyverno.io/title: "Verify Image Signatures"
policies.kyverno.io/category: "Supply Chain Security"
spec:
validationFailureAction: Enforce
webhookTimeoutSeconds: 30
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences:
- "registry.example.com/*"
- "ghcr.io/your-org/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
attestations:
- type: https://slsa.dev/provenance/v1
conditions:
- all:
- key: "{{ builder.id }}"
operator: Equals
value: "https://github.com/actions/runner"
4.4 Auto Resource Generation
Automatically provision standard resources when new namespaces are created:
# Auto-generate defaults on namespace creation
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-namespace-defaults
spec:
rules:
# Default NetworkPolicy (deny all ingress)
- name: generate-default-network-policy
match:
any:
- resources:
kinds: ["Namespace"]
exclude:
any:
- resources:
namespaces: ["kube-system", "kyverno", "istio-system"]
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{ request.object.metadata.name }}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress
# ResourceQuota
- name: generate-resource-quota
match:
any:
- resources:
kinds: ["Namespace"]
generate:
apiVersion: v1
kind: ResourceQuota
name: default-quota
namespace: "{{ request.object.metadata.name }}"
data:
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "50"
5. CI/CD Pipeline Integration — Shift-Left Security
Integrate the Kyverno CLI into your pipeline to catch policy violations before deployment:
# .github/workflows/kyverno-check.yaml
name: Kyverno Policy Check
on:
pull_request:
paths:
- 'k8s/**'
- 'helm/**'
jobs:
policy-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Kyverno CLI
run: |
curl -LO https://github.com/kyverno/kyverno/releases/download/v1.17.0/kyverno-cli_v1.17.0_linux_x86_64.tar.gz
tar -xzf kyverno-cli_v1.17.0_linux_x86_64.tar.gz
sudo mv kyverno /usr/local/bin/
- name: Validate Kubernetes manifests
run: |
kyverno apply policies/ \
--resource k8s/manifests/ \
--detailed-results \
--output json > results.json
- name: Check for violations
run: |
VIOLATIONS=$(jq '[.results[] | select(.status=="fail")] | length' results.json)
if [ "$VIOLATIONS" -gt 0 ]; then
echo "Policy violations found: $VIOLATIONS"
jq '.results[] | select(.status=="fail") | {policy: .policy, resource: .resource, message: .message}' results.json
exit 1
fi
echo "All policies passed!"
6. Troubleshooting Common Issues
6.1 Webhook Timeout Causing Resource Creation Failures
Symptom: context deadline exceeded errors on Pod creation.
# Check Kyverno pod status
kubectl get pods -n kyverno -o wide
kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller --tail=100
# Check webhook config
kubectl get mutatingwebhookconfigurations kyverno-resource-mutating-webhook-cfg -o yaml | grep -A5 timeoutSeconds
# Increase timeout (default 10s to 30s)
helm upgrade kyverno kyverno/kyverno \
--namespace kyverno \
--set admissionController.webhookTimeout=30
Warning: Changing
failurePolicytoIgnorelets resources through without policy checks when Kyverno is unresponsive. In production, keepFailmode and instead scale up replicas with adequate resources.
6.2 Checking Compliance Reports for Existing Resources
# List PolicyReports across namespaces
kubectl get policyreport -A
# Get failed policy details
kubectl get policyreport -n default -o jsonpath='{range .items[0].results[?(@.result=="fail")]}{.policy}{"\t"}{.resources[0].name}{"\t"}{.message}{"\n"}{end}'
6.3 Policy Exceptions for Specific Workloads
# PolicyException for monitoring agents that need host access
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: allow-privileged-monitoring
namespace: kyverno
spec:
exceptions:
- policyName: pss-baseline-enforce
ruleNames:
- deny-privileged-containers
match:
any:
- resources:
kinds: ["Pod"]
namespaces: ["monitoring"]
names: ["node-exporter-*"]
7. Migration Guide — From Legacy API to CEL
Kyverno 1.17 deprecates ClusterPolicy/Policy (JMESPath-based), with removal planned in v1.20 (October 2026):
| Version | Status | Recommended Action |
|---|---|---|
| v1.17 (current) | ClusterPolicy deprecated, CEL GA | Write new policies in CEL, inventory existing |
| v1.18 | Warning logs on legacy API usage | Begin migrating high-priority policies |
| v1.19 | Last version supporting legacy APIs | Complete migration verification |
| v1.20 (Oct 2026) | ClusterPolicy/Policy removed | CEL-only operation |
8. Kyverno + GitOps with ArgoCD
Manage Kyverno policies as Git-versioned resources deployed via ArgoCD:
# ArgoCD Application for Kyverno policies
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kyverno-policies
namespace: argocd
spec:
project: platform
source:
repoURL: https://github.com/your-org/k8s-policies.git
targetRevision: main
path: kyverno/policies
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
Policy changes follow the same PR workflow as application code: review, merge, auto-deploy. Running Kyverno CLI tests at the PR stage prevents broken policies from reaching your clusters.
9. Conclusion
Kyverno's CNCF graduation signals that Policy-as-Code has matured into a first-class pillar of the Kubernetes ecosystem. CEL-based expression unification, YAML's low entry barrier, and a single engine handling Validate, Mutate, Generate, and VerifyImages deliver real value to both platform and security teams.
Three areas to watch: AI/MCP gateway policy extension as Kyverno prepares to govern AI workloads and Model Context Protocol gateways; full CEL migration with legacy API removal in v1.20 (October 2026); and multi-cluster governance for consistent policy enforcement at scale.
If you are already running Kubernetes, start by installing Kyverno in Audit mode to assess your current compliance posture. You may be surprised by the gaps you find.
Originally published at ManoIT Tech Blog.

Top comments (0)