The Complete Guide to Passing the Kyverno Certified Associate (KCA) Exam
Kubernetes policy management has become a critical discipline as organizations scale their clusters and enforce security, compliance, and…
The Complete Guide to Passing the Kyverno Certified Associate (KCA) Exam
Kubernetes policy management has become a critical discipline as organizations scale their clusters and enforce security, compliance, and operational standards. The Kyverno Certified Associate (KCA) exam tests whether you can design, implement, and troubleshoot policies using Kyverno — the Kubernetes-native policy engine built by Nirmata and now a CNCF incubating project.
This guide covers everything you need to pass: exam format, all major domains with annotated YAML examples, CLI usage, and a practical study strategy.
Exam at a glance:
- 60 multiple-choice questions
- 90 minutes
- Passing score: 75/100
- Entirely conceptual — no live cluster, but expects deep YAML and JMESPath fluency
Part 1: Understanding Kyverno’s Four Pillars
Before writing a single line of policy YAML, you need to internalize what Kyverno actually does. Every exam question traces back to one of these four capabilities.
1. Validation — Kyverno inspects incoming Kubernetes resources and either allows or blocks them based on rules you define. This is the most common use case — enforcing standards like required labels, disallowing privileged containers, or mandating resource limits.
2. Mutation — Kyverno intercepts resources before they reach etcd and modifies them. This is how you inject defaults — adding labels, setting security contexts, or appending sidecar containers — without requiring developers to remember to do it themselves.
3. Generation — When a resource is created, Kyverno can automatically create additional resources. The classic example is generating a default NetworkPolicy in every new namespace, or copying a ConfigMap from one namespace into another.
4. Image Verification — Kyverno integrates with Cosign and Sigstore to verify that container images have been signed by a trusted party before allowing them to run. This closes a significant supply chain security gap.
Part 2: How Kyverno Fits Into the Admission Controller Flow
Understanding where Kyverno sits in the Kubernetes request lifecycle is foundational — several exam questions hinge on this.
Client Request
↓
Authentication & Authorization
↓
Mutating Admission Webhooks ← Kyverno mutation runs here
↓
Schema Validation (OpenAPI)
↓
Validating Admission Webhooks ← Kyverno validation runs here
↓
etcd (persisted)
Key implication: mutation always runs before validation. This means a mutating policy can add a field, and a validating policy can then check that field exists — they work together.
Part 3: Policy Structure Deep Dive
ClusterPolicy vs Policy
# ClusterPolicy — enforces rules across ALL namespaces
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: my-cluster-policy
---
# Policy — enforces rules only within the namespace it is deployed in
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: my-namespaced-policy
namespace: production # only resources in 'production' are checked
The Core Policy Fields
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: annotated-policy-example
spec:
validationFailureAction: Enforce # Enforce = deny the request on violation
# Audit = allow but log to PolicyReport
failurePolicy: Fail # Fail = block admission if Kyverno webhook is unreachable
# Ignore = allow through if Kyverno is unavailable
background: true # true = scan existing resources periodically
# false = only check new/updated resources
# Must be false if the rule uses request.userInfo
rules: []
Match and Exclude Logic
rules:
- name: example-match-exclude
match:
any: # any: = OR — resource must satisfy AT LEAST ONE of these blocks
- resources:
kinds:
- Deployment
namespaces:
- production
- resources:
kinds:
- StatefulSet
exclude:
any: # any: = OR — resource matching ANY exclude block is skipped
- resources:
namespaces:
- kube-system # never touch system namespaces
- subjects:
- kind: ServiceAccount
name: system-deployer # exclude a specific service account
any: is OR logic — one match is enough. all: is AND logic — every condition must be true. This distinction comes up repeatedly in exam questions.
Part 4: Validation Policies
Pattern-Based Validation
The simplest form — describe what a valid resource looks like, and Kyverno blocks anything that doesn’t match.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
background: true
rules:
- name: check-required-labels
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
validate:
message: "Resources must have 'app', 'env', and 'team' labels."
pattern:
metadata:
labels:
app: "?*" # ?* = one or more characters (rejects empty string or missing key)
env: "?*"
team: "?*"
Deny-Based Validation with JMESPath
For conditions that can’t be expressed as a simple pattern, use deny: with JMESPath expressions.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: no-latest-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Container images must not use the 'latest' tag. Pin to a specific version."
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[?contains(image, 'latest')] | length(@) }}"
# Step by step:
# request.object.spec.containers → the array of containers in this Pod
# [?contains(image, 'latest')] → filter: keep containers whose image
# string contains 'latest'
# | length(@) → count how many matched
# If the count > 0, at least one container uses latest → deny fires
operator: GreaterThan
value: 0
- key: "{{ request.object.spec.initContainers[?contains(image, 'latest')] | length(@) }}"
# Same check for initContainers — don't forget these on the exam
operator: GreaterThan
value: 0
Disallow Privileged Containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
spec:
validationFailureAction: Enforce
rules:
- name: no-privileged
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed."
pattern:
spec:
containers:
- =(securityContext): # =(...) = field is optional; only checked if present
=(privileged): false # if privileged exists, it must be false
# absent fields are NOT a violation
Part 5: Mutation Policies
patchStrategicMerge
Works like kubectl apply — merges your block into the existing resource, leaving unrelated fields untouched.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-defaults
spec:
rules:
- name: set-resource-limits
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*" # (name): "*" = apply to every container in the list
resources:
limits:
memory: "256Mi" # added if missing; does NOT overwrite existing limits
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
patchesJson6902
For surgical edits — when you need to target a specific index, remove a field, or make a precise replacement.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: harden-security-context
spec:
rules:
- name: add-readonly-root
match:
any:
- resources:
kinds:
- Pod
mutate:
patchesJson6902: |-
- op: add # op: add | remove | replace
path: /spec/containers/0/securityContext/readOnlyRootFilesystem # JSON Pointer — /0 = first container
value: true
- op: add
path: /spec/containers/0/securityContext/allowPrivilegeEscalation
value: false
- op: add
path: /spec/securityContext/runAsNonRoot # Pod-level security context
value: true
Adding Annotations Dynamically
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-audit-annotation
spec:
rules:
- name: annotate-with-user
match:
any:
- resources:
kinds:
- Deployment
mutate:
patchStrategicMerge:
metadata:
annotations:
audit.kyverno.io/created-by: "{{ request.userInfo.username }}"
# request.userInfo.username = the Kubernetes user who submitted the request
# stamps every Deployment with who created it at admission time
# NOTE: background: false is required when using request.userInfo
Part 6: Generation Policies
Generation fires when a matching resource is created and auto-produces a new resource.
Generate a NetworkPolicy in Every New Namespace
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: default-networkpolicy
spec:
rules:
- name: generate-default-deny
match:
any:
- resources:
kinds:
- Namespace # trigger: fires whenever any Namespace is created
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-ingress
namespace: "{{ request.object.metadata.name }}" # target = the newly created namespace
synchronize: true # true = Kyverno owns this resource; recreates it if deleted
# false = create once and let users manage it manually
data:
spec:
podSelector: {} # {} = selects all pods in the namespace
policyTypes:
- Ingress # deny all ingress traffic by default
Clone a ConfigMap into New Namespaces
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: clone-registry-config
spec:
rules:
- name: copy-registry-credentials
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: v1
kind: ConfigMap
name: registry-config
namespace: "{{ request.object.metadata.name }}"
synchronize: true
clone:
namespace: kyverno # source namespace where the original ConfigMap lives
name: registry-config # source ConfigMap name to clone from
Part 7: Image Verification
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
background: false # image verification requires the live admission context
rules:
- name: check-image-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "registry.example.com/*" # glob — applies to all images from this registry
required: true # true = image MUST have a valid signature
attestors:
- count: 1 # at least 1 attestor must verify successfully
entries:
- keys:
publicKeys: |- # Cosign public key (from `cosign generate-key-pair`)
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
Part 8: JMESPath Reference
JMESPath is Kyverno’s query language for extracting and filtering data from resource specs. The exam tests it heavily.
# Pattern 1 — extract a single value
key: "{{ request.object.metadata.name }}"
# Returns: the resource's name as a string
# Pattern 2 — filter an array
key: "{{ request.object.spec.containers[?contains(image, 'myregistry')] }}"
# Returns: array of containers whose image references 'myregistry'
# Pattern 3 — count filtered results
key: "{{ request.object.spec.containers[?contains(image, 'latest')] | length(@) }}"
# | length(@) pipes the filtered array into the length function
# Returns: integer count of matching containers
# Pattern 4 — null-safe fallback
key: "{{ request.object.metadata.labels.env || '' }}"
# || '' = if the left side is null/missing, return empty string
# Prevents JMESPath errors when the field doesn't exist
# Pattern 5 — extract all values from an array field
key: "{{ request.object.spec.containers[].image }}"
# [] = wildcard projection — extracts 'image' from every element
# Returns: ["nginx:1.21", "redis:6"]
# Pattern 6 — reference data from the context block
rules:
- name: lookup-example
context:
- name: allowedRegistries # assigns the API call result to this variable name
apiCall:
urlPath: "/api/v1/namespaces/policy-data/configmaps/allowed-registries"
jmesPath: "data.registries | split(@, ',')"
# post-processes the API response
# split(@, ',') turns a comma-separated string into an array
validate:
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[0].image }}"
operator: AnyNotIn
value: "{{ allowedRegistries }}" # reference the context variable by name
Part 9: PolicyReport
Kyverno auto-generates PolicyReport (namespace-scoped) and ClusterPolicyReport (cluster-scoped) resources when policies run in Audit mode. You don't create these — you read them.
# Auto-generated by Kyverno — understand the structure, not how to write it
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
name: polr-ns-default
namespace: default # one PolicyReport per namespace
summary:
pass: 47 # resources that satisfied all policies
fail: 3 # resources that violated at least one policy
warn: 0 # violations against warn-action policies
error: 0 # Kyverno encountered an error evaluating the policy
skip: 0 # policy did not apply (e.g. resource was excluded)
results:
- policy: require-labels # which ClusterPolicy triggered this result
rule: check-required-labels # which rule within that policy
resources:
- apiVersion: apps/v1
kind: Deployment
name: legacy-app
namespace: default
result: fail # pass | fail | warn | error | skip
message: "Resources must have 'app', 'env', and 'team' labels."
scored: true # true = counts toward pass/fail summary totals
severity: medium # info | low | medium | high | critical
Part 10: PolicyException
When you need to exempt a specific resource from a policy without modifying the policy itself.
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: legacy-app-exception
namespace: legacy # PolicyException is namespace-scoped
spec:
exceptions:
- policyName: disallow-latest-tag # exact name of the ClusterPolicy to exempt from
ruleNames:
- no-latest-tag # exempt only this rule, not the entire policy
match:
any:
- resources:
kinds:
- Pod
namespaces:
- legacy # exception only applies to Pods in the 'legacy' namespace
names:
- legacy-app-* # glob — only Pods named 'legacy-app-something'
Part 11: ClusterCleanupPolicy
Background scans audit but never delete. For automated cleanup, use ClusterCleanupPolicy.
apiVersion: kyverno.io/v2alpha1
kind: ClusterCleanupPolicy
metadata:
name: remove-completed-jobs
spec:
match:
any:
- resources:
kinds:
- Job
selector:
matchLabels:
cleanup: "enabled" # only Jobs explicitly opted in to cleanup
conditions:
any:
- key: "{{ status.conditions[?type=='Complete'] | length(@) }}"
# filter status.conditions for entries where type == 'Complete'
# count > 0 means the Job finished successfully
operator: GreaterThan
value: 0
- key: "{{ time_since('', metadata.creationTimestamp, '') }}"
# time_since returns elapsed duration since the timestamp
operator: GreaterThan
value: "1h" # only delete Jobs older than 1 hour
schedule: "0 * * * *" # cron — runs at the top of every hour
Part 12: Auto-Generated Rules
When you write a policy targeting Pod, Kyverno automatically generates equivalent rules for higher-level controllers (Deployment, StatefulSet, DaemonSet, Job, CronJob) that produce Pods.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-memory-limits
annotations:
pod-policies.kyverno.io/autogen-controllers: "Deployment,StatefulSet,DaemonSet"
# Controls which controllers get auto-generated rules.
# Omit = Kyverno generates for all supported controllers (default behavior).
# "none" = disable auto-gen entirely.
# Comma-separated list = target only those controllers.
spec:
rules:
- name: check-memory-limits
match:
any:
- resources:
kinds:
- Pod # write for Pod; Kyverno clones this rule for listed controllers
validate:
message: "Memory limits must be set."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
Part 13: Default Ports and TLS Defaults
These constants appear on the exam. Memorize them.
Default Ports:
PortPurpose9443Admission webhook traffic — most critical8000Prometheus metrics scrape endpoint8080Liveness and readiness probes6060pprof profiling (disabled by default)
TLS Certificate Defaults:
SettingDefault ValueCA certificate validity365 daysTLS certificate validity150 daysCertificate renewal check interval12 hours
Part 14: CLI Usage
The kyverno CLI lets you test policies locally without a cluster. Know the key commands and their flags.
# Apply a policy against a resource manifest (no cluster required)
kyverno apply policy.yaml --resource pod.yaml
# Apply multiple policies against multiple resources
kyverno apply ./policies/ --resource ./manifests/
# Show detailed output including passing results
kyverno apply policy.yaml --resource pod.yaml --detailed-results
# Run a structured test suite (uses kyverno-test.yaml descriptor)
kyverno test ./tests/
# Run tests with verbose output
kyverno test ./tests/ --detailed-results
# Test a JMESPath expression against a JSON file
kyverno jp query -i pod.json 'spec.containers[].image'
# Validate policy YAML syntax (catches schema errors before applying)
kyverno validate policy.yaml
# Validate all policies in a directory
kyverno validate ./policies/
kyverno-test.yaml Structure
# kyverno-test.yaml — lives in the test directory alongside policy and resource files
name: disallow-latest-tag-tests
policies:
- ../policies/disallow-latest-tag.yaml # relative path to the policy under test
resources:
- resources/pod-with-latest.yaml # resource that SHOULD be blocked
- resources/pod-with-version.yaml # resource that SHOULD pass
results:
- policy: disallow-latest-tag
rule: no-latest-tag
resource: pod-with-latest # matches metadata.name in the resource file
result: fail # expected outcome: pass | fail | skip
- policy: disallow-latest-tag
rule: no-latest-tag
resource: pod-with-version
result: pass
Part 15: CEL Support
Kyverno supports Common Expression Language (CEL) as an alternative to JMESPath for writing rule conditions.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: cel-example
spec:
validationFailureAction: Enforce
rules:
- name: check-replicas-cel
match:
any:
- resources:
kinds:
- Deployment
validate:
cel:
expressions:
- expression: "object.spec.replicas <= 10"
# object = the incoming resource being admitted
# Returns true = valid; false = violation
message: "Replicas must not exceed 10."
- expression: "has(object.metadata.labels) && 'app' in object.metadata.labels"
# has() = checks if a field exists (CEL equivalent of ?* in patterns)
# 'app' in object.metadata.labels = key membership check
message: "An 'app' label is required."
Part 16: Study Strategy and Exam Tips
What to Focus On
High weight — spend most of your time here:
- JMESPath filtering, counting, null handling, piping operators
validationFailureActionandfailurePolicydifferencesbackgroundfield behavior and when to disable itpatchStrategicMergevspatchesJson6902— when to use eachsynchronizein generate rulesany:vsall:match logic- Auto-gen rule behavior and the controlling annotation
Medium weight:
- PolicyReport structure and result fields
- PolicyException syntax and scope
- Default ports (9443 especially)
- TLS TTL defaults
- CLI command flags
Don’t neglect:
ClusterCleanupPolicy— newer CRD, likely appears- CEL expressions — basic syntax
context:block for external data lookups=(optional): valuepattern operator
Practical Study Plan
Week 1 — Build policies by hand. Write validation, mutation, and generation policies against a local kind cluster. Don’t copy-paste — type every field until the structure is automatic.
Week 2 — Master JMESPath. Use kyverno jp query against real resource JSON. Practice every pattern in the table above until they're second nature.
Week 3 — Mock exams. Take all available practice tests. For every wrong answer, find the exact Kyverno docs section that covers it and re-read it.
Day before — Review constants. Ports, TLS TTLs, background scan behavior, auto-gen annotation. These are free points if memorized.
On Exam Day
- Read every question twice — the difference between
EnforceandAudit, orFailandIgnore, is often the entire answer - For JMESPath questions, trace the expression step by step: what does the filter return, what does the pipe do, what is the final type and value
- When two answers look identical, check the
failurePolicyorbackgroundfield — that's usually where the trap is
Summary
Kyverno’s power comes from composing these primitives: match the right resources, validate or mutate them with patterns or JMESPath, generate companion resources automatically, and verify image provenance. The KCA exam tests whether you can read and write this fluently — not just describe it abstractly.
The candidates who pass are the ones who have typed enough YAML that the structure is muscle memory. Build policies, break them intentionally, fix them, and test them with the CLI. That hands-on foundation is exactly what the exam is designed to surface.
메타데이터
- post_id
- ff7c12a30fc6
- slug
- the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
- url
- https://medium.com/@rahul.tolearn/the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
- canonical_url
- https://medium.com/@rahul.tolearn/the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
- author_url
- https://medium.com/@rahul.tolearn
- status
- ok
- fetched_at
- 2026-06-26 03:39:16