Migrating from Traditional Policies to CEL
Migrating from Traditional Policies to CEL-based ValidatingPolicy
This guide helps you migrate from traditional Kyverno ClusterPolicy/Policy resources using validate.pattern or validate.deny to the new CEL-based ValidatingPolicy introduced in Kyverno v1.14.
Why Migrate to CEL-based Policies?
CEL-based ValidatingPolicy offers significant advantages over traditional policies:
- Performance: 25% average latency improvement and up to 80% CPU reduction
- Kubernetes Native: Can generate native ValidatingAdmissionPolicies automatically
- Expressiveness: More powerful and flexible validation logic
- Future-Ready: CEL is the strategic direction for Kubernetes policy validation
Understanding the Differences
Traditional ClusterPolicy Structure
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: require-labels
5spec:
6 validationFailureAction: Enforce
7 rules:
8 - name: check-labels
9 match:
10 any:
11 - resources:
12 kinds:
13 - Pod
14 validate:
15 message: "Required labels missing"
16 pattern:
17 metadata:
18 labels:
19 app: "?*"
20 version: "?*"
New ValidatingPolicy Structure
1apiVersion: kyverno.io/v1alpha1
2kind: ValidatingPolicy
3metadata:
4 name: require-labels
5spec:
6 validationActions: [Enforce]
7 rules:
8 - name: check-labels
9 match:
10 any:
11 - resources:
12 kinds:
13 - Pod
14 validate:
15 cel:
16 expressions:
17 - expression: "has(object.metadata.labels.app) && has(object.metadata.labels.version)"
18 message: "Required labels 'app' and 'version' are missing"
Step-by-Step Migration Process
Step 1: Identify Migration Candidates
Start with policies that use:
- Simple
validate.patternrules - Basic
validate.denyconditions - Resource field validation
- Label/annotation requirements
Not suitable for immediate migration:
- Policies using
mutate,generate, orverifyImagesrules - Complex JMESPath expressions requiring external data
- Policies requiring Kyverno-specific features like policy exceptions
Step 2: Convert Validation Logic
Pattern-based to CEL Conversion
Traditional Pattern:
1validate:
2 pattern:
3 spec:
4 containers:
5 - name: "*"
6 image: "!*:latest"
CEL Equivalent:
1validate:
2 cel:
3 expressions:
4 - expression: "object.spec.containers.all(container, !container.image.endsWith(':latest'))"
5 message: "Container images must not use 'latest' tag"
Deny-based to CEL Conversion
Traditional Deny:
1validate:
2 deny:
3 conditions:
4 all:
5 - key: "{{ request.object.spec.replicas }}"
6 operator: GreaterThan
7 value: 10
8 message: "Replica count cannot exceed 10"
CEL Equivalent:
1validate:
2 cel:
3 expressions:
4 - expression: "object.spec.replicas <= 10"
5 message: "Replica count cannot exceed 10"
Step 3: Handle Variable Translation
Built-in Variable Mapping
| Traditional Kyverno | CEL Equivalent | Description |
|---|---|---|
{{ request.object }} | object | The resource being validated |
{{ request.oldObject }} | oldObject | Previous version (for updates) |
{{ request.operation }} | request.operation | Operation type (CREATE, UPDATE, DELETE) |
{{ serviceAccountName }} | request.userInfo.username | Service account name |
{{ request.namespace }} | object.metadata.namespace | Resource namespace |
Example Variable Migration
Traditional:
1validate:
2 deny:
3 conditions:
4 any:
5 - key: "{{ request.operation }}"
6 operator: Equals
7 value: "CREATE"
8 - key: "{{ request.object.metadata.namespace }}"
9 operator: Equals
10 value: "kube-system"
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: "!(request.operation == 'CREATE' && object.metadata.namespace == 'kube-system')"
5 message: "Cannot create resources in kube-system namespace"
Common Migration Patterns
1. Required Fields Validation
Traditional:
1validate:
2 pattern:
3 metadata:
4 labels:
5 app: "?*"
6 environment: "production|staging|development"
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: "has(object.metadata.labels.app)"
5 message: "Label 'app' is required"
6 - expression: "object.metadata.labels.environment in ['production', 'staging', 'development']"
7 message: "Label 'environment' must be one of: production, staging, development"
2. Resource Limits Validation
Traditional:
1validate:
2 pattern:
3 spec:
4 containers:
5 - name: "*"
6 resources:
7 limits:
8 memory: "?*"
9 cpu: "?*"
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: |
5 object.spec.containers.all(container,
6 has(container.resources.limits.memory) && has(container.resources.limits.cpu)
7 )
8 message: "All containers must specify CPU and memory limits"
3. Conditional Validation
Traditional:
1validate:
2 deny:
3 conditions:
4 all:
5 - key: "{{ request.object.spec.containers[].securityContext.privileged || `false` }}"
6 operator: Equals
7 value: true
8 - key: "{{ request.object.metadata.namespace }}"
9 operator: NotEquals
10 value: "kube-system"
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: |
5 !(object.spec.containers.exists(container,
6 has(container.securityContext.privileged) &&
7 container.securityContext.privileged == true
8 ) && object.metadata.namespace != 'kube-system')
9 message: "Privileged containers not allowed outside kube-system namespace"
Advanced Migration Scenarios
Working with Lists and Arrays
Traditional (using foreach):
1validate:
2 foreach:
3 - list: "request.object.spec.containers"
4 deny:
5 conditions:
6 any:
7 - key: "{{ element.image }}"
8 operator: AnyIn
9 value: ["nginx:latest", "redis:latest"]
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: "!object.spec.containers.exists(container, container.image in ['nginx:latest', 'redis:latest'])"
5 message: "Containers cannot use latest tags for nginx or redis"
Complex Object Validation
Traditional:
1validate:
2 pattern:
3 spec:
4 template:
5 spec:
6 containers:
7 - name: "*"
8 env:
9 - name: "DATABASE_URL"
10 valueFrom:
11 secretKeyRef:
12 name: "?*"
CEL:
1validate:
2 cel:
3 expressions:
4 - expression: |
5 object.spec.template.spec.containers.all(container,
6 !container.env.exists(envVar,
7 envVar.name == 'DATABASE_URL' &&
8 (!has(envVar.valueFrom) || !has(envVar.valueFrom.secretKeyRef))
9 )
10 )
11 message: "DATABASE_URL must be sourced from a secret"
Testing Your Migration
1. Validate CEL Expressions
Use the Kyverno CLI to test your CEL expressions:
1# Test with a sample resource
2kyverno apply validating-policy.yaml --resource test-pod.yaml
2. Side-by-Side Testing
Deploy both policies in different namespaces temporarily:
1# Test traditional policy in namespace 'old-policy-test'
2# Test CEL policy in namespace 'cel-policy-test'
3# Compare results with identical resources
3. Use Policy Reports
Monitor policy reports to ensure equivalent behavior:
1kubectl get polr -n test-namespace -o yaml
Performance Considerations
CEL Expression Optimization
Avoid:
1# Inefficient: Multiple separate validations
2expressions:
3- expression: "has(object.metadata.labels.app)"
4- expression: "has(object.metadata.labels.version)"
5- expression: "has(object.metadata.labels.environment)"
Prefer:
1# Efficient: Combined validation
2expressions:
3- expression: |
4 ['app', 'version', 'environment'].all(label,
5 has(object.metadata.labels[label])
6 )
7 message: "Required labels: app, version, environment"
ValidatingAdmissionPolicy Generation
Enable automatic generation for optimal performance:
1apiVersion: kyverno.io/v1alpha1
2kind: ValidatingPolicy
3metadata:
4 name: efficient-policy
5 annotations:
6 policies.kyverno.io/generate-validating-admission-policy: "true"
7spec:
8 # ... policy rules
Migration Checklist
- Identify suitable policies for CEL migration
- Convert validation logic from pattern/deny to CEL expressions
- Update variable references to CEL syntax
- Test CEL expressions with sample resources
- Validate behavior matches original policy
- Enable ValidatingAdmissionPolicy generation if appropriate
- Monitor performance improvements
- Update documentation and runbooks
- Train team on CEL syntax and debugging
Troubleshooting Common Issues
CEL Expression Errors
Error: unknown field 'nonexistent'
Solution: Use has() function to check field existence:
1expression: "has(object.spec.nonexistent) && object.spec.nonexistent == 'value'"
Error: index out of bounds
Solution: Check array length before accessing:
1expression: "size(object.spec.containers) > 0 && object.spec.containers[0].image != 'bad'"
Variable Migration Issues
Issue: Traditional variables not working in CEL Solution: Use CEL built-in variables or resource library:
1# Instead of {{ request.object.metadata.name }}
2expression: "object.metadata.name"
3
4# For external data, use resource library
5expression: "resource.get('v1', 'ConfigMap', 'default', 'my-config') != null"
Next Steps
After successfully migrating to ValidatingPolicy:
- Explore ImageValidatingPolicy for supply chain security
- Consider MutatingPolicy for resource modifications (v1.15+)
- Implement policy exceptions using CEL expressions
- Set up monitoring for policy performance and compliance
Additional Resources
- CEL Language Specification
- Kyverno CEL Functions Reference
- ValidatingPolicy API Reference
- Kyverno CLI Testing Guide
Note: This migration guide focuses on ValidatingPolicy. For mutate, generate, or verifyImages rules, continue using ClusterPolicy until MutatingPolicy, GeneratingPolicy, and ImageValidatingPolicy meet your specific requirements.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.