apiVersion: v1
kind: Namespace
metadata:
name: team-a
labels:
team: payments
---
apiVersion: policies.kyverno.io/v1
kind: GeneratingPolicy
metadata:
name: default-deny
spec:
evaluation:
generateExisting:
enabled: true
synchronize:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["namespaces"]
variables:
- name: downstream
expression: >-
[{
"apiVersion": dyn("networking.k8s.io/v1"),
"kind": dyn("NetworkPolicy"),
"metadata": dyn({"name": "default-deny"}),
"spec": dyn({"podSelector": dyn({}), "policyTypes": dyn(["Ingress"])})
}]
generate:
- expression: generator.Apply(object.metadata.name, variables.downstream)
---
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: add-team-label
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
matchConditions:
- name: namespace-has-team
expression: namespaceObject.metadata.?labels[?'team'].hasValue()
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
Object{metadata: Object.metadata{labels: {"team": namespaceObject.metadata.labels["team"]}}}
---
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-requests
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
object.spec.containers.all(c,
c.?resources.?requests[?'cpu'].hasValue() &&
c.?resources.?requests[?'memory'].hasValue())
message: Every container needs CPU and memory requests
---
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
name: vendor-agent
namespace: team-a
spec:
policyRefs:
- name: require-requests
kind: ValidatingPolicy
matchConditions:
- name: only-the-agent
expression: object.metadata.name.startsWith('vendor-agent')
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
namespace: team-a
spec:
replicas: 2
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
containers:
- name: checkout
image: ghcr.io/acme/checkout:2.4.1
resources:
requests:
cpu: 100m
memory: 128MiKyverno graph
Kyverno CEL policies: ValidatingPolicy, MutatingPolicy, GeneratingPolicy and exceptions
How Kyverno 1.20's policy types act on one team namespace: a MutatingPolicy sets defaults, a ValidatingPolicy rejects Pods, a GeneratingPolicy adds a NetworkPolicy, a PolicyException exempts one tool and PolicyReports record the results.
Kyverno 1.20 runs policies from the policies.kyverno.io group only. Each kind does one job: MutatingPolicy changes an object on its way in, ValidatingPolicy accepts or rejects it, and GeneratingPolicy creates other objects when a trigger appears. All three select objects with matchConstraints and express their logic in CEL.
This graph follows one namespace, team-a, from the moment it is created to a Pod being admitted into it, and shows where each policy acts and what it leaves behind.
Interactive graph
Click a resource to see what it does, or follow the setup step by step.
How it works
All 9 resources and what they do
- Namespace team-av1
- The trigger for the GeneratingPolicy, and the source of the
teamlabel the MutatingPolicy copies onto Pods. - GeneratingPolicy default-denypolicies.kyverno.io/v1
- When a Namespace is created, the background controller evaluates
generateand creates whatgenerator.Applylists, here a default-deny NetworkPolicy.generateExistingcovers namespaces older than the policy, andsynchronizeputs a deleted copy back. - NetworkPolicy default-denynetworking.k8s.io/v1
- Generated by Kyverno, with labels that name the policy and the trigger. It selects every Pod and allows no ingress, so the team has to add policies for the traffic it wants.
- MutatingPolicy add-team-labelpolicies.kyverno.io/v1
- Runs in the mutating webhook, before validation. It copies the namespace's
teamlabel onto each new Pod with anApplyConfigurationpatch, and the match condition skips namespaces that have no such label. - ValidatingPolicy require-requestspolicies.kyverno.io/v1
- Runs in the validating webhook, after every mutation. Each expression must be true or the request is denied with the message.
Auditin place ofDenyonly records the result. - PolicyException vendor-agentpolicies.kyverno.io/v1
- Exempts the vendor's agent, whose chart sets no requests. It names the policy by kind and name, and its match conditions keep the exemption to Pods whose name starts with
vendor-agent. - Deployment checkoutapps/v1
- The team's workload. The policies match Pods, so the Deployment itself is accepted, and it is its ReplicaSet that sees the denial when a Pod breaks a rule.
- Pod checkout-7c9d8-…v1
- Created by the ReplicaSet. On the way in they gain
team: paymentsfrom the MutatingPolicy, then pass the ValidatingPolicy because both requests are set. - PolicyReport <pod uid>wgpolicyk8s.io/v1alpha2
- One per scanned object, written by the reports controller. It lists each policy's result as
pass,failorskip, andkubectl get policyreports -Asums them up.
Common mistakes
- Kyverno 1.20 stores
ClusterPolicyand the otherkyverno.iokinds and no longer runs them.kubectl get cpolcan still say Ready while nothing is enforced. - CEL errors on a missing field.
object.metadata.labels.teamfails on a Pod with no labels, whereobject.metadata.?labels[?'team'].orValue('')returns a default. - A policy that matches Pods doesn't reject the Deployment that would create them. Look at the ReplicaSet's events, or
kubectl describe replicaset, for the denial. - A PolicyException without
matchConditionsexempts every object the policy matches, in every namespace. - With
synchronizeon, deleting the GeneratingPolicy deletes what it generated. Turn it off first if the generated objects have to stay.
The manifests you write
Everything above that is marked "You write it", in one file. The rest comes from add-ons and controllers.
Keep going
More graphs
- Deployment, Service and Ingress: how a request reaches your Pods
- Gateway API: GatewayClass, Gateway and HTTPRoute with a canary split
- PVC, StorageClass and CSI: how a cloud disk ends up in your Pod
- RBAC: how a ServiceAccount gets its permissions
- cert-manager with Let's Encrypt: Certificate, CertificateRequest, Order and Challenge
- A private CA with cert-manager and trust-manager
- Istio traffic management: Gateway, VirtualService, DestinationRule and policies
- Flux dependencies: Git and OCI sources, Kustomizations and HelmReleases
- Argo CD: ApplicationSet, AppProject and the Applications it generates
- Inside an OCI image: tag, index, manifests, config and layers
- OCI artifacts: Helm charts, Flux artifacts and image volumes
- A Helm release: chart, values, revisions and the objects Helm owns
- Kustomize: a base, two overlays, a component and generated ConfigMaps
- A StatefulSet database: headless Service, per-Pod volumes, snapshots and restore
- Autoscaling end to end: KEDA, the HPA it creates and Karpenter adding nodes
- Prometheus Operator: ServiceMonitor, PrometheusRule and the path to an alert
- External Secrets Operator: from a cloud secret manager to a Pod, with rotation
- Cilium egress to a domain: CiliumNetworkPolicy, the DNS proxy and toFQDNs
- Istio ambient mode: ztunnel, waypoint proxies and authorization without sidecars
- Argo Rollouts canary: traffic weights on an HTTPRoute and analysis from Prometheus