TT Lab
Blog

한국어 English 日本語 中文

Get started
Blog

Blog

Reading KubeVirt Source Code 6: CDI — DataVolume, importer, size calculation, cloning

Status: Draft (2025-10-17), Code + Read-Only Measurements. Based on CDI v1.66.0(commit 938af7c5) and KubeVirt v1.9.0 . The measurements are from DataVolumes, PVCs, CDIConfigs, and StorageProfiles that have already completed importing, as well as from inside the pods of VMs using those PVCs.

Reading KubeVirt Source Code 6: CDI

Table of Contents

1. Intuition: Attaching a "Populating Method" to a PVC

A PVC is an empty container. Since a VM needs a disk with an OS installed, someone must download an image and pour it into the container. DataVolume is a CRD that records "create a PVC like this and populate it from this source (HTTP, registry, another PVC, upload)" in a single object, and CDI is a bundle of controllers and worker pods that perform this task. Its storage and lifecycle are separate from KubeVirt, and it connects to VMs via dataVolumeTemplates . The VM controller creates the DataVolume (handleDataVolumes, vm.go:512), and the VMI controller does not create pods until the DataVolume is ready (areDataVolumesReady, datavolumes.go:116). This corresponds to "Delaying pod creation while DataVolume populates" seen in Part 3.

2. Diagram: The Path of Importing

CDI — DataVolume 하나가 채워진 PVC 가 되기까지 v1.66.0 의 기본 경로: CSI 스토리지면 볼륨 채우미(populator)를 쓴다 DataVolume (spec.storage: 10Gi, source: registry) VM 의 dataVolumeTemplates 에서 virt-controller 가 만든다 DV import 컨트롤러 datavolume/ import-controller.go VolumeImportSource 출처를 담은 CR (DV 와 같은 이름) PVC (목표) — 크기를 부풀려 만든다 dataSourceRef → VolumeImportSource · renderPvcSpecVolumeSize() import 채우미 populators/ import-populator.go PVC' (prime) 같은 크기의 쌍둥이 이름 prime-<uid> import 컨트롤러 PVC' 주석을 보고 importer 파드를 만든다 importer 파드 — DataProcessor 단계 Info → Transfer → Convert(qemu-img convert) → Resize Resize: min(빈 공간, 요청) ÷ (1 + 여분) → 1MiB 내림 = GetUsableSpace() — 게스트 디스크 크기가 여기서 정해진다 Rebind — PV 를 PVC' 에서 목표 PVC 로 옮긴다 그 뒤 PVC' 은 지운다 · DV 는 Succeeded 실측(목표 PVC 의 주석): usePopulator=true · pvcPrime=prime-… populator.progress=100.0% · pod.phase=Succeeded
Figure: Until a DataVolume Becomes a Populated PVC

3. Code Paths

3.1 DataVolume Controller: Determining the Shape and Size of the PVC

The import DataVolume is handled by pkg/controller/datavolume/import-controller.go of syncImport() (:195. The common part syncDvPvcState()(controller-base.go:486draws the spec of the PVC to be created, where two fields diverge (renderPvcSpec, util.go:108).

Next, it decides whether to use a volume populator (shouldUseCDIPopulator, controller-base.go:1316). If the storage class belongs to a CSI driver, it uses one, which can be turned off with the DataVolume annotation cdi.kubevirt.io/storage.usePopulator . When used, it creates a CR containing the source VolumeImportSource (reconcileVolumeImportSourceCR, import-controller.go:348), and makes the dataSourceRef of the target PVC point to it.

3.2 Populator: Pouring into the Twin PVC and Transferring the PV

Under Kubernetes' volume populator specification, nobody binds a PVC with dataSourceRef until the populator fills it. CDI's import populator does not touch the target PVC directly, but instead creates a twin with the same spec ( twin with the same spec(createPVCPrime, populator-base.go:194). The name is prime-<목표 PVC UID> , annotated for immediate binding, and labeled to be excluded from backups.

PVC' is watched by the legacy import controller (pkg/controller/import-controller.go). It reads the annotation to create the importer pod (createImporterPod, :531), and passes the pod status back to the annotations of PVC'. The populator side ( reconcileTargetPVC() (import-populator.go:154) copies those annotations to the target PVC to report progress. Once PVC' finishes, it cc.Rebind(...) (:206) to change the PV's claimRef to the target PVC and bind it. Since the image-containing PV changes its owner entirely, no data copying occurs. Thanks to this design, the target PVC goes through only two states from start to finish: 「not bound yet → bound fully filled」, leaving no window for anyone to attach a half-full disk in between.

3.3 Importer: Step-by-Step Processing and Resizing

The core of the importer pod is the state machine of pkg/importer/data-processor.go (ProcessDataWithPause, :241). Info(View Source Format) → Transfer…(Receive) → Convert(qemu-img convert as raw) → Resize → Complete , and step functions return the next step.

The size is determined by resize()(:294). For a filesystem volume, ResizeImage()(:331) expands the image to the smaller of the 「requested size」 and the 「available space」, where the available space is getUsableSpace() (:396) → util.GetUsableSpace() . For a block volume, it uses the raw device size as is.

3.4 Two Size Calculation Functions

10Gi 를 요청하면 PVC 와 디스크는 몇 바이트가 되나 파일시스템 여분 0.06(기본값), 실측과 계산이 바이트까지 같다 요청 spec.storage.resources.requests.storage = 10Gi GetRequiredSpace(0.06, 10Gi) ceil( 1MiB 정렬(10,737,418,240) × 1.06 ) → PVC 요청 11,381,663,335 B (실측 일치) GetUsableSpace(0.06, PVC 크기) 1MiB 내림( ceil(11,381,663,335 ÷ 1.06) ) → 디스크 이미지 10,737,418,240 B = 10 GiB 실측: qemu-img info → virtual size 10 GiB 복제 전략은 StorageProfile 이 정한다 (clone/planner.go) snapshot 기본 · 스냅샷 클래스가 있을 때 csi-clone CSI 드라이버의 볼륨 복제 copy 호스트가 바이트를 통째로 복사 실측: 공유 스토리지 클래스에 VolumeSnapshotClass 가 없어 copy 로 정해졌다 — 스마트 클론은 그 클래스가 생겨야 켜진다
Figure: Calculations determining 10Gi requests into PVC and disk sizes, and the cloning strategy

pkg/util/util.go are inverse functions of each other.

func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
	alignedSize := RoundUp(requestedSpace, DefaultAlignBlockSize)
	spaceWithOverhead := int64(math.Ceil(float64(alignedSize) * (1 + filesystemOverhead)))
	return spaceWithOverhead
}

Source: containerized-data-importer v1.66.0 pkg/util/util.go , comments removed from lines 232–241 (Apache-2.0). GetRequiredSpace, :232 is used when creating PVC requests (via the DataVolume controller InflateSizeWithOverhead ), GetUsableSpace, :223 is used when determining the image size (importer). To summarize:

For the overhead, CDIConfig's status.filesystemOverhead uses the storage class-specific value if available, and falls back to the global value otherwise (GetFilesystemOverheadForStorageClass, common/util.go:629). The global default is "0.06"(common.go:226), and the alignment unit DefaultAlignBlockSize is 1MiB (util.go:32). Block mode is not inflated (InflateSizeWithOverhead, :1572).

As a result, if you request 10Gi with spec.storage , the guest gets a 10GiB disk, and the PVC is larger by that margin. Conversely, if you request 10Gi with spec.pvc , the PVC is exactly 10Gi and the disk becomes nearly 6% smaller. Since the official documentation only states to "request a PVC large enough to hold both the image and filesystem metadata," a PR adding this calculation to the documentation (#4318, document PR by the author, currently under review) is open.

3.5 StorageProfile and Clone Strategy

StorageProfile is an object created by CDI for each storage class, populating two fields.

When a DataVolume to clone a PVC arrives, clone/planner.go 's ChooseStrategy() (:157) → computeStrategyForSourcePVC()(:297) starts from the default snapshot(:323), sequentially reflects global overrides and the StorageProfile, and falls back to host-assisted copy if snapshot is chosen but no matching VolumeSnapshotClass exists (:354). What the documentation calls smart cloneis this snapshot path (planSnapshotFromPVC, :670: snapshot → temporary PVC restore → Rebind), and csi-clone is the CSI driver's volume cloning (planCSIClone, :739), copy is a host-assisted copy where pods are spun up for both the source and target to stream bytes (planHostAssistedFromPVC, :526).

4. Measurements

Read from a small cluster running v1.66.0 (2026-10-17). The target is a single root DataVolume populated with a registry image, and a VM using it.

Size. The DataVolume used access mode RWO, requested spec.storage in 10Gi, and was on a shared storage CSI class.

What Value Calculation
DataVolume Request 10Gi = 10,737,418,240 B —
PVC spec.resources.requests.storage 11,381,663,335 B ⌈10,737,418,240 × 1.06⌉ = 11,381,663,335 ✓
PVC status.capacity.storage 11,381,663,335 B Equal
Guest Disk (qemu-img info, raw) 10,737,418,240 B = 10 GiB ⌈11,381,663,335 ÷ 1.06⌉ → 1MiB Floor = 10GiB ✓
Actual Size Occupied by Disk File 867MiB Sparse file

VMI's status.volumeStatus[].persistentVolumeClaimInfo.filesystemOverhead also had "0.06" written on it. Here is the answer to the question left open in Part 3 of "Until Student VMs Boot up" ("According to CDI docs it's 10Gi, design docs say about 9.4GiB — Unmeasured"). Using spec.storage results in 10GiB . 9.4GiB corresponds to the size when given 10Gi with spec.pvc (10Gi ÷ 1.06 → 1MiB Floor ≈ 9.43GiB).

Trace of the Filler. The target PVC's annotation still had cdi.kubevirt.io/storage.usePopulator: "true", cdi.kubevirt.io/storage.populator.pvcPrime: prime-<UID>, cdi.kubevirt.io/storage.populator.progress: 100.0%, cdi.kubevirt.io/storage.pod.phase: Succeeded, cdi.kubevirt.io/storage.contentType: kubevirt . This is the result of the filler copying PVC''s annotations to the target in Section 3.2. PVC' and the importer pod had already been deleted.

Configuration. CDIConfig's status.filesystemOverhead had global 0.06and all four storage classes set to 0.06 , and the feature gate HonorWaitForFirstConsumer was enabled.

StorageProfile. Two CSI classes on shared storage had their claimPropertySets populated with ReadWriteMany·Filesystem (drivers present in the provisioner table). Two node-local classes had empty claimPropertySets because they were provisioners not found in the table. All four classes had cloneStrategy as copy , and there was not a single VolumeSnapshotClass in the cluster. In other words, cloning a PVC in that small cluster performs a full byte-by-byte copyinstead of a smart clone. This is the exact reason why Part 4 of "Until Student VMs Boot up" chose the write layer instead of PVC cloning for each student.

5. Limitations

A word from Titi. spec.storage and spec.pvc might look like they differ by only one character, but the disk size differs by 6%. If you want to receive the guest disk as exactly 10GiB, use spec.storage , and keep in mind that the PVC will be slightly larger!

References

All accessed on 2026-10-17.

Sign in to like

Comments

No comments yet.

Sign in to leave a comment