Rules that exist only in documentation don’t get followed. Rules enforced by computers do.
Kubernetes gives you incredible flexibility. Every team can deploy whatever they want, configured however they like. This freedom becomes chaos without guardrails.
Kyverno is a policy engine for Kubernetes. It validates, mutates, and generates resources based on policies you define — as Kubernetes-native YAML.
Why Kyverno?
There are multiple policy engines: Open Policy Agent (OPA) with Gatekeeper, Kyverno, Kubewarden. I chose Kyverno because:
- Native YAML — No Rego, no new language to learn
- Validation + Mutation + Generation — One tool for all policy types
- Declarative — Policies are Kubernetes resources
- GitOps-friendly — Manage policies like any other manifest
OPA/Gatekeeper is powerful but requires learning Rego. Kyverno lets you start writing policies immediately if you know YAML.
Core Concepts
Kyverno policies can do three things:
Validate — Block resources that don’t meet criteria
"You can't deploy without resource limits"
Mutate — Automatically modify resources
"All pods get this label automatically"
Generate — Create new resources when others are created
"Every namespace gets a default NetworkPolicy"
Installing Kyverno
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno \
--namespace kyverno \
--create-namespace
For GitOps with ArgoCD:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kyverno
namespace: argocd
spec:
project: default
source:
repoURL: https://kyverno.github.io/kyverno/
chart: kyverno
targetRevision: 3.1.4
helm:
values: |
admissionController:
replicas: 3
backgroundController:
replicas: 2
cleanupController:
replicas: 2
reportsController:
replicas: 2
destination:
server: https://kubernetes.default.svc
namespace: kyverno
syncPolicy:
automated:
prune: true
selfHeal: true
Your First Policy: Require Labels
A classic starting policy — require all pods to have specific labels:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: require-team-label
match:
any:
- resources:
kinds:
- Pod
validate:
message: "The label 'team' is required."
pattern:
metadata:
labels:
team: "?*"
Now try creating a pod without the label:
kubectl run test --image=nginx
# Error: The label 'team' is required.
Validation Failure Actions
Two modes:
Enforce — Block non-compliant resources (production)
validationFailureAction: Enforce
Audit — Allow but report non-compliance (testing)
validationFailureAction: Audit
Start with Audit to see what would fail, then switch to Enforce.
Common Validation Policies
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."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
Disallow Privileged Containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
spec:
validationFailureAction: Enforce
rules:
- name: deny-privileged
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed."
pattern:
spec:
containers:
- securityContext:
privileged: false
Disallow Latest Tag
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using ':latest' tag is not allowed. Specify a version tag."
pattern:
spec:
containers:
- image: "!*:latest"
Restrict Image Registries
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-registries
spec:
validationFailureAction: Enforce
rules:
- name: allowed-registries
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must come from approved registries."
pattern:
spec:
containers:
- image: "registry.example.com/* | gcr.io/my-project/*"
Mutation Policies
Automatically modify resources to enforce standards:
Add Default Labels
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-labels
spec:
rules:
- name: add-managed-by
match:
any:
- resources:
kinds:
- Pod
- Deployment
- Service
mutate:
patchStrategicMerge:
metadata:
labels:
managed-by: kyverno
environment: "{{request.namespace}}"
Add Default Security Context
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-security-context
spec:
rules:
- name: add-run-as-nonroot
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
Add Image Pull Secrets
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-imagepullsecret
spec:
rules:
- name: add-registry-secret
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
imagePullSecrets:
- name: registry-credentials
Generation Policies
Create resources automatically when others are created:
Default NetworkPolicy per Namespace
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-networkpolicy
spec:
rules:
- name: generate-deny-all
match:
any:
- resources:
kinds:
- Namespace
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny
namespace: "{{request.object.metadata.name}}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Every new namespace gets a default-deny NetworkPolicy. Teams must explicitly allow traffic.
Default ResourceQuota per Namespace
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-resourcequota
spec:
rules:
- name: generate-quota
match:
any:
- resources:
kinds:
- Namespace
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
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
persistentvolumeclaims: "10"
Using Variables
Kyverno supports JMESPath expressions for dynamic values:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-namespace-label
spec:
rules:
- name: add-ns-to-pods
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
metadata:
labels:
namespace: "{{request.namespace}}"
created-at: "{{time_now_utc()}}"
Useful variables:
{{request.namespace}}— Namespace of the resource{{request.object.metadata.name}}— Name of the resource{{request.userInfo.username}}— User who made the request{{time_now_utc()}}— Current timestamp
Excluding Resources
Don’t apply policies to system components:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
rules:
- name: require-team-label
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kube-public
- kyverno
- subjects:
- kind: ServiceAccount
name: system:*
Policy Reports
Kyverno generates policy reports showing compliance:
kubectl get policyreport -A
kubectl get clusterpolicyreport
Example report:
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
name: polr-ns-default
namespace: default
results:
- message: "validation rule 'require-team-label' passed."
policy: require-labels
result: pass
source: kyverno
- message: "validation rule 'require-limits' failed."
policy: require-resource-limits
result: fail
source: kyverno
Integrate with Prometheus for alerting on policy violations:
# ServiceMonitor for Kyverno metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kyverno
spec:
selector:
matchLabels:
app.kubernetes.io/name: kyverno
endpoints:
- port: metrics
Policy Sets
Kyverno maintains curated policy sets:
# Install Pod Security Standards (Baseline)
kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/pod-security/baseline/
# Install Pod Security Standards (Restricted)
kubectl apply -f https://raw.githubusercontent.com/kyverno/policies/main/pod-security/restricted/
These implement Kubernetes Pod Security Standards as Kyverno policies.
My Production Policy Set
Here are the policies I run on every cluster:
# require-probes.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-probes
spec:
validationFailureAction: Enforce
rules:
- name: require-readiness-probe
match:
any:
- resources:
kinds:
- Deployment
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
validate:
message: "Readiness probe is required for all deployments."
pattern:
spec:
template:
spec:
containers:
- readinessProbe:
"?*": "?*"
---
# disallow-default-namespace.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-default-namespace
spec:
validationFailureAction: Enforce
rules:
- name: deny-default
match:
any:
- resources:
kinds:
- Pod
- Deployment
- Service
namespaces:
- default
validate:
message: "Using the 'default' namespace is not allowed."
deny: {}
---
# require-requests.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-requests
spec:
validationFailureAction: Enforce
rules:
- name: require-cpu-memory-requests
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
validate:
message: "CPU and memory requests are required."
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
Testing Policies
Use the Kyverno CLI to test policies before applying:
# Install CLI
brew install kyverno
# Test policy against a resource
kyverno apply policy.yaml --resource pod.yaml
# Test all policies in a directory
kyverno apply ./policies/ --resource ./manifests/
Create a test suite:
# test/values.yaml
policies:
- policy.yaml
resources:
- resources/valid-pod.yaml
- resources/invalid-pod.yaml
results:
- policy: require-labels
resource: valid-pod.yaml
result: pass
- policy: require-labels
resource: invalid-pod.yaml
result: fail
Run tests:
kyverno test ./test/
Background Scanning
Kyverno can scan existing resources, not just new ones:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: audit-existing
spec:
validationFailureAction: Audit
background: true # Enable background scanning
rules:
- name: check-labels
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Label 'team' is missing."
pattern:
metadata:
labels:
team: "?*"
This generates reports for all existing pods, not just new ones.
Exceptions
Sometimes you need to bypass policies:
apiVersion: kyverno.io/v1
kind: PolicyException
metadata:
name: allow-privileged-monitoring
namespace: monitoring
spec:
exceptions:
- policyName: disallow-privileged
ruleNames:
- deny-privileged
match:
any:
- resources:
kinds:
- Pod
namespaces:
- monitoring
names:
- node-exporter*
Document why each exception exists. Exceptions should be rare.
Integration with CI/CD
Validate manifests in your pipeline before they reach the cluster:
# .gitlab-ci.yml
validate-policies:
stage: test
image: ghcr.io/kyverno/kyverno-cli:latest
script:
- kyverno apply ./policies/ --resource ./k8s/
rules:
- if: $CI_MERGE_REQUEST_ID
Catch policy violations before merge, not during deployment.
Why This Matters
Policies are documentation that enforces itself.
Without policy engines:
- Standards exist in wikis nobody reads
- Reviews catch issues sometimes, inconsistently
- Non-compliance accumulates silently
With Kyverno:
- Standards are code, versioned in Git
- Enforcement is automatic, immediate
- Violations are visible in reports
This is low-friction governance. Not because it’s less strict, but because compliance happens automatically. Developers don’t need to remember every rule — the system remembers for them.
The best policies are the ones you never have to think about. Kyverno turns your Kubernetes standards into self-enforcing code.
