CDI Operations: DataVolumes, Imports, and Upload
Overview
The Containerized Data Importer (CDI) is the component responsible for all VM disk provisioning in OpenShift Virtualization. Every time you create a VM with a disk image — whether from an HTTP URL, a container registry, a local file upload, or a clone of an existing PVC — CDI handles the data transfer, format conversion, and PVC population behind the scenes.
This tutorial explains CDI architecture, walks through each import source type, and covers operational topics like progress monitoring, automated boot source updates with DataImportCron, scratch space, resource limits, and troubleshooting stuck imports.
What You Will Learn
-
How CDI fits into the OpenShift Virtualization stack
-
How to import disk images from HTTP, registry, upload, and PVC clone sources
-
How to monitor DataVolume progress and understand retry behavior
-
How to automate boot source updates with DataImportCron
-
How to configure CDI scratch space, resource limits, and filesystem overhead
-
How to diagnose and fix common CDI failures
Prerequisites
-
OpenShift 4.18+ with OpenShift Virtualization operator installed
-
Cluster admin access (
system:adminor equivalent) -
ocCLI andvirtctlCLI installed -
Default StorageClass configured (e.g.,
lvms-vg1)
Step 1: Understand CDI Architecture
CDI runs as a set of pods in the openshift-cnv namespace. The main components are:
-
cdi-operator — manages the lifecycle of CDI itself (upgrades, configuration)
-
cdi-apiserver — validates DataVolume and DataImportCron resources
-
cdi-deployment (cdi-controller) — watches DataVolume objects and creates importer or cloner pods to populate PVCs
-
cdi-uploadproxy — accepts disk image uploads from
virtctl image-uploadand routes data to the target PVC
When you create a DataVolume, the CDI controller creates a short-lived worker pod (importer, cloner, or uploader) that mounts the target PVC, writes the disk data, and exits. Once the worker pod completes, the DataVolume transitions to Succeeded and the PVC is ready for a VM to use.
Verify CDI pods are running on your cluster:
oc get pods -n openshift-cnv -l app.kubernetes.io/component=storage
NAME READY STATUS RESTARTS AGE
cdi-apiserver-7f9c7b5b4-xk2lm 1/1 Running 0 5d
cdi-deployment-6b8c5d8f9-r4tnp 1/1 Running 0 5d
cdi-operator-5f4c7d8b6-jh9wz 1/1 Running 0 5d
cdi-uploadproxy-7d9f8c6b5-m3kpq 1/1 Running 0 5d
Check the CDI custom resource for current configuration:
oc get cdi cdi-kubevirt-hyperconverged
The CDI resource controls global settings like resource limits, scratch space overhead, and upload proxy configuration. You will modify some of these settings later in this tutorial.
Step 2: Understand DataVolumes and Their Lifecycle
A DataVolume is a CDI custom resource that wraps a PVC with instructions for populating it. When you create a DataVolume, CDI:
-
Creates a PVC with the requested size and access mode.
-
Launches an importer (or cloner/uploader) pod that mounts the PVC.
-
Streams data from the source, converting formats as needed (qcow2 to raw, gzip decompression, etc.).
-
Reports progress through the DataVolume status.
-
Marks the DataVolume as
Succeededwhen the data transfer completes.
DataVolumes go through these phases:
| Phase | Description |
|---|---|
Pending |
PVC is being created or waiting for storage provisioning. |
WaitForFirstConsumer |
PVC uses |
ImportScheduled |
CDI has scheduled the import but the worker pod is not running yet. |
ImportInProgress |
Worker pod is actively transferring data. Progress percentage is updated. |
CloneScheduled / CloneInProgress |
Same as import phases, but for PVC-to-PVC clones. |
UploadScheduled / UploadReady |
DataVolume is waiting for data to be uploaded via |
Succeeded |
Data transfer completed. PVC is ready to use. |
Failed |
Data transfer failed. Check events and worker pod logs. |
Create a namespace for the exercises in this tutorial:
oc create namespace cdi-operations-demo
Step 3: Import from an HTTP Source
HTTP import is the most common method for pulling publicly available disk images. CDI downloads the file, detects its format (raw, qcow2, vmdk, gzip, xz), and converts it to raw format on the target PVC.
oc apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: fedora-http
namespace: cdi-operations-demo
annotations:
cdi.kubevirt.io/storage.bind.immediate.requested: "true"
spec:
source:
http:
url: "https://download.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2"
storage:
resources:
requests:
storage: 10Gi
EOF
The annotation cdi.kubevirt.io/storage.bind.immediate.requested: "true" forces the PVC to bind immediately. Without it, StorageClasses that use WaitForFirstConsumer binding mode will leave the PVC pending until a pod schedules on the node.
|
Monitor the import progress:
oc get dv fedora-http -n cdi-operations-demo -w
NAME PHASE PROGRESS RESTARTS AGE
fedora-http ImportScheduled N/A 5s
fedora-http ImportInProgress 0.00% 15s
fedora-http ImportInProgress 22.45% 30s
fedora-http ImportInProgress 67.89% 60s
fedora-http Succeeded 100.0% 90s
Press Ctrl+C once the DataVolume shows Succeeded.
You can also check the importer pod directly to see download speed and conversion activity:
oc get pods -n cdi-operations-demo -l cdi.kubevirt.io/storage.import.importPvcName=fedora-http
oc logs -n cdi-operations-demo -l cdi.kubevirt.io/storage.import.importPvcName=fedora-http
Once succeeded, verify the resulting PVC:
oc get pvc fedora-http -n cdi-operations-demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
fedora-http Bound pvc-xxxx 10Gi RWO lvms-vg1 2m
HTTP Import with Authentication
For HTTP sources that require authentication, create a Secret with the credentials and reference it in the DataVolume:
oc create secret generic http-auth-secret \
--from-literal=username=myuser \
--from-literal=password=mypassword \
-n cdi-operations-demo
Then reference the Secret in the DataVolume spec:
spec:
source:
http:
url: "https://private-server.example.com/images/rhel9.qcow2"
secretRef: http-auth-secret
HTTP Import with Custom Certificates
If the HTTP server uses a self-signed or internal CA certificate, create a ConfigMap with the CA bundle and reference it:
oc create configmap http-ca-cert \
--from-file=ca-bundle.crt=/path/to/ca-bundle.crt \
-n cdi-operations-demo
Then add it to the DataVolume:
spec:
source:
http:
url: "https://internal-server.example.com/images/rhel9.qcow2"
certConfigMap: http-ca-cert
Step 4: Import from a Container Registry
Container registries can host disk images packaged as container images (ContainerDisks). CDI uses the node’s container runtime to pull the image and extract the disk file.
oc apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: fedora-registry
namespace: cdi-operations-demo
annotations:
cdi.kubevirt.io/storage.bind.immediate.requested: "true"
spec:
source:
registry:
url: "docker://quay.io/containerdisks/fedora:latest"
pullMethod: node
storage:
resources:
requests:
storage: 10Gi
EOF
The pullMethod field controls how CDI retrieves the image:
-
node— uses the node’s container runtime (CRI-O) to pull the image. This method reuses the node’s pull secrets and registry mirrors. -
pod— CDI pulls the image directly from within the importer pod. Use this when the node does not have access to the registry.
Monitor until Succeeded:
oc get dv fedora-registry -n cdi-operations-demo -w
Press Ctrl+C when the DataVolume reaches Succeeded.
Step 5: Upload a Disk Image from Your Workstation
The virtctl image-upload command streams a local disk image file to the CDI upload proxy, which writes it to a PVC. This method is best for one-off imports where the image exists on your workstation and is not hosted on a web server or registry.
To upload, you need a local qcow2 or raw image. For this example, download a small Fedora Cloud image first:
curl -LO https://download.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2
Then upload it:
virtctl image-upload dv fedora-upload \
--image-path=./Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2 \
--size=10Gi \
--namespace=cdi-operations-demo \
--insecure \
--force-bind
Key flags:
-
dv fedora-upload— creates a DataVolume namedfedora-upload(you can also usepvcinstead ofdvto create a bare PVC) -
--size— PVC size, must be at least the virtual size of the image -
--insecure— skip TLS verification for the upload proxy endpoint (required if using self-signed certificates) -
--force-bind— force immediate PVC binding forWaitForFirstConsumerStorageClasses
PVC cdi-operations-demo/fedora-upload created
Uploading data to https://cdi-uploadproxy-openshift-cnv.apps...
420.00 MiB / 420.00 MiB [=================================] 100.00% 1m30s
Uploading data completed successfully, waiting for processing to complete
Processing completed successfully
Verify:
oc get dv fedora-upload -n cdi-operations-demo
Step 6: Clone an Existing PVC
PVC cloning creates a copy of an existing disk. CDI supports two cloning strategies:
-
Host-assisted cloning — CDI creates a source pod and a target pod, streaming data between them. This works with any storage backend.
-
Smart cloning — the CSI driver performs a snapshot-based clone, which is faster and does not require data streaming. CDI uses smart cloning automatically when the CSI driver supports volume snapshots.
Clone the fedora-http PVC created in Step 3:
oc apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: fedora-clone
namespace: cdi-operations-demo
annotations:
cdi.kubevirt.io/storage.bind.immediate.requested: "true"
spec:
source:
pvc:
name: fedora-http
namespace: cdi-operations-demo
storage:
resources:
requests:
storage: 10Gi
EOF
Monitor the clone:
oc get dv fedora-clone -n cdi-operations-demo -w
NAME PHASE PROGRESS RESTARTS AGE
fedora-clone CloneScheduled N/A 5s
fedora-clone CloneInProgress 10.00% 15s
fedora-clone Succeeded 100.0% 45s
Press Ctrl+C when the clone reaches Succeeded.
Cross-Namespace Cloning
To clone a PVC from a different namespace, specify the source namespace in spec.source.pvc.namespace. The CDI service account needs permission to read PVCs in the source namespace. CDI creates the required RBAC automatically when the requesting user has get and list permissions on PVCs in the source namespace.
Step 7: Monitor DataVolume Progress and Handle Retries
CDI reports progress through the DataVolume .status fields. You can query these programmatically:
oc get dv -n cdi-operations-demo -o custom-columns=NAME:.metadata.name,PHASE:.status.phase,PROGRESS:.status.progress,CONDITIONS:.status.conditions[0].reason
Retry Behavior
When an import fails (network timeout, HTTP 500, transient error), CDI retries automatically. The default retry count is 3. Between retries, CDI uses exponential backoff.
You can see retry counts in the DataVolume status:
oc get dv -n cdi-operations-demo -o custom-columns=NAME:.metadata.name,PHASE:.status.phase,RESTARTS:.status.restartCount
If all retries are exhausted, the DataVolume transitions to Failed. To retry a failed DataVolume, delete it and recreate it:
oc delete dv <failed-dv-name> -n cdi-operations-demo
Then re-apply the DataVolume manifest.
Step 8: Automate Boot Source Updates with DataImportCron
DataImportCron automatically imports disk images on a schedule. This is how OpenShift Virtualization keeps boot source images (Fedora, RHEL, CentOS Stream, Windows) up to date in the openshift-virtualization-os-images namespace. You can create your own DataImportCron objects for custom images.
oc apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataImportCron
metadata:
name: fedora-image-cron
namespace: cdi-operations-demo
spec:
schedule: "0 3 * * 1"
managedDataSource: fedora-latest
garbageCollect: Outdated
importsToKeep: 2
template:
spec:
source:
registry:
url: "docker://quay.io/containerdisks/fedora:latest"
pullMethod: node
storage:
resources:
requests:
storage: 10Gi
EOF
Key fields:
-
schedule— cron expression.0 3 * * 1runs every Monday at 03:00 UTC. -
managedDataSource— name of the DataSource that DataImportCron manages. VMs reference this DataSource to get the latest image. -
garbageCollect: Outdated— automatically deletes old DataVolumes when a new import completes. -
importsToKeep: 2— retains the two most recent imports. This lets you roll back to a previous version if needed.
Verify the DataImportCron:
oc get dataimportcron -n cdi-operations-demo
NAME LASTIMPORT NEXTIMPORT LASTIMPORTNAMESPACE
fedora-image-cron Mon, 21 Jul 03:00 cdi-operations-demo
DataImportCron creates a DataSource that always points to the latest imported PVC. VMs that reference this DataSource automatically get the newest image when they are created:
spec:
dataVolumeTemplates:
- spec:
sourceRef:
kind: DataSource
name: fedora-latest
namespace: cdi-operations-demo
storage:
resources:
requests:
storage: 10Gi
Step 9: Configure CDI Scratch Space and Filesystem Overhead
Scratch Space
CDI uses scratch space (a temporary PVC) during format conversions. When importing a qcow2 image, CDI first writes the qcow2 data to the scratch PVC, then converts it to raw format on the target PVC. Scratch space is also used for gzip and xz decompression.
If the scratch space PVC fails to provision (no StorageClass, insufficient capacity), the import fails with an error about scratch space. To specify which StorageClass CDI should use for scratch space:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{"spec":{"config":{"scratchSpaceStorageClass":"lvms-vg1"}}}'
If you do not set this, CDI uses the default StorageClass for scratch PVCs.
Verify the current scratch space configuration:
oc get cdi cdi-kubevirt-hyperconverged -o jsonpath='{.spec.config.scratchSpaceStorageClass}'
echo
| Not all imports require scratch space. Raw image imports and PVC clones typically do not need scratch space. Scratch space is required for qcow2-to-raw conversion and decompression of compressed images. |
Filesystem Overhead
When using Filesystem mode PVCs (the default for most StorageClasses), the underlying storage file system reserves a percentage of the volume for metadata. CDI accounts for this by requesting slightly more space than the image requires.
The default filesystem overhead is 5.5%. You can adjust it globally or per StorageClass:
oc get cdi cdi-kubevirt-hyperconverged -o jsonpath='{.spec.config.filesystemOverhead}'
echo
To change the global overhead percentage:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{"spec":{"config":{"filesystemOverhead":{"global":"0.055"}}}}'
To set a per-StorageClass override:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{"spec":{"config":{"filesystemOverhead":{"storageClass":{"lvms-vg1":"0.05"}}}}}'
Step 10: Configure CDI Resource Limits
CDI importer and cloner pods consume CPU and memory during data transfer. On busy clusters, you may want to limit these resources to prevent import jobs from starving other workloads.
View the current resource configuration:
oc get cdi cdi-kubevirt-hyperconverged -o json | jq '.spec.config.podResourceRequirements'
Set resource limits for CDI worker pods:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{
"spec": {
"config": {
"podResourceRequirements": {
"limits": {
"cpu": "2",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "60Mi"
}
}
}
}
}'
These limits apply to all CDI worker pods (importers, cloners, uploaders). Setting the CPU limit too low slows down format conversion. Setting the memory limit too low can cause OOMKilled errors during decompression of large compressed images.
Step 11: Troubleshoot Common CDI Issues
DataVolume Stuck in ImportScheduled
The DataVolume stays in ImportScheduled and no importer pod appears.
Check if the PVC is bound:
oc get pvc -n cdi-operations-demo
If the PVC shows Pending, check events:
oc describe pvc <pvc-name> -n cdi-operations-demo
Common causes:
-
No default StorageClass — set one with
oc patch storageclass <name> -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' -
WaitForFirstConsumer — add the
cdi.kubevirt.io/storage.bind.immediate.requested: "true"annotation to the DataVolume -
Insufficient storage capacity — check available space with
oc get pv
DataVolume Stuck in ImportInProgress at 0%
The importer pod is running but progress stays at 0%.
Check the importer pod logs:
oc logs -n cdi-operations-demo -l cdi.kubevirt.io/storage.import.importPvcName=<dv-name>
Common causes:
-
Slow or unreachable source URL — test the URL from within the cluster:
oc run curl-test --rm -it --image=curlimages/curl — curl -sI <url> -
TLS certificate errors — add a CA certificate ConfigMap or use
certConfigMapin the DataVolume spec -
DNS resolution failure — check DNS from within the cluster
Scratch Space Errors
If the import fails with a message about scratch space:
Unable to create scratch space PVC: no default storage class found
Set the scratch space StorageClass:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{"spec":{"config":{"scratchSpaceStorageClass":"lvms-vg1"}}}'
Then delete and recreate the failed DataVolume.
Importer Pod OOMKilled
If the importer pod is killed with OOMKilled status, the memory limit is too low for the decompression or conversion operation. Increase the memory limit:
oc patch cdi cdi-kubevirt-hyperconverged --type merge -p '{"spec":{"config":{"podResourceRequirements":{"limits":{"memory":"2Gi"}}}}}'
Slow Transfers
If imports take longer than expected:
-
Check CDI worker pod resource limits. Low CPU limits slow down qcow2-to-raw conversion.
-
Check network connectivity between the importer pod and the source.
-
For HTTP sources, check if the server supports range requests. CDI can resume interrupted downloads if the server supports HTTP
Rangeheaders.
Cleanup
Delete the namespace to remove all resources created in this tutorial:
oc delete namespace cdi-operations-demo
Verify:
oc get namespace cdi-operations-demo
Error from server (NotFound): namespaces "cdi-operations-demo" not found
If you changed CDI global configuration (scratch space, resource limits, filesystem overhead) and want to revert, patch the CDI resource back to its defaults or remove the fields you added:
oc patch cdi cdi-kubevirt-hyperconverged --type json -p '[
{"op": "remove", "path": "/spec/config/scratchSpaceStorageClass"},
{"op": "remove", "path": "/spec/config/podResourceRequirements"}
]'
| Only run the cleanup patch above if you want to revert CDI configuration to defaults. If you set these values intentionally for your environment, leave them in place. |
Summary
In this tutorial you learned how to:
-
Identify CDI components and verify they are running in the
openshift-cnvnamespace -
Understand the DataVolume lifecycle and its phases from Pending through Succeeded
-
Import disk images from HTTP URLs, container registries, local uploads, and PVC clones
-
Monitor import progress and understand CDI retry behavior
-
Automate boot source updates with DataImportCron
-
Configure scratch space, filesystem overhead, and CDI worker pod resource limits
-
Diagnose and fix common CDI failures including stuck imports, scratch space errors, and OOMKilled pods