CKS — Kyverno Alternative

8 minute read

Kyverno Alternative

CKS Exam Domain: Minimize Microservice Vulnerabilities (20%)

Kyverno is a Kubernetes-native policy engine that uses YAML instead of Rego for policy definitions. While OPA Gatekeeper is the primary focus for the CKS exam, Kyverno is a CNCF Incubating project that is increasingly adopted and may appear in exam scenarios.


What Is Kyverno?

Kyverno (Greek for “govern”) is a policy engine designed exclusively for Kubernetes. Unlike OPA, which is a general-purpose engine requiring Rego, Kyverno policies are written in familiar Kubernetes YAML.

Key differentiators:

  • No new language — policies are YAML manifests.
  • Three policy types — validate, mutate, and generate.
  • Kubernetes-native — works with kubectl, kustomize, Helm.
  • No external dependencies — single controller deployment.

Installing Kyverno

# Method 1: kubectl apply
kubectl apply -f https://github.com/kyverno/kyverno/releases/download/v1.11.0/install.yaml

# Method 2: Helm
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace

# Verify
kubectl get pods -n kyverno

Expected output:

NAME                                            READY   STATUS    RESTARTS   AGE
kyverno-admission-controller-6f8b5f6d7c-x2j4k  1/1     Running   0          30s
kyverno-background-controller-d4c7b8f5d-abc12   1/1     Running   0          30s
kyverno-cleanup-controller-5b8c9d4e3f-def34     1/1     Running   0          30s
kyverno-reports-controller-7a9e2f1b8c-ghi56     1/1     Running   0          30s

ClusterPolicy Resource Structure

Kyverno uses ClusterPolicy (cluster-wide) and Policy (namespace-scoped) resources:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: <policy-name>
  annotations:
    policies.kyverno.io/title: "Policy Title"
    policies.kyverno.io/category: "Security"
    policies.kyverno.io/severity: "high"
spec:
  validationFailureAction: Enforce   # Enforce | Audit
  background: true                    # Scan existing resources
  rules:
  - name: <rule-name>
    match:
      any:
      - resources:
          kinds:
          - Pod
          namespaces:
          - default
          - production
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
    validate:                         # or mutate: or generate:
      message: "Human-readable error message"
      pattern:
        # Expected resource structure

Key Fields

Field Purpose
validationFailureAction Enforce (deny) or Audit (report only)
background Whether to scan existing resources
match Which resources the rule applies to
exclude Which resources to skip
validate Validation rules (block non-compliant)
mutate Mutation rules (modify resources)
generate Generation rules (create new resources)

Validate Rules

Example 1: Require Labels

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
  annotations:
    policies.kyverno.io/title: "Require Labels"
    policies.kyverno.io/severity: "medium"
spec:
  validationFailureAction: Enforce
  background: true
  rules:
  - name: require-app-label
    match:
      any:
      - resources:
          kinds:
          - Pod
          - Deployment
          - StatefulSet
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
          - kyverno
    validate:
      message: "The label 'app' is required on all resources."
      pattern:
        metadata:
          labels:
            app: "?*"     # ?* means "any non-empty value"

Test:

# Denied — no app label
kubectl run test --image=nginx

# Allowed
kubectl run test --image=nginx --labels="app=web"

Example 2: Block Privileged Containers

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: block-privileged
  annotations:
    policies.kyverno.io/title: "Block Privileged Containers"
    policies.kyverno.io/severity: "high"
spec:
  validationFailureAction: Enforce
  background: true
  rules:
  - name: block-privileged-containers
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Privileged containers are not allowed."
      pattern:
        spec:
          containers:
          - securityContext:
              privileged: "!true"   # !true means "not true"
  - name: block-privileged-init-containers
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Privileged init containers are not allowed."
      pattern:
        spec:
          =(initContainers):       # =() means "if field exists"
          - securityContext:
              privileged: "!true"

Example 3: Enforce Allowed Registries

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: allowed-registries
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-registries
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images must come from allowed registries: gcr.io/company/ or registry.k8s.io/"
      pattern:
        spec:
          containers:
          - image: "gcr.io/company/* | registry.k8s.io/*"

Example 4: Require Resource Limits

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-limits
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "CPU and memory limits are required for all containers."
      pattern:
        spec:
          containers:
          - resources:
              limits:
                memory: "?*"
                cpu: "?*"

Kyverno Pattern Operators

Operator Meaning Example
?* Any non-empty value app: "?*" (label must exist and be non-empty)
!value Not equal to value privileged: "!true"
* Wildcard image: "gcr.io/*"
\| OR image: "gcr.io/* \| docker.io/*"
>N Greater than replicas: ">1"
<N Less than replicas: "<10"
>=N Greater than or equal replicas: ">=2"
=() Conditional (if exists) =(initContainers): ...

Mutate Rules

Kyverno can modify resources during admission — something Gatekeeper handles less elegantly.

Example: Inject Default Labels

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
spec:
  rules:
  - name: add-team-label
    match:
      any:
      - resources:
          kinds:
          - Pod
          - Deployment
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            +(team): "platform"    # + means "add if not present"

The + prefix means: add the label only if it doesn’t already exist. Existing labels are preserved.

Example: Inject Sidecar Container

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: inject-logging-sidecar
spec:
  rules:
  - name: inject-sidecar
    match:
      any:
      - resources:
          kinds:
          - Pod
        selector:
          matchLabels:
            inject-sidecar: "true"
    mutate:
      patchStrategicMerge:
        spec:
          containers:
          - name: log-collector
            image: gcr.io/company/log-collector:v1.2
            resources:
              limits:
                cpu: "100m"
                memory: "64Mi"

Example: Set Default Security Context

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: default-security-context
spec:
  rules:
  - name: set-run-as-non-root
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        spec:
          securityContext:
            +(runAsNonRoot): true
            +(seccompProfile):
              type: RuntimeDefault

Generate Rules

Kyverno can create new resources automatically when a triggering resource is created.

Example: Auto-Create NetworkPolicy per Namespace

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

When a new namespace is created, Kyverno automatically generates a default-deny-ingress NetworkPolicy in that namespace. synchronize: true keeps the generated resource in sync — if someone deletes it, Kyverno recreates it.

Example: Auto-Create ResourceQuota

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-resource-quota
spec:
  rules:
  - name: default-quota
    match:
      any:
      - resources:
          kinds:
          - Namespace
    generate:
      synchronize: true
      apiVersion: v1
      kind: ResourceQuota
      name: default-quota
      namespace: "{{request.object.metadata.name}}"
      data:
        spec:
          hard:
            pods: "20"
            requests.cpu: "4"
            requests.memory: "8Gi"
            limits.cpu: "8"
            limits.memory: "16Gi"

Kyverno vs Gatekeeper — Detailed Comparison

Feature OPA Gatekeeper Kyverno
Policy language Rego YAML
Learning curve Steep (new language) Low (familiar YAML)
Validation Yes (ConstraintTemplate + Constraint) Yes (ClusterPolicy validate)
Mutation Limited (Assign/AssignMetadata CRDs) Full (patchStrategicMerge, patchesJson6902)
Generation No Yes (auto-create resources)
Policy model Two objects (Template + Constraint) Single object (ClusterPolicy)
Audit/reporting Built-in audit controller PolicyReport CRDs
CNCF status Graduated (OPA) Incubating
Resource overhead Moderate (3+ pods) Moderate (4 pods)
CLI testing opa eval, opa test, gator kyverno apply, kyverno test
Image verification External (via Sigstore integration) Built-in (cosign, notary)
Variable substitution Not native Built-in ({{request.object...}})
Exception handling Constraint excludedNamespaces PolicyException resource
CKS exam focus Primary Emerging

When to Use Which

Scenario Recommendation
CKS exam preparation OPA Gatekeeper (primary), Kyverno (awareness)
Greenfield Kubernetes project Kyverno (simpler, more features)
Complex multi-system policies OPA (reusable across non-K8s systems)
Team unfamiliar with Rego Kyverno
Mutation-heavy requirements Kyverno
Auto-generation of resources Kyverno (exclusive feature)

When CKS May Reference Kyverno

The CKS exam curriculum mentions policy engines broadly. While OPA Gatekeeper is the established tool:

  • Kyverno may appear as a pre-installed component you need to work with.
  • Questions might ask you to compare policy approaches.
  • You may encounter Kyverno ClusterPolicy resources to read and understand.
  • Mutation and generation questions are natural Kyverno territory.

Exam strategy: Be comfortable reading Kyverno YAML policies. Know the three rule types (validate, mutate, generate) and the validationFailureAction setting.


Kyverno Policy Reports

Kyverno generates PolicyReport and ClusterPolicyReport resources for visibility:

# List policy reports (namespace-scoped)
kubectl get policyreport -A

# List cluster policy reports
kubectl get clusterpolicyreport

# Describe a report
kubectl describe policyreport -n default

Sample report:

apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
  name: polr-ns-default
  namespace: default
results:
- message: "validation rule 'require-app-label' passed."
  policy: require-labels
  result: pass
  rule: require-app-label
  source: kyverno
- message: "The label 'app' is required on all resources."
  policy: require-labels
  result: fail
  rule: require-app-label
  source: kyverno
  resources:
  - apiVersion: v1
    kind: Pod
    name: legacy-pod
    namespace: default
summary:
  pass: 15
  fail: 1
  warn: 0
  error: 0
  skip: 0

Useful Kyverno Commands

# List all cluster policies
kubectl get clusterpolicy

# Describe a specific policy
kubectl describe clusterpolicy require-labels

# Check policy status
kubectl get clusterpolicy require-labels -o jsonpath='{.status}'

# List policy reports
kubectl get policyreport -A

# Test a policy locally (Kyverno CLI)
kyverno apply policy.yaml --resource pod.yaml

# Test with JUnit output
kyverno test . --detailed-results

Practice Questions

Question 1

Write a Kyverno ClusterPolicy that requires all Pods to have a team label.

Show Answer
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-team-label
spec:
  validationFailureAction: Enforce
  background: true
  rules:
  - name: require-team-label
    match:
      any:
      - resources:
          kinds:
          - Pod
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
          - kyverno
    validate:
      message: "The label 'team' is required."
      pattern:
        metadata:
          labels:
            team: "?*"

Question 2

What is the difference between validationFailureAction: Enforce and validationFailureAction: Audit?

Show Answer
Action Behaviour
Enforce Blocks the admission request if the resource violates the policy. Equivalent to Gatekeeper’s deny.
Audit Allows the request but records the violation in a PolicyReport resource. Equivalent to Gatekeeper’s dryrun.

Use Audit to test policies before enforcement. Switch to Enforce for production.

# Testing phase
spec:
  validationFailureAction: Audit

# Production phase
spec:
  validationFailureAction: Enforce

Question 3

Write a Kyverno mutate rule that adds a managed-by: kyverno label to all Pods that don’t already have it.

Show Answer
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-managed-by-label
spec:
  rules:
  - name: add-managed-by
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            +(managed-by): "kyverno"

The + prefix ensures the label is only added if it doesn’t already exist. If a Pod already has a managed-by label, it is preserved.


Question 4

Write a Kyverno generate rule that creates a default LimitRange in every new namespace.

Show Answer
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-default-limitrange
spec:
  rules:
  - name: default-limitrange
    match:
      any:
      - resources:
          kinds:
          - Namespace
    exclude:
      any:
      - resources:
          namespaces:
          - kube-system
          - kube-public
          - kyverno
    generate:
      synchronize: true
      apiVersion: v1
      kind: LimitRange
      name: default-limits
      namespace: "{{request.object.metadata.name}}"
      data:
        spec:
          limits:
          - type: Container
            default:
              cpu: "500m"
              memory: "256Mi"
            defaultRequest:
              cpu: "100m"
              memory: "128Mi"

synchronize: true means if someone deletes the LimitRange, Kyverno recreates it.


Question 5

Name three capabilities that Kyverno has that OPA Gatekeeper does not natively support.

Show Answer
  1. Generate rules — Kyverno can automatically create new Kubernetes resources (NetworkPolicies, ResourceQuotas, LimitRanges) when a triggering resource is created. Gatekeeper has no generation capability.

  2. Built-in image verification — Kyverno natively integrates with cosign and Notary to verify image signatures and attestations. Gatekeeper requires external tooling.

  3. Variable substitution — Kyverno supports {{request.object.metadata.name}} style variables in policy definitions, making policies more dynamic. Gatekeeper relies on Rego for dynamic values.

Additional differences:

  • Kyverno uses a single ClusterPolicy resource vs Gatekeeper’s two-resource model (ConstraintTemplate + Constraint).
  • Kyverno’s mutation support is more comprehensive with patchStrategicMerge and patchesJson6902.

Previous Next
← Rego Policies OPA Gatekeeper Lab →

Updated: