The Problem
Cluster governance tends to live in people's heads: "don't use the latest tag," "always set resource limits," "no privileged pods." These rules get written in a wiki and ignored. Kyverno turns them into enforceable Kubernetes policies — written in YAML, no Rego to learn — that run at admission time. The missing piece is delivery: how do policies get onto every cluster and stay there? That is where ArgoCD comes in, syncing policies from Git and self-healing them if anyone tampers with them.
The result is governance that is reviewed in pull requests, versioned in Git, enforced at admission, and automatically restored if removed.
Why Kyverno
Kyverno is a Kubernetes-native policy engine. Its policies are plain Kubernetes resources, so the same kubectl, RBAC, and GitOps tooling you already use applies to them. There are three policy actions:
| Action | What it does |
|---|---|
| <strong>validate</strong> | Allow or deny a request based on rules (e.g. require resource limits) |
| <strong>mutate</strong> | Modify a request before it is stored (e.g. inject default labels) |
| <strong>generate</strong> | Create related resources automatically (e.g. a default NetworkPolicy per namespace) |
Because there is no Rego, platform engineers who already read Kubernetes YAML can author policies on day one.
A Validation Policy — Disallow the <code class="inline-code">latest</code> Tag
The classic first policy: reject any container image using the mutable :latest tag.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-image-tag
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Using the 'latest' tag or no tag is not allowed."
pattern:
spec:
containers:
- image: "!:latest & :*"
A Validation Policy — Require Resource Limits
A second common rule: every container must declare CPU and memory limits so one workload cannot starve a node.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Enforce
rules:
- name: validate-limits
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "CPU and memory limits are required."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
Enforce vs Audit
The validationFailureAction field decides how strict a policy is:
Enforce— non-compliant requests are rejected at admission. Use this for hard rules in production.Audit— non-compliant requests are allowed but a PolicyReport records the violation. Use this to roll out a new policy without breaking existing workloads.
A safe rollout is: ship a policy as Audit, watch the reports for a week, then flip it to Enforce in a reviewed pull request.
kubectl get clusterpolicy
kubectl get policyreport -A
kubectl describe clusterpolicy disallow-latest-tag
Delivering Policies with ArgoCD
Store all policies in a Git folder and let ArgoCD own it. Because policies are just manifests, the Application looks like any other:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kyverno-policies
namespace: argocd
spec:
project: platform
source:
repoURL: https://github.com/devopskit/policies.git
targetRevision: main
path: kyverno
destination:
server: https://kubernetes.default.svc
namespace: kyverno
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- ServerSideApply=true
selfHeal: true means if someone deletes a policy to sneak a non-compliant workload through, ArgoCD puts it right back. Governance becomes tamper-resistant.
Ordering with Sync Waves
Kyverno (the controller) must exist before its policies. Use ArgoCD sync waves so the engine installs first:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # policies apply after wave 0 (Kyverno)
Common Mistakes to Avoid
- Shipping new policies straight as
Enforce. You will block existing, non-compliant workloads and cause an outage. Start withAudit. - No sync-wave ordering. If ArgoCD applies policies before the Kyverno controller is ready, the webhook calls fail. Install the engine in an earlier wave.
- Letting the webhook fail-closed without a plan. If Kyverno is down and the webhook fail policy is strict, admission for the whole cluster can stall. Scope policies and set
failurePolicydeliberately. - Policies that match everything. Over-broad
matchblocks can hit kube-system and break the cluster. Scope by namespace or labels. - Editing policies with
kubectlon the cluster. WithselfHealon, ArgoCD reverts you. Change policies in Git, through review.
FAQ
How is Kyverno different from OPA/Gatekeeper?
Kyverno policies are written in native Kubernetes YAML, whereas Gatekeeper uses the Rego language via OPA. For teams already fluent in Kubernetes manifests, Kyverno has a gentler learning curve, and it also supports mutate and generate actions, not just validation.
What is the difference between Enforce and Audit?
Enforce rejects non-compliant requests at admission, stopping the resource from being created. Audit allows the request but records the violation in a PolicyReport, which is ideal for testing a policy's impact before turning it on fully.
Why deliver Kyverno policies through ArgoCD instead of applying them directly?
GitOps delivery makes every policy change a reviewed, versioned pull request and gives you self-healing: if a policy is deleted or edited on the cluster, ArgoCD restores it from Git. That turns governance into something auditable and tamper-resistant rather than ad hoc.
Can Kyverno fix non-compliant resources automatically?
Yes, through mutate policies. Instead of rejecting a request, a mutate rule can inject sensible defaults — for example adding standard labels or a default security context — before the resource is stored, which reduces friction for application teams.