Skip to main content
Version: v4.12 Stable

KubeVirt

Enterprise
Available in these plansFreeDevProdScale
KubeVirt Node Provider

Free, Dev, Prod, and Scale are vCluster Platform license plans. Open source does not need a license or a Platform connection. See Compare open source and free tiers.

The KubeVirt provider allows you to use KubeVirt to automatically provision virtual machines as nodes for your clusters. When a vCluster requests a new node, the platform creates a KubeVirt VirtualMachine based on the template defined in the NodeProvider and the specific NodeType requested.

To inspect and operate KubeVirt VMs directly from vCluster Platform, see Virtual Machines. For administrator guidance on VM disk imports and Multus networks, see Data Volumes and Network Attachment Definitions.

This enables you to offer different "flavors" of virtual machines, for example small, medium, and large compute shapes, as nodes for your vCluster, all managed from a central configuration.

How the KubeVirt provider provisions a nodevCluster PlatformNodeClaimRequests a node on create.Triggers VM deletion on removal.References a NodeType for the VM template.KubeVirt control plane clusterVirtualMachineCreated from the NodeType template.cloud-init injects a join-token Secret.Deleting the NodeClaimdeletes this VirtualMachine.Tenant clusterWorker nodeThe VM registers using itsinjected join token.
The platform turns a NodeClaim into a KubeVirt VirtualMachine on the control plane cluster, which joins the tenant cluster as a worker node through cloud-init. Deleting the NodeClaim reverses the flow.

Overview​

The KubeVirt provider works by defining a base virtualMachineTemplate and a collection of nodeTypes. Each nodeType represents a specific kind of virtual machine you want to offer. When a NodeClaim is created for a vCluster, it references one of these nodeTypes. The platform then generates a VirtualMachine manifest by combining the base template with any customizations defined in the chosen nodeType and applies it to the target KubeVirt control plane cluster.

This approach allows for customizations like:

  • Resource Overrides: Easily create node types with different amounts of CPU or memory.
  • Template Merging: Modify specific parts of the base template, like adding taints or labels.
  • Template Replacement: Define completely distinct virtual machine templates for specialized use cases.

How it works: Cloud-init Node registration​

For a virtual machine to automatically join a vCluster as a node, it needs to be configured with the correct vCluster address and join token. The KubeVirt provider automates this process using cloud-init.

Here's the workflow:

  1. When a NodeClaim is processed, the platform generates a cloud-init configuration containing the necessary registration scripts.
  2. This configuration is stored in a Kubernetes Secret in the KubeVirt control plane cluster.
  3. The provider injects this Secret into the VirtualMachine definition as a cloudInitNoCloud disk.
  4. When the VM boots, cloud-init executes the script from the disk, configuring the machine and registering it as a node with the tenant cluster.
Image Requirement

This entire process depends on the guest OS image having cloud-init installed and enabled. Furthermore, the image must be compatible with KubeVirt's cloudInitNoCloud data source. Standard cloud images for distributions like Ubuntu are generally compatible. If you use custom images, you must ensure they meet this requirement.

Infrastructure deployment​

The KubeVirt provider can deploy the KubeVirt and CDI operators onto the control plane cluster, or use an installation that you manage yourself. By default the provider expects KubeVirt to already be installed on the target cluster. To have the provider install it, enable the deployment under spec.kubeVirt.deploy.kubevirt:

apiVersion: management.loft.sh/v1
kind: NodeProvider
metadata:
name: kubevirt-provider
spec:
kubeVirt:
clusterRef:
cluster: my-cluster
namespace: vcluster-platform
deploy:
kubevirt:
enabled: true
helmValues: |
kubevirt:
operator:
kubevirtVersion: v1.8.2

Deployment fields (NodeProvider kubeVirt.deploy.kubevirt):

FieldDescriptionDefault
enabledDeploy the KubeVirt and CDI operators. Immutable after creation.false
chartRepoOverride the Helm chart repository.oci://ghcr.io/loft-sh/charts
chartOverride the Helm chart name.kubevirt
versionOverride the Helm chart version.Bundled version
helmValuesRaw YAML applied to both the operators and the KubeVirt and CDI resources (see below).—

Configure the operators:

ValueDescriptionDefault
kubevirt.operator.image.registry / .org / .name / .tagKubeVirt operator image.quay.io / kubevirt / virt-operator / v1.8.2
kubevirt.operator.kubevirtVersionKubeVirt version the operator installs.v1.8.2
cdi.operator.image.registry / .org / .name / .tagCDI operator image.quay.io / kubevirt / cdi-operator / v1.65.0
cdi.operator.cdiVersionCDI version the operator installs.v1.65.0
cdi.operator.serviceMonitorNamespaceNamespace for the CDI ServiceMonitor. Empty disables its creation.""
imagePullSecretsPull secrets for the operator images.[]

Configure the KubeVirt and CDI resources:

The same helmValues also populate the KubeVirt and CDI resources that the provider applies. The platform renders kubevirt.spec verbatim into the KubeVirt resource .spec, and cdi.spec into the CDI resource .spec, so KubeVirt and CDI configuration such as feature gates is controllable from here.

ValueDescriptionDefault
kubevirt.specRendered into the KubeVirt resource .spec.Minimal configuration, no feature gates.
cdi.specRendered into the CDI resource .spec.Feature gates HonorWaitForFirstConsumer and WebhookPvcRendering, Linux node selectors.

For example, to enable a KubeVirt feature gate and add a CDI feature gate:

deploy:
kubevirt:
enabled: true
helmValues: |
kubevirt:
spec:
configuration:
developerConfiguration:
featureGates:
- LiveMigration
cdi:
spec:
config:
featureGates:
- HonorWaitForFirstConsumer
- WebhookPvcRendering
- ExpandDisks
Guarded against existing installations

Before deploying, the platform checks the target cluster for the KubeVirt CRD (kubevirts.kubevirt.io). When KubeVirt is already present, the provider does not redeploy it. The provider removes KubeVirt on provider deletion only when it deployed it (enabled: true).

deploy.kubevirt.enabled, clusterRef.cluster and clusterRef.namespace are immutable after creating the provider. To change them, create a new provider.

Configuration​

A KubeVirt NodeProvider configuration consists of a reference to the control plane cluster, a base VM template, and a list of node types.

Minimal example​

Here is a minimal configuration for a KubeVirt provider. It defines a single node type that uses the base template without any modifications.

apiVersion: management.loft.sh/v1
kind: NodeProvider
metadata:
name: kubevirt-provider-minimal
spec:
displayName: "KubeVirt Minimal Provider"
properties:
kubevirt.vcluster.com/image-url: docker://quay.io/containerdisks/ubuntu:22.04
kubevirt.vcluster.com/root-disk-size: 20Gi
kubeVirt:
# clusterRef allows configuration of where KubeVirt is running and where VirtualMachines will be created.
clusterRef:
cluster: test-kubevirt-1 # name of the control plane cluster already connected to the platform
namespace: vcluster-platform # namespace in this control plane cluster where VirtualMachines will be created
# Base template for all Virtual Machines created by this provider
virtualMachineTemplate:
spec:
template:
spec:
domain:
resources:
requests:
cpu: "1"
memory: "2Gi"
# Define the types of nodes users can request
nodeTypes:
- name: "small-ubuntu-vm"
displayName: "Small Ubuntu VM (KubeVirt)"
maxCapacity: 10

Choose where VirtualMachines are created​

spec.kubeVirt.namespaceStrategy controls which namespace on the control plane cluster a node's VirtualMachine object is created in:

  • Provider (default): every VirtualMachine is created in the node provider's own namespace, clusterRef.namespace.
  • namespaceStrategy: VirtualCluster: each VirtualMachine is created in the namespace of the tenant cluster whose NodeClaim requested the node, instead of the provider's namespace.
spec:
kubeVirt:
namespaceStrategy: VirtualCluster

The VirtualCluster strategy falls back to the Provider behavior when it can't resolve a usable tenant cluster namespace within clusterRef.cluster. This happens in the following cases:

  • The NodeClaim has no tenant cluster reference.
  • The referenced VirtualClusterInstance doesn't exist.
  • The tenant cluster runs in a different connected cluster from the KubeVirt provider.
  • The tenant cluster is standalone and doesn't run in a namespace on the provider cluster.

As a result, namespaceStrategy: VirtualCluster has no effect for standalone tenant clusters.

Define NodeTypes​

The real power of the KubeVirt provider comes from its flexible nodeTypes. You can define multiple types, each with specific overrides or entire template replacements.

Override resources​

You can create a NodeType that inherits the base virtualMachineTemplate but specifies different CPU and memory resources. This is useful for offering different machine sizes.

nodeTypes:
- name: "small-ubuntu-vm-2"
displayName: "Small Ubuntu VM (KubeVirt)"
maxCapacity: 10
# This NodeType uses the base template's resources (1 CPU, 2Gi Memory)

- name: "mid-ubuntu-vm-2"
displayName: "Mid Ubuntu VM (KubeVirt)"
maxCapacity: 5
resources:
cpu: 4
memory: 5Gi
# This NodeType overrides the base template to provide 4 CPUs and 5Gi of memory

Merge template modifications​

The mergeVirtualMachineTemplate field allows you to provide a partial VirtualMachine template that will be strategically merged with the base template. This is ideal for changing specific attributes, like adding Kubernetes taints or labels, without redefining the entire VM.

Merge Behavior

The merge logic follows standard Kubernetes strategic merge patching. Arrays are typically replaced, while maps (objects) are merged.

In this example, the high-memory-node type adds a taint and a label to the base template.

# ... (omitting provider spec for brevity)
nodeTypes:
- name: "high-memory-node"
maxCapacity: 2
displayName: "High Memory Node (KubeVirt)"
mergeVirtualMachineTemplate:
metadata:
labels:
# This label will be added to the base labels
workload: memory-intensive
spec:
template:
spec:
# Add a taint to ensure only specific pods schedule here
taints:
- key: "workload"
value: "high-memory"
effect: "NoSchedule"

Replace the entire template​

For cases where a NodeType requires a fundamentally different configuration, you can use the virtualMachineTemplate field inside the NodeType definition. This completely ignores the base template and uses the specified one instead.

# ... (omitting provider spec for brevity)
nodeTypes:
- name: "different-vm-with-different-template-2"
maxCapacity: 1
displayName: "Different VM with Different Template (KubeVirt)"
# This template completely replaces the provider-level base template
virtualMachineTemplate:
metadata:
labels:
foo: baz # Note: the base label 'foo: bar' is not present
spec:
# ... (full, self-contained VirtualMachine spec)

Example: vCluster with static KubeVirt node​

This example demonstrates a realistic multi-cluster scenario. We will configure a NodeProvider on the management cluster that provisions specialized VMs onto a separate, KubeVirt-enabled target cluster.

  1. Verify prerequisites.

    Before you begin, ensure you have a KubeVirt-enabled Kubernetes cluster connected to your vCluster Platform management plane. You can verify this by listing the connected clusters.

    kubectl get clusters

    You should see your target cluster in the list. For this example, we'll assume it's named test-kubevirt-1.

    NAME CREATED AT
    loft-cluster 2025-08-21T10:43:14Z
    test-kubevirt-1 2025-08-21T11:51:36Z
    test-no-kubevirt-2 2025-08-21T12:34:25Z
  2. Create the NodeProvider

    Apply the NodeProvider configuration to your management cluster. This configuration references the target cluster, test-kubevirt-1, and specifies that new VMs should be created in the vcluster-platform namespace on that cluster.

    apiVersion: management.loft.sh/v1
    kind: NodeProvider
    metadata:
    name: kubevirt-provider-advanced
    spec:
    displayName: "KubeVirt Advanced Provider"
    properties:
    kubevirt.vcluster.com/image-url: docker://quay.io/containerdisks/ubuntu:22.04
    kubevirt.vcluster.com/root-disk-size: 20Gi
    kubeVirt:
    clusterRef:
    cluster: test-kubevirt-1
    namespace: vcluster-platform
    virtualMachineTemplate:
    metadata:
    labels:
    provider: kubevirt
    spec:
    template:
    spec:
    domain:
    devices:
    interfaces:
    - name: default
    masquerade: {}
    resources:
    requests:
    cpu: "2"
    memory: "4Gi"
    networks:
    - name: default
    pod: {}
    nodeTypes:
    - name: "standard-node"
    displayName: "Standard Ubuntu VM (KubeVirt)"
    maxCapacity: 10
    - name: "high-memory-node"
    maxCapacity: 5
    displayName: "High Memory Node (KubeVirt)"
    resources:
    cpu: 4
    memory: 16Gi
    mergeVirtualMachineTemplate:
    metadata:
    labels:
    workload: memory-intensive
    spec:
    template:
    spec:
    taints:
    - key: "workload"
    value: "high-memory"
    effect: "NoSchedule"
  3. Verify NodeTypes are Available

    After applying the NodeProvider, the platform processes it and creates corresponding NodeType resources. You can list these to confirm they are ready to be claimed.

    kubectl get nodetype

    The command will show that both the standard-node and the high-memory-node are in the Available phase.

    NAME AVAILABLE TOTAL PROVIDER COST PHASE CREATED AT
    high-memory-node 5 5 kubevirt-provider-advanced 72 Available 2025-08-25T13:17:30Z
    standard-node 10 10 kubevirt-provider-advanced 28 Available 2025-08-25T13:17:30Z
  4. Create a vCluster with Auto-Provisioning

    Now, create a vCluster instance that is configured to automatically request a node upon creation. We'll do this by providing a values file that enables the autoNodes feature.

    First, create a file named test-kubevirt-vc.yaml with the following content. This tells the vCluster to automatically claim one node matching the standard-node type.

    privateNodes:
    enabled: true
    autoNodes:
    - provider: my-provider
    static:
    - name: static-standard-nodes
    quantity: 1
    nodeTypeSelector:
    - property: vcluster.com/node-type
    value: standard-node

    Next, create the vCluster using this file.

    vcluster create --driver platform --connect=false -f test-kubevirt-vc.yaml --cluster loft-cluster test-kubevirt-node-1
  5. Verify NodeClaim.

    Once the vCluster is created, its autoNodes configuration will automatically generate a NodeClaim. You can verify this on the management cluster.

    kubectl get nodeclaim -A

    You will see a NodeClaim for the standard-node type, which eventually will reach Available state, indicating that the request has been fulfilled.

    NAMESPACE NAME STATUS VCLUSTER NODETYPE CREATED AT
    p-default test-kubevirt-node-1-x49gd Available test-kubevirt-node-1 standard-node 2025-08-25T13:41:07Z
  6. Verify the VirtualMachine on the Target Cluster.

    The NodeClaim triggers the NodeProvider to create a VirtualMachine. Switch your kubectl context to the target KubeVirt cluster (test-kubevirt-1) and inspect the VM object.

    # Switch context to the KubeVirt control plane cluster
    kubectx test-kubevirt-1

    # Check for the running Virtual Machine
    kubectl -n vcluster-platform get vm

    The output confirms that the VirtualMachine corresponding to the NodeClaim is running.

    NAME AGE STATUS READY
    test-kubevirt-node-1-x49gd 3m1s Running True
  7. Verify the Node in the vCluster.

    Finally, connect to your vCluster and verify that the new node has successfully joined the cluster.

    vcluster --driver platform connect test-kubevirt-node-1

    After connecting, check the nodes within the vCluster.

    kubectl get nodes

    The output will show the new node in the Ready state, confirming the node is up and ready for workloads.

    NAME STATUS ROLES AGE VERSION
    test-kubevirt-node-1-9xm54 Ready <none> 27s v1.33.4

Configuration properties​

Properties are key-value pairs on the NodeProvider, NodeType, or NodeClaim that control provisioning behavior. NodeClaim properties take precedence over NodeType. NodeType properties take precedence over NodeProvider.

Inline template​

kubevirt.vcluster.com/vm-template​

Type: string (inline YAML of a VirtualMachine resource)

Allows a NodeClaim to fully specify its VirtualMachine template inline, bypassing the NodeProvider's virtualMachineTemplate and any NodeType. When this property is set on a NodeClaim, the provider skips the default NodeType-based scheduling and uses the supplied template directly. The claim does not need a typeRef in this case.

This is useful when a tenant wants to create a one-off VM whose shape does not match any predefined NodeType.

Standalone Machines only

This property is currently only compatible with standalone Machines. It cannot be used with NodeClaims created by a tenant cluster's node pool. This is not enforced. Setting it on a node-pool claim will produce undefined behavior.

Image configuration​

The image configuration properties synthesize a root-disk DataVolumeTemplate, volume, and disk and prepend them to the VM template. The synthesized disk is prepended to the VM's disk list, so it takes boot precedence over any other OS disks defined in the template. For administrator workflows, ownership, and import troubleshooting, see Data Volumes.

vcluster.com/os-image​

Type: string

References a KubeVirt OSImage resource by name. The OSImage's properties merge into the NodeClaim's properties. If the OSImage sets kubevirt.vcluster.com/image-url or kubevirt.vcluster.com/image-datasource, the NodeClaim's source-related fields (image-url, image-checksum, image-datasource) are discarded before the overlay to avoid mismatched sources.

kubevirt.vcluster.com/image-url​

Type: string (HTTP(S) URL or docker:// reference)

URL of the OS image. HTTP and HTTPS URLs produce an http DataVolume source; docker:// URLs produce a registry source. Configure either this property or kubevirt.vcluster.com/image-datasource. If both are present, the provider uses the DataSource.

kubevirt.vcluster.com/image-checksum​

Type: string

Checksum of the OS image, used only when image-url is an HTTP(S) URL.

kubevirt.vcluster.com/image-datasource​

Type: string (<name> or <namespace>/<name>)

References a CDI DataSource. The provider sets the DataVolume's sourceRef to this DataSource. If you only supply <name>, the provider looks up the DataSource in the node provider namespace from spec.kubeVirt.clusterRef.namespace. Use <namespace>/<name> to select a different namespace explicitly. Configure either this property or kubevirt.vcluster.com/image-url. If both are present, the provider uses the DataSource and ignores the URL and checksum.

For a cross-namespace clone, the connected-cluster credentials the KubeVirt node provider uses must be able to create datavolumes/source or pods in the DataSource namespace.

kubevirt.vcluster.com/root-disk-size​

Type: string (Kubernetes quantity, for example 40Gi) Required: Yes, whenever image configuration is used.

Storage request for the synthesized root-disk DataVolume. Provisioning fails if image configuration is set without a root-disk size.

kubevirt.vcluster.com/image-datavolume-template​

Type: string (inline YAML)

A partial DataVolumeTemplate that is deep-merged onto the defaults before the platform applies the image source and disk size. Use this to override fields like storage.accessModes or storage.storageClassName. The platform always sets the DataVolume's source and sourceRef based on the image properties. You cannot override them here.

kubevirt.vcluster.com/root-disk-template​

Type: string (inline YAML)

A partial disk entry that is deep-merged onto the default root disk (bus: virtio) before being prepended to the VM's domain.devices.disks list. Use this to set the bus, boot order, or other disk fields.

Network configuration​

KubeVirt provisioning supports additional properties for network configuration with cloud-init:

kubevirt.vcluster.com/network-cidr​

Type: string (CIDR notation)

Specifies a CIDR range for allocating IP addresses to the VM. Example: 10.100.0.0/24

Required properties:

  • kubevirt.vcluster.com/network-nameservers: DNS nameservers

Optional properties:

  • kubevirt.vcluster.com/network-interface: Interface name in the VM (defaults to enp1s0)

kubevirt.vcluster.com/network-interface​

Type: string

The name of the network interface in the VM to configure, for example eth0, eth1, or enp1s0. It should normally match the default name from the operating system's device enumeration. Optional for CIDR-based configuration. Defaults to enp1s0 when unset.

kubevirt.vcluster.com/network-ip-range​

Type: string (comma-separated IP ranges)

Specifies IP ranges to use for allocation instead of using CIDR-based allocation. Format: IP1-IP2,IP3-IP4

Example: 10.0.0.20-10.0.0.30,10.0.0.40-10.0.0.50

kubevirt.vcluster.com/network-nameservers​

Type: string (comma-separated)

DNS nameservers to configure in the VM. Example: 8.8.8.8,8.8.4.4

kubevirt.vcluster.com/network-data​

Type: string (base64-encoded cloud-init network-config)

Allows providing a complete custom network configuration for cloud-init. When set, this overrides automatic network configuration.

kubevirt.vcluster.com/network-attachment-definition​

Type: string (format: [<namespace>/]<name>)

References a Multus NetworkAttachmentDefinition that already exists in the target namespace on the control plane cluster. The provider injects a multus network and a corresponding interface into the VM template using this NAD. If a network or interface with the same name already exists in the template, it is overwritten.

This property is the supported way to attach a VM to an additional network such as a bridge. Set it on the NodeType for VMs that should share a network, or on the NodeClaim for a one-off attachment. For UI workflows, examples, ownership, and NAD troubleshooting, see Network Attachment Definitions.

The following properties tune the injected interface. They only take effect when kubevirt.vcluster.com/network-attachment-definition is set.

kubevirt.vcluster.com/interface-binding​

Type: string

Sets the binding method for the injected interface. One of bridge, masquerade, or sriov. Defaults to bridge.

KubeVirt removed several network bindings, including macvtap and slirp, from its in-tree API in v1.3, so this property now accepts only the remaining in-tree bindings and rejects any other value. Plugin-based bindings such as macvtap, passt, slirp, and custom bindings are selected through kubevirt.vcluster.com/interface-template instead.

kubevirt.vcluster.com/interface-template​

Type: string (YAML)

A partial KubeVirt interface definition merged over the default injected interface ({name: multus, bridge: {}}). Use it for binding-specific options that kubevirt.vcluster.com/interface-binding does not cover. For a network binding plugin that is already installed and registered with KubeVirt, set binding: {name: <plugin>} in this template. The interface name is always forced to multus, because KubeVirt requires the interface name to match the network name.

kubevirt.vcluster.com/multus-default​

Type: string ("true" or "false")

When "true", the Multus network becomes the VM's default network, and the pod network and its interface are removed, since KubeVirt rejects a VM that has both. Defaults to "false".

Network environment​

vcluster.com/network-environment​

Type: string

Names the NodeEnvironment whose properties merge into the NodeClaim's effective property set. The platform resolves this network environment from the merged properties of NodeProvider, NodeType, and NodeClaim. When set, this property takes precedence over the typed NodeClaim.spec.environmentRef field.

Use it to declare network environment defaults at the provider or type level, or to keep network configuration separate from provider and type definitions.

SSH and user data​

The vcluster.com/ssh-keys and vcluster.com/user-data properties behave identically across providers. See the metal3 reference for details.

To render cloud-init from a reusable template, reference a MachineConfigTemplate with the vcluster.com/user-data-template-config property. See Machine Config Templates for the template syntax and available variables.

Integrate with kube-ovn​

KubeVirt VMs can attach to kube-ovn networks, so each set of auto-nodes runs on its own isolated, routable overlay network with OVN-native DHCP and optional external egress.

Attach VMs to a kube-ovn network​

A VM joins a kube-ovn network the same way it joins any other network, through a Multus NetworkAttachmentDefinition that's set as the VM's default network with an interface binding. This works whether you created the kube-ovn network yourself, another system manages it, or the platform provisioned it for you. To have the platform create the network, see Provision a kube-ovn network.

By default, the integration uses the managedtap interface binding instead of bridge. The bridge binding makes KubeVirt run its own DHCP inside the launcher pod, which answers the guest before kube-ovn can, shadowing OVN-native DHCP so the VM never receives the address and gateway the subnet serves. managedtap avoids this. The binding is overridable, so you can set a different one where it fits your network.

The attachment is driven by these KubeVirt properties (see Network configuration):

  • kubevirt.vcluster.com/network-attachment-definition: the kube-ovn Multus NAD to attach to.
  • kubevirt.vcluster.com/multus-default: "true" so the kube-ovn network is the VM's default network.
  • kubevirt.vcluster.com/interface-template: {"binding":{"name":"managedtap"}} to use the managedtap binding.

When the NodeEnvironment carries the kube-ovn.vcluster.com/subnet property, the platform fills these in automatically, each only when you have not set it yourself. Set any of them explicitly on the NodeType or NodeClaim to override the default.

  • kubevirt.vcluster.com/network-attachment-definition points at the NetworkAttachmentDefinition named after the subnet in the provider namespace (<provider-namespace>/<subnet>).
  • kubevirt.vcluster.com/multus-default becomes "true".
  • kubevirt.vcluster.com/interface-template selects the managedtap binding, for the reason described above.

Register the managedtap binding​

The integration uses managedtap by default, and registering it is required whenever you use it. The binding must be registered on the KubeVirt resource on the control plane cluster, and it requires KubeVirt v1.4 or later. If the default managedtap binding is not registered, provisioning fails. Override the binding on the NodeType or NodeClaim if you do not want to register it.

Register the binding through the KubeVirt provider's deploy.kubevirt.helmValues. The platform renders kubevirt.spec verbatim into the KubeVirt resource .spec, so add the network binding under it:

apiVersion: management.loft.sh/v1
kind: NodeProvider
metadata:
name: kubevirt-provider
spec:
kubeVirt:
deploy:
kubevirt:
enabled: true
helmValues: |
kubevirt:
spec:
configuration:
network:
binding:
managedtap:
domainAttachmentType: managedTap

If KubeVirt is managed outside the provider, register the same binding on your existing KubeVirt resource instead.

Verify the binding registration

The network binding registration follows the KubeVirt API, not a vCluster schema. Confirm the exact fields for your KubeVirt version against the KubeVirt network binding plugin documentation.

Provision a kube-ovn network​

Instead of pointing VMs at an existing kube-ovn network, you can have the platform create one for you. Set kube-ovn.vcluster.com/vpc on the NodeEnvironment to provision the kube-ovn resources on the control plane cluster. This property gates provisioning. Without it, the platform creates nothing. Provisioning runs as part of the NodeEnvironment's infrastructure lifecycle, so the network exists before any VM uses the environment and is removed when the environment is deleted.

A VM attaches to a network provisioned this way through the same attachment properties. When the NodeEnvironment carries kube-ovn.vcluster.com/subnet, those attachment defaults are filled in automatically.

kube-ovn.vcluster.com/vpc​

Type: string

Name of the kube-ovn Vpc. Setting it enables provisioning and creates the VPC, subnet, and NetworkAttachmentDefinition. When the VPC does not exist, the platform creates it. A pre-existing VPC is only modified when this NodeEnvironment manages it.

kube-ovn.vcluster.com/subnet​

Type: string Required: Yes, when kube-ovn.vcluster.com/vpc is set.

Name of the kube-ovn Subnet provisioned inside the VPC. The platform also creates a NetworkAttachmentDefinition of the same name in the provider namespace that VMs attach to.

kube-ovn.vcluster.com/subnet-cidr​

Type: string (IPv4 CIDR) Required: Yes, when kube-ovn.vcluster.com/vpc is set.

CIDR block for the subnet. Only IPv4 is supported; an IPv6 or dual-stack CIDR is rejected. The subnet's CIDR is immutable, so changing it on a live environment requires recreating the subnet.

kube-ovn.vcluster.com/external-subnet​

Type: string

Name of a pre-existing external Subnet. Setting it wires the VPC's external connectivity and default route, using the external subnet's gateway as the next hop. The platform never creates or deletes this external subnet. Required for egress.

kube-ovn.vcluster.com/egress-enabled​

Type: string ("true" or "false")

When "true", the platform creates the egress source NAT objects (OvnEip and OvnSnatRule) so VMs on the subnet can reach external networks. Requires kube-ovn.vcluster.com/external-subnet. Defaults to "false". Disabling egress after it was enabled is not supported.

The platform labels every object it provisions with kube-ovn.vcluster.com/node-environment, set to the NodeEnvironment's UID, and removes those objects by that label when the NodeEnvironment is deleted. Objects it did not create, such as a pre-existing VPC or the external subnet, are left untouched.

Expose services with the kube-ovn cloud-controller-manager​

Provisioning a kube-ovn network and attaching VMs to it gives them a VPC and overlay connectivity. To expose LoadBalancer services and direct pod routing on that same VPC, deploy the kube-ovn cloud-controller-manager in the tenant cluster. See kube-ovn cloud-controller-manager for setup. The integration here builds the VPC and wires the VMs onto it; the cloud-controller-manager then provides LoadBalancer services and pod routing on top of it.

Netris integration​

You can integrate KubeVirt VMs with Netris for automated network configuration and IP management.

netris.vcluster.com/server-cluster​

Type: string

Specifies the Netris server cluster to use for this VM. When set, vCluster Platform:

  1. Queries the Netris server cluster to collect subnet, gateway and vxlan id information.
  2. Allocates an IP address from the subnet.
  3. Creates Multus NetworkAttachment definition netris-<node provider name>-<server cluster name> using the bridge plugin with the nbr-<vnet vxlan id> bridge.
  4. Injects cloud-init network configuration with the allocated IP.
  5. Injects a network and interface into the VM template through the kubevirt.vcluster.com/network-attachment-definition property.

The interface injection itself is a generic mechanism. Netris is one way to drive it, but any NodeType or NodeClaim can set kubevirt.vcluster.com/network-attachment-definition directly to attach a VM to an existing NAD without involving Netris.

Required properties:

  • kubevirt.vcluster.com/network-interface: Interface to configure
  • kubevirt.vcluster.com/network-nameservers: DNS servers

Prerequisites:

  • Netris integration must be enabled and configured in the vCluster configuration.
  • vCluster Platform license must include the Netris feature.
  • Bridge with the nbr-<vnet vxlan id> must exist on the control plane cluster nodes.

Optional properties:

  • kubevirt.vcluster.com/network-attachment-definition

    If specified, the integration does not create another Multus NetworkAttachmentDefinition, but instead uses this one.

Example with Netris and Multus​

apiVersion: management.loft.sh/v1
kind: NodeProvider
metadata:
name: kubevirt-netris-provider
spec:
properties:
kubevirt.vcluster.com/image-url: docker://quay.io/containerdisks/ubuntu:22.04
kubevirt.vcluster.com/root-disk-size: 20Gi
kubeVirt:
clusterRef:
cluster: kubevirt-host
namespace: vcluster-platform
virtualMachineTemplate:
spec:
template:
spec:
domain:
devices:
interfaces:
- name: multus
bridge: {}
resources:
requests:
cpu: "2"
memory: "4Gi"
networks:
- name: multus
multus:
networkName: netris-bridge
nodeTypes:
- name: netris-vms
maxCapacity: 2
properties:
# Netris server cluster for network configuration
netris.vcluster.com/server-cluster: vcluster-1
# Interface to configure in the VM
kubevirt.vcluster.com/network-interface: enp1s0
# DNS servers
kubevirt.vcluster.com/network-nameservers: "8.8.8.8,8.8.4.4"
resources:
cpu: 2
memory: 4Gi

Create a NetworkAttachmentDefinition in the KubeVirt control plane cluster:

apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: netris-bridge
namespace: vcluster-platform
spec:
config: |
{
"cniVersion": "0.3.1",
"type": "bridge",
"bridge": "br-netris"
}

Use in vCluster:

privateNodes:
enabled: true
autoNodes:
- provider: kubevirt-netris-provider
static:
- name: netris-vms
quantity: 2
nodeTypeSelector:
- property: vcluster.com/node-type
value: netris-vms

# Enable Netris integration
integrations:
netris:
enabled: true
connector: netris-credentials

For more information on Netris integration, see the Netris integration documentation.