CKS — Kyverno Alternative
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
validationFailureActionsetting.
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
-
Generate rules — Kyverno can automatically create new Kubernetes resources (NetworkPolicies, ResourceQuotas, LimitRanges) when a triggering resource is created. Gatekeeper has no generation capability.
-
Built-in image verification — Kyverno natively integrates with cosign and Notary to verify image signatures and attestations. Gatekeeper requires external tooling.
-
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
patchStrategicMergeandpatchesJson6902.
| Previous | Next |
|---|---|
| ← Rego Policies | OPA Gatekeeper Lab → |