Plugins¶
Overview¶
Plugins allow extending KubeVirt with out-of-tree functionality. A Plugin is a cluster-scoped Custom Resource that defines launcher hooks, node hooks, and admission references.
The Plugins feature is currently in Alpha (introduced in v1.9) and requires the Plugins feature gate.
Enabling the feature gate¶
Enable the Plugins feature gate by following the steps in
Activating feature gates.
Launcher Hooks¶
Launcher hooks run inside the virt-launcher pod at well-defined points in the VM lifecycle. Each hook is either a CEL expression or a sidecar container.
| Hook point | When it runs | CEL | Sidecar |
|---|---|---|---|
GuestDefinition |
After the domain is generated, before it is handed to libvirt | Yes | Yes |
PreBoot |
After boot artifacts are generated, before the guest starts | No | Yes |
PreMigrationSource |
On the migration source, before libvirt starts the migration | No | Yes |
Hooks are applied in declaration order within each plugin. Across plugins, hooks are applied in alphabetical order by plugin name.
CEL expressions¶
A CEL expression targets a single hook point, set in hookPoint. CEL is
supported only for the GuestDefinition hook point.
The expression transforms the domain inline. It uses the vmi and domainSpec
variables, whose fields are accessed by their Go names (for example
vmi.Spec.Domain), and must return a Domain{...} struct using
libvirtxml type names. The result is deep-merged with the generated domain -
only fields mentioned in the expression are overwritten, and unmentioned fields
are left untouched. Slices are replaced wholesale, not merged element-by-element.
apiVersion: plugin.kubevirt.io/v1alpha1
kind: Plugin
metadata:
name: cel-domain-example
spec:
launcherHooks:
- cel:
hookPoint: GuestDefinition
expression: 'Domain{CPU: DomainCPU{Mode: "host-passthrough"}}'
Sidecar¶
A sidecar container in virt-launcher serves gRPC over a Unix socket. Use sidecar hooks when you need logic beyond what CEL can express, such as reading external state, making API calls, or modifying files.
The Plugin CR references the socket path and lists the hook points the sidecar
handles in permittedHooks, so a single sidecar can serve several hook points.
To inject the sidecar container into the virt-launcher pod, set up a
MutatingAdmissionPolicy (or webhook). This gives you full control over the
sidecar container spec (image, resources, security context, volumes).
apiVersion: plugin.kubevirt.io/v1alpha1
kind: Plugin
metadata:
name: my-plugin
spec:
launcherHooks:
- sidecar:
socketPath: "/var/run/kubevirt-plugin/my-plugin/hook.sock"
permittedHooks:
- GuestDefinition
- PreBoot
- PreMigrationSource
Socket path constraints:
- The socket must reside under
/var/run/kubevirt-plugin/<plugin-name>/. KubeVirt adds/var/run/kubevirt-pluginto the virt-launcher pod as thekubevirt-plugin-socketsvolume, shared with the compute container. - The path must end with
.sock, be at most 108 bytes long, and contain no.., repeated or trailing/. - Symlinks are not allowed in socket paths.
- Sidecars must mount the
kubevirt-plugin-socketsvolume with asubPath, so that each plugin has isolated access to its own directory. Pods that violate this are rejected.
At each hook point, virt-launcher waits up to 30 seconds in total for the
sockets of the sidecars handling it to appear. This wait is separate from the
per-hook timeout.
GuestDefinition¶
The GuestDefinition hook mutates the domain before it is handed to libvirt.
It runs when the VM boots and may be called more than once during a single
boot, so implementations must be idempotent.
On the migration target, the hook is also invoked with the MigrationTarget
invocation context, so that a sidecar can prepare the target pod. Its returned
domain is not used: the target runs the domain migrated from the source, which
already includes the mutations applied at boot. The hook does not run on the
migration source.
Legacy annotation-based hook sidecars run before plugin hooks, so plugins see the domain as modified by them.
PreBoot¶
The PreBoot hook runs after the boot artifacts (such as the cloud-init ISO)
are generated and before the guest starts. Use it to modify guest
initialization data that depends on runtime information, for example
cloud-init network configuration built from the pod's network-status
annotation.
The request contains the VMI and the paths of the generated artifacts inside the compute container:
cloudInitISO: the generated cloud-init ISO, if the VM has a cloud-init volume.ephemeralDiskDir: the directory that holds the ephemeral disks,/var/run/kubevirt-ephemeral-disks.
To access the artifacts, the sidecar must mount the ephemeral-disks volume at
/var/run/kubevirt-ephemeral-disks, the same path as in the compute container.
When multiple plugins handle PreBoot, each sidecar sees the files as left by
the previous one.
Things to keep in mind:
PreBootruns only when the VM boots, not on the migration target. Volumes hotplugged into a running VM never pass through it.PreBootmay be called again after a failed start, and file changes are not rolled back. Implementations must be idempotent.- A hook with
failureStrategy: Ignoreis not stopped when it exceeds its timeout: the VM starts while the hook may still be modifying files. Hooks that modify files should usefailureStrategy: Fail, write atomically (temporary file and rename), and honor the request context. GuestDefinitionhooks run beforePreBoothooks.
PreMigrationSource¶
The PreMigrationSource launcher hook runs on the migration source, right
before virt-launcher hands the migration to libvirt. It allows a plugin to
tune the migration per VM, for example to enable parallel (multifd) migration
or select a compression method.
The request contains the VMI and the migration configuration KubeVirt is
about to pass to libvirt: the VIR_MIGRATE_* flags bitmask and the
VIR_MIGRATE_PARAM_* parameters. The response contains only the changes:
set_flagsadds flag bits andclear_flagsremoves them. A bit present in both is an error.- For each parameter, an unset field keeps the current value, a set field overrides it, and an empty wrapper or empty list removes the parameter.
- An empty response changes nothing.
When multiple hooks handle PreMigrationSource, each one receives the
configuration as modified by the previous hooks. The result is passed to
libvirt as-is, so the plugin has full control over, and full responsibility
for, the migration configuration, including the migration URIs.
Things to keep in mind:
- If a hook with
failureStrategy: Failfails, the migration fails. WithIgnore, the migration proceeds with the configuration returned by the previous successful hooks. - If the migration is aborted while hooks are running, the request context is canceled. Hooks should honor it.
- The migration monitor starts after the hooks return, so the time spent in hooks does not count towards the migration timeouts.
- The migration monitor follows the resulting
VIR_MIGRATE_POSTCOPYflag: clearing it disables post-copy, and setting it allows post-copy under the same conditions as when KubeVirt sets it.
This hook is distinct from the PreMigrationSource node hook, which runs on
the node before the migration starts and cannot change the migration
configuration.
Node Hooks¶
Node hooks execute during VM lifecycle events via gRPC from virt-handler to a plugin server. The plugin author deploys and manages this server as a DaemonSet, giving the cluster admin control over its capabilities and resources via standard Kubernetes mechanisms.
Available hook points:
PreVMStart: before the VM starts.PostVMStart: after the VM is running.OnVMStop: when KubeVirt stops a running VM, for example when the VMI is deleted. It does not fire when the guest shuts down or crashes on its own.PostVMStop: after the VM has stopped.PreMigrationSource: on the source node, before the migration starts.PreMigrationTarget: on the target node, before the migration starts.PostMigrationTarget: on the target node, after the migration completes.
Node hooks may fire multiple times for the same lifecycle event due to reconciliation retries. Implementations must be idempotent.
The socket must be on a host path that virt-handler mounts, for example under
/var/run/kubevirt/.
apiVersion: plugin.kubevirt.io/v1alpha1
kind: Plugin
metadata:
name: node-hook-example
spec:
nodeHooks:
- socket: /var/run/kubevirt/plugins/my-plugin.sock
permittedHooks:
- PreVMStart
- OnVMStop
Admission References¶
Plugins can reference Kubernetes admission policies and webhooks for pod/VMI modifications:
apiVersion: plugin.kubevirt.io/v1alpha1
kind: Plugin
metadata:
name: admission-example
spec:
mutatingAdmissionPolicies:
- name: "my-mutating-policy"
validatingAdmissionPolicies:
- name: "my-validating-policy"
mutatingAdmissionWebhooks:
- name: "my-mutating-webhook"
validatingAdmissionWebhooks:
- name: "my-validating-webhook"
These are listed in the Plugin CR to provide a central place to understand all plugin components.
Failure Handling and Conditions¶
failureStrategy:Fail(default) blocks the operation on error.Ignorelogs the error and continues. For launcher hooks, it can be set at plugin level as the default and overridden per hook. Node hooks use only their ownfailureStrategy.condition: A CEL expression that filters which VMIs the plugin applies to. It can reference onlyvmi, using Go field names, and must evaluate to a bool. Can be set at plugin level and per hook. Per-hook conditions further narrow the plugin-level filter. An error while evaluating a condition fails the operation regardless offailureStrategy.timeout: Per-hook maximum wait duration for sidecar launcher hooks and node hooks. Defaults to 30s. It does not apply to CEL hooks.
This example applies only to VMIs that carry the example.com/tuning label.
The CEL hook further narrows to VMIs where the label value is cpu. The
sidecar hook tunes the migration of all labeled VMIs; if it fails or does not
respond within 10 seconds, the migration proceeds without its changes.
apiVersion: plugin.kubevirt.io/v1alpha1
kind: Plugin
metadata:
name: full-example
spec:
failureStrategy: Fail
condition: '"example.com/tuning" in vmi.Labels'
launcherHooks:
- cel:
hookPoint: GuestDefinition
expression: 'Domain{CPU: DomainCPU{Mode: "host-passthrough"}}'
condition: 'vmi.Labels["example.com/tuning"] == "cpu"'
- sidecar:
socketPath: "/var/run/kubevirt-plugin/full-example/hook.sock"
permittedHooks:
- PreMigrationSource
failureStrategy: Ignore
timeout: 10s
Developing Plugins¶
Sidecar launcher hooks and node hooks communicate via gRPC. The proto definitions
can be found at
pkg/hooks/plugins/v1alpha1/api.proto
in the KubeVirt repository.
- Launcher hooks implement the
LauncherHookService, with one RPC per hook point:GuestDefinition: the request contains the domain XML, the VMI object (as JSON), and a sidecar context with the invocation context (BootorMigrationTarget). The response contains the modified domain XML.PreBoot: the request contains the VMI object (as JSON) and the artifact paths.PreMigrationSource: the request contains the VMI object (as JSON) and the libvirt migration flags and parameters. The response contains the changes to apply.
- Node hooks implement the
NodeHookService.ExecuteNodeHookRPC. The request contains the hook point name, the VMI object (as JSON), and node context.
A sidecar only needs to implement the RPCs for the hook points listed in its
permittedHooks.
Related¶
- Hook Sidecar Container - legacy mechanism (deprecated).
- VEP 190 - design details.