KubeVirt Provisioner¶
The kubevirt.jumpstarter.dev provisioner creates virtual machines on a remote
KubeVirt / OpenShift Virtualization
cluster and manages them through the Jumpstarter ExporterSet lifecycle.
This guide walks through configuring a two-cluster setup where:
A management cluster (e.g. kind) runs the Jumpstarter operator and lightweight exporter pods.
A target cluster (e.g. OpenShift with CNV) runs the actual VMs.
Prerequisites¶
Jumpstarter operator installed on the management cluster
A KubeVirt-capable target cluster (OpenShift Virtualization or upstream KubeVirt)
A kubeconfig for the target cluster with permissions to create
VirtualMachine,Service, andDataVolumeresourcesThe
jumpstarter-kubevirtexporter image available to the management cluster
Architecture¶
┌──────────────────────────┐ ┌──────────────────────────────┐
│ Management Cluster │ │ Target Cluster (CNV) │
│ │ │ │
│ ExporterSet controller │──────▶│ VirtualMachine CR │
│ Exporter Pod │ │ DataVolume (disk image) │
│ ├─ KubevirtPower │──────▶│ SSH Service (NodePort/LB) │
│ ├─ KubevirtConsole │ │ Forward Port Services │
│ ├─ KubevirtStorage │ │ │
│ ├─ ssh (TcpNetwork) │ │ │
│ └─ custom (TcpNetwork)│ │ │
└──────────────────────────┘ └──────────────────────────────┘
The provisioner creates VMs with RunStrategy: Halted — the Python
KubevirtPower driver’s power.on() starts the VM.
Setup¶
1. Create a credentials Secret¶
The provisioner needs a kubeconfig to reach the target cluster. Create a Secret in the management cluster’s namespace:
kubectl create secret generic kubevirt-credentials \
--from-file=kubeconfig=/path/to/target-kubeconfig \
-n jumpstarter-lab
2. Create a VirtualTargetClass¶
The VirtualTargetClass defines the provisioner backend and shared parameters for all VMs in the pool.
Minimal example (containerDisk)¶
apiVersion: virtualtarget.jumpstarter.dev/v1alpha1
kind: VirtualTargetClass
metadata:
name: kubevirt-basic
namespace: jumpstarter-lab
spec:
provisioner: kubevirt.jumpstarter.dev
bindingMode: Immediate
reclaimPolicy: Delete
credentialsSecretRef:
name: kubevirt-credentials
images:
exporter:
image: quay.io/jumpstarter-dev/jumpstarter-kubevirt:latest
parameters:
namespace: my-vms
image: quay.io/containerdisks/fedora:latest
Full example (DataVolume + SSH + port forwarding + cloud-init)¶
apiVersion: virtualtarget.jumpstarter.dev/v1alpha1
kind: VirtualTargetClass
metadata:
name: kubevirt-full
namespace: jumpstarter-lab
spec:
provisioner: kubevirt.jumpstarter.dev
bindingMode: Immediate
reclaimPolicy: Delete
credentialsSecretRef:
name: kubevirt-credentials
images:
exporter:
image: quay.io/jumpstarter-dev/jumpstarter-kubevirt:latest
parameters:
namespace: my-vms
resources:
cpu: 4
memory: "8Gi"
storage: "40Gi"
sshAccess: LoadBalancer
forwardPorts:
adb: 6520
web: 8080
cloudInit: |
#cloud-config
ssh_pwauth: false
ssh_authorized_keys:
- "ssh-ed25519 REPLACE_WITH_YOUR_PUBLIC_KEY"
runcmd:
- apt-get update
- apt-get install -y docker.io
storage:
type: dataVolume
size: "40Gi"
source:
http:
url: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
3. Create an ExporterSet¶
The ExporterSet references the VirtualTargetClass and defines how many replicas (VMs) to maintain:
apiVersion: virtualtarget.jumpstarter.dev/v1alpha1
kind: ExporterSet
metadata:
name: kubevirt-pool
namespace: jumpstarter-lab
spec:
minReplicas: 1
maxReplicas: 3
selector:
matchLabels:
app: kubevirt-pool
template:
metadata:
labels:
app: kubevirt-pool
spec:
drivers: []
virtualTargetClassName: kubevirt-full
Parameter Reference¶
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
Namespace on the target cluster for VMs |
|
string |
required for containerDisk |
Container disk image |
|
int |
|
vCPU cores |
|
string |
|
Guest memory |
|
string |
|
Root disk size |
|
string |
|
|
|
map |
|
Named ports to forward (e.g. |
|
string |
|
Cloud-init userdata |
|
string |
|
|
|
string |
|
DataVolume disk size |
|
string |
|
Storage class for the DataVolume |
|
list |
|
PV access modes |
|
string |
HTTP URL for disk image |
|
|
string |
Container registry image |
|
|
string |
Clone from existing PVC |
|
|
string |
PVC namespace |
Auto-Injected Drivers¶
The provisioner automatically injects the following drivers into the exporter configuration:
Driver |
Type |
Purpose |
|---|---|---|
|
|
Start/stop/status via KubeVirt API |
|
|
Serial console via WebSocket |
|
|
Flash disk images via DataVolume |
|
|
SSH connectivity (when |
|
|
ADB tunneling (when |
|
|
Bluetooth simulation via RootCanal test channel (when |
|
|
Wireless simulation via netsim REST API (when |
custom |
|
Other |
All KubeVirt drivers receive the remote cluster’s kubeconfig automatically.
Usage¶
Leasing an exporter¶
jmp shell -l app=kubevirt-pool
Power management¶
j power on
j power read # Running / Halted
j power off
Serial console¶
j console pipe
SSH (when sshAccess is configured)¶
The ssh TcpNetwork driver provides raw TCP access to port 22. Use it with
PexpectAdapter or connect directly via the resolved Service address.
Storage flashing¶
Replace the VM’s root disk with a new image:
j storage flash /path/to/new-image.qcow2
Lifecycle¶
Create: ExporterSet controller → provisioner creates VM + Services on target cluster → exporter pod starts on management cluster
Running: Exporter pod uses kubeconfig to control VM via KubeVirt API
Delete:
reclaimPolicy: Delete→ provisioner deletes VM and Services on target cluster. Services also haveownerReferencesto the VM as a secondary cleanup mechanism.
Networking¶
SSH Access¶
When sshAccess is set to NodePort or LoadBalancer, the provisioner:
Adds port 22 to the VMI interface
Creates a Service on the target cluster selecting the VM’s pod
Waits for a reachable Service address (LoadBalancer ingress or a cluster node address for NodePort) and injects a
TcpNetworkdriver with the resolved host and port
Port Forwarding¶
Each entry in forwardPorts follows the same pattern — a port is added to the
VMI interface, a Service is created, and a TcpNetwork driver is injected with
the resolved address.
Troubleshooting¶
VM not starting¶
Check the VirtualMachine status on the target cluster:
oc get vm -n <namespace>
oc describe vm <vm-name> -n <namespace>
DataVolume stuck importing¶
Check the DataVolume and CDI importer pod:
oc get dv -n <namespace>
oc get pods -n <namespace> | grep importer
Exporter pod not connecting¶
Verify the credentials Secret contains a valid kubeconfig:
kubectl get secret kubevirt-credentials -n jumpstarter-lab -o jsonpath='{.data.kubeconfig}' | base64 -d | head -5
Service not reachable¶
Check the Service and endpoints on the target cluster:
oc get svc -l jumpstarter.dev/provisioner=kubevirt.jumpstarter.dev -n <namespace>
oc get endpoints <svc-name> -n <namespace>