|
This is unreleased documentation for Admission Controller 1.39-dev. |
Compile CEL to WebAssembly
|
This is an experimental feature available since Admission Controller v1.39.0. |
In this tutorial, you compile a Kubernetes ValidatingAdmissionPolicy into a WebAssembly module with kwctl.
You then inspect the module and prepare it for deployment as a ClusterAdmissionPolicy.
Why compile CEL to WebAssembly?
The alternative approach with cel-policy policy runs CEL code using the
official CEL Go interpreter.
See Using cel-policy for how that policy works.
That approach has two drawbacks:
-
Size: The
cel-policypolicy bundles the entire CEL runtime and the Kubernetes CEL libraries, even when a policy uses only some of them. As a result, the WebAssembly module is large, which increases the PolicyServer startup time. -
Portability: The CEL code lives inside the Admission Controller policy manifest. This is good for prototyping because there is no build step. But it makes the policy hard to share. The manifest also carries deployment details such as the target namespaces and objects, the enforcement mode, and more.
Alternatively, when you compile CEL to WebAssembly, you produce a smaller and more efficient module that you can share and reuse across different deployments.
Admission Controller uses the ferricel open source project to compile a Kubernetes
ValidatingAdmissionPolicy into a specialized WebAssembly module.
As a comparison, the WebAssembly file produced by this compiler is around 1.5MiB, while the one produced by the cel-policy is 14MiB.
The compiled module is also much faster. See Performance for benchmark results.
Prerequisites
-
The
kwctlCLI tool. -
A
ValidatingAdmissionPolicythatcel-policycan run, and its matchingValidatingAdmissionPolicyBinding.
Compile a policy
You compile a policy from the ValidatingAdmissionPolicy and its binding with
the kwctl scaffold vap command.
|
Before Admission Controller v1.39.0, this command converted a |
Start from this ValidatingAdmissionPolicy definition, in a file named vap-with-params.yml:
vap-with-params.yml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "vap-test"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "deployments/scale"]
paramKind:
apiVersion: v1
kind: ConfigMap
validations:
- expression: "object.spec.replicas <= params.data.maxReplicas"
message: "not enough replicas"
And this ValidatingAdmissionPolicyBinding, in a file named vap-binding-params.yml:
vap-binding-params.yml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "kw-scaffold-demo"
spec:
policyName: "vap-test"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
paramRef:
name: "replica-limit-override"
namespace: "test"
parameterNotFoundAction: Deny
Compile them with this command:
kwctl scaffold vap \
--policy vap-with-params.yml \
--binding vap-binding-params.yml \
--compile-to-wasm policy.wasm
The command produces two files:
-
policy.wasm: TheValidatingAdmissionPolicycompiled to WebAssembly. -
metadata.yml: The standard metadata file for Admission Controller policies.
The metadata.yml file looks like this:
metadata.yml
rules:
- apiGroups:
- apps
apiVersions:
- v1
resources:
- deployments
- deployments/scale
operations:
- CREATE
- UPDATE
annotations:
io.kubewarden.policy.title: kw-scaffold-demo
mutating: false
backgroundAudit: true
executionMode: ferricel
policyType: kubernetes
contextAwareResources:
- apiVersion: v1
kind: ConfigMap
hostCapabilities:
- kubernetes/get_resource
- kubernetes/list_resources_all
- kubernetes/list_resources_by_namespace
The command derives these metadata values from the ValidatingAdmissionPolicy and
ValidatingAdmissionPolicyBinding definitions.
The command also prints a ClusterAdmissionPolicy definition to standard output:
Generated ClusterAdmissionPolicy
apiVersion: policies.kubewarden.io/v1
kind: ClusterAdmissionPolicy
metadata:
name: kw-scaffold-demo
spec:
module: file:///path/to/policy.wasm
settings:
failurePolicy: Fail
paramKind:
apiVersion: v1
kind: ConfigMap
paramRef:
name: replica-limit-override
namespace: test
parameterNotFoundAction: Deny
rules:
- apiGroups:
- apps
apiVersions:
- v1
resources:
- deployments
- deployments/scale
operations:
- CREATE
- UPDATE
mutating: false
contextAwareResources:
- apiVersion: v1
kind: ConfigMap
mode: protect
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: default
This is the ClusterAdmissionPolicy definition that you can deploy in a Kubernetes cluster.
All these settings are derived from the ValidatingAdmissionPolicy specification. |
Next, like with any type of Admission Controller policy, annotate the WebAssembly module with this command:
kwctl annotate -m metadata.yml -o annotated.wasm policy.wasm
Inspect the policy
The kwctl inspect command extracts more information from compiled CEL policies.
This is the output of kwctl inspect annotated.wasm:
Output of kwctl inspect annotated.wasm
Details
title: kw-scaffold-demo
mutating: false
background audit support: true
context aware: true
policy type: kubernetes
execution mode: ferricel
Annotations
io.kubewarden.kwctl 1.39.0
Rules
- apiGroups:
- apps
apiVersions:
- v1
resources:
- deployments
- deployments/scale
operations:
- CREATE
- UPDATE
Context Aware
The policy requires access to the following Kubernetes resources at evaluation time:
- apiVersion: v1
kind: ConfigMap
To avoid abuses, review carefully what the policy requires access to.
Ferricel
language: Rust, CEL
processed-by: rustc 1.98.1 (48a229cea 2026-09-01), walrus 0.27.2, ferricel 0.13.0
VAP variables (referenced): object, params
source:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: vap-test
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- deployments
- deployments/scale
paramKind:
apiVersion: v1
kind: ConfigMap
validations:
- expression: object.spec.replicas <= params.data.maxReplicas
message: not enough replicas
The new Ferricel section shows:
-
The version of the ferricel compiler that produced the WebAssembly module.
-
The original
ValidatingAdmissionPolicysource code.
The ferricel compiler embeds this information in the WebAssembly module as custom sections. This helps with transparency and debugging. See the ferricel documentation about producers metadata and source custom sections for more details.
Run the policy
Use kwctl run to test the policy locally before you deploy it.
See Testing for policy authors for the request and settings files
that kwctl run needs, and for how to write automated tests.
To distribute the policy, push it to an OCI registry with kwctl push:
kwctl push annotated.wasm registry.example.com/policies/kw-scaffold-demo:v0.1.0
Then set the module field of the ClusterAdmissionPolicy to the registry URL, for example
registry://registry.example.com/policies/kw-scaffold-demo:v0.1.0.
See Distributing policies for more details.
|
All CEL policies need access to the Kubernetes API server. Policies with parameters need access
to the referenced resources, such as the A policy without parameters still needs to query the API server for the Namespace of the object being evaluated. Kubernetes |
Performance
The table compares the mean evaluation time for two Kubernetes ValidatingAdmissionPolicy configurations. The benchmark
compared the CEL code run by the cel-policy versus the specialized module produced by the ferricel compiler.
The benchmark used the kwctl bench command. It recorded the interaction with the
Kubernetes API server and replayed it to avoid network latency and other external factors.
| Variant | ferricel (mean time per evaluation) | cel-policy (mean time per evaluation) | Speedup |
|---|---|---|---|
Plain VAP (no params, no host callbacks) |
197.4µs |
20.85ms |
~106x |
VAP with ConfigMap |
332.2µs |
23.19ms |
~70x |
The WebAssembly policy produced by the ferricel compiler runs one to two orders of magnitude faster
than the interpreted cel-policy.
Limitations
The ferricel compiler currently passes the full CEL compliance suite and supports the cel-go and Kubernetes extensions. However, it’s still an experimental project and might not yet cover all edge cases.
The full status of the ferricel compiler can be found in the official documentation: CEL compliance.
If your compiled policy does not behave as expected, file an issue on the ferricel bug tracker.