DEV Community

daniel jeong
daniel jeong

Posted on • Originally published at manoit.co.kr

Kyverno Graduates from CNCF: A Production Guide to Policy-as-Code for Kubernetes Governance

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."
Enter fullscreen mode Exit fullscreen mode

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."
Enter fullscreen mode Exit fullscreen mode
# 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."
Enter fullscreen mode Exit fullscreen mode

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"}
          ]
Enter fullscreen mode Exit fullscreen mode

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.

Kyverno Policy-as-Code architecture diagram showing Kubernetes API Server admission webhook flow through validate, mutate, generate and verify-images policies with CEL engine and compliance reporting

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Warning: Changing failurePolicy to Ignore lets resources through without policy checks when Kyverno is unresponsive. In production, keep Fail mode 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}'
Enter fullscreen mode Exit fullscreen mode

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-*"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)