TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

阅读 KubeVirt 源代码 6:CDI — DataVolume、importer、大小计算、克隆

状态:草案(2026-10-17),源码 + 只读实测。 基准为 CDI v1.66.0(提交 938af7c5)与 KubeVirt v1.9.0 。实测对象是已经完成导入的 DataVolume、PVC、CDIConfig、StorageProfile,以及读取使用该 PVC 的 VM 的 Pod 内部的磁盘。

阅读 KubeVirt 源代码 6:CDI

目录

1. 直觉:为 PVC 附加「填充方法」

PVC 是一个空容器。由于虚拟机需要一个安装了操作系统的磁盘,因此必须有人下载镜像并将其注入容器中。 DataVolume 是一个将“创建这样的 PVC,并使用此来源(HTTP、注册表、其他 PVC、上传)进行填充”记录在单个对象中的 CRD,而 CDI 则是负责执行该任务的控制器和工作 Pod 集合。它的存储和版本与 KubeVirt 独立,并通过 VM 的 dataVolumeTemplates 相连。VM 控制器创建 DataVolume(handleDataVolumes, vm.go:512),而 VMI 控制器在 DataVolume 准备好之前不会创建 Pod(areDataVolumesReady, datavolumes.go:116)。我们在第 3 部分中看到的“在 DataVolume 填充时延迟 Pod 创建(Delaying pod creation while DataVolume populates)”即位于此处。

2. 图解:导入之路

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
图:DataVolume 变为已填充 PVC 的过程

3. 代码路径

3.1 DataVolume 控制器:决定 PVC 的形态与大小

用于导入的 DataVolume 由 pkg/controller/datavolume/import-controller.go 的 syncImport() (:195负责。公共部分 syncDvPvcState()(controller-base.go:486)绘制要创建的 PVC 规范,在此处分支为两个字段(renderPvcSpec, util.go:108).

。然后决定是否使用卷填充器(shouldUseCDIPopulator, controller-base.go:1316)。如果 StorageClass 属于 CSI 驱动程序则使用它,也可以通过 DataVolume 注解 cdi.kubevirt.io/storage.usePopulator 来关闭。如果使用,则创建包含来源的 VolumeImportSource CR(reconcileVolumeImportSourceCR, import-controller.go:348),并使目标 PVC 的 dataSourceRef 指向它来创建。

3.2 填充器:注入双胞胎 PVC'' 并迁移 PV

在 Kubernetes 的卷填充器规范中,在填充器填充带有 dataSourceRef 的 PVC 之前,任何人不得将其绑定(bind)。CDI 的 import 填充器不直接触摸目标 PVC,而是创建一个 规格相同的双胞胎 PVC'(createPVCPrime, populator-base.go:194)。名称为 prime-<목표 PVC UID> ,为其添加注解以使其立即绑定,并附加将其从备份目标中排除的标签。

PVC' 由长期存在的 import 控制器(pkg/controller/import-controller.go)监视。它读取注解以创建 importer Pod(createImporterPod, :531),并将 Pod 的状态传回 PVC' 的注解中。填充器端( reconcileTargetPVC() (import-populator.go:154)将该注解复制到目标 PVC 并报告进度,当 PVC' 完成时,通过 cc.Rebind(...) (:206将 PV 的 claimRef 更改为目标 PVC 并进行绑定。由于包含镜像的 PV 整体更换了所有者,因此不存在数据复制。由于此设计,目标 PVC 从头到尾只经历两种状态:“尚未绑定 → 充满后绑定”,中间没有任何机会让任何人挂载半满的磁盘。

3.3 importer:分阶段处理与大小调整

importer Pod 的主体是 pkg/importer/data-processor.go 的状态机(ProcessDataWithPause, :241). Info(查看来源格式)→ Transfer…(接收)→ Convert(qemu-img convert (转换为 raw)→ Resize → Complete ),阶段函数会返回下一个阶段。

大小由 resize()(:294决定。如果是文件系统卷, ResizeImage()(:331会在「请求的大小」和「可用空间」中较小的一个范围内增大镜像,可用空间即 getUsableSpace() (:396) → util.GetUsableSpace() 。如果是块卷,则直接使用设备大小。

3.4 两个大小计算函数

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 로 정해졌다 — 스마트 클론은 그 클래스가 생겨야 켜진다
图:10Gi 请求变为 PVC 和磁盘大小的计算方式,以及克隆策略

pkg/util/util.go 的两个函数互为逆操作。

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

来源:去除 containerized-data-importer v1.66.0 pkg/util/util.go 第 232–241 行注释后的代码(Apache-2.0)。 GetRequiredSpace, :232 在创建 PVC 请求时使用(DataVolume 控制器通过 InflateSizeWithOverhead ),而 GetUsableSpace, :223 则在确定镜像大小时(importer)使用。总结如下:

预留比例使用 CDIConfig 的 status.filesystemOverhead 中对应 StorageClass 的值(如果存在),否则使用全局值(GetFilesystemOverheadForStorageClass, common/util.go:629)。全局默认值为 "0.06"(common.go:226),对齐单位 DefaultAlignBlockSize 为 1MiB(util.go:32)。块模式不会被放大(InflateSizeWithOverhead, :1572).

结果是,如果使用 spec.storage 请求 10Gi,客机将获得 10GiB 的磁盘,而 PVC 会相应更大。 相反,如果使用 spec.pvc 请求 10Gi,则 PVC 正好是 10Gi,而磁盘会缩小近 6%。官方文档仅写着“请求足够大的 PVC 以容纳镜像和文件系统元数据”,因此添加了将此计算过程补充到文档中的 PR(#4318,由作者提交的文档 PR,正在评审中)。

3.5 StorageProfile 与克隆策略

StorageProfile 是 CDI 为每个 StorageClass 创建的对象。它填充两个内容。

当复制 PVC 的 DataVolume 到来时, clone/planner.go 的 ChooseStrategy() (:157) → computeStrategyForSourcePVC()(:297从默认的 snapshot(:323开始,依次反映全局覆盖和 StorageProfile,如果是 snapshot 但没有匹配的 VolumeSnapshotClass,则退化为主机复制(:354)。文档中提到的 智能克隆即此 snapshot 路径(planSnapshotFromPVC, :670:快照 → 恢复临时 PVC → 重新绑定), csi-clone 是 CSI 驱动程序的卷克隆(planCSIClone, :739), copy ),而planHostAssistedFromPVC, :526是在源和目标上各启动一个 Pod 以流式传输字节的主机复制(

)。

4. 实测

在安装了 v1.66.0 的小型集群中进行了读取(2026-10-17)。目标是一个由注册表镜像填充的根 DataVolume 以及使用它的 VM。 大小。 spec.storage DataVolume 在 10Gi上的访问模式为 RWO,请求大小为

,属于共享存储的 CSI 类。 内容 值
计算 DataVolume 请求 —
10Gi = 10,737,418,240 B spec.resources.requests.storage PVC ⌈10,737,418,240 × 1.06⌉ = 11,381,663,335 ✓
11,381,663,335 B status.capacity.storage PVC 11,381,663,335 B
相同qemu-img info客机磁盘( ,raw) 10,737,418,240 B = 10 GiB
⌈11,381,663,335 ÷ 1.06⌉ → 1MiB 向下取整 = 10GiB ✓ 磁盘文件实际占用的空间大小 867MiB

稀疏(sparse)文件 status.volumeStatus[].persistentVolumeClaimInfo.filesystemOverhead VMI 的 "0.06" 中也写着 spec.storage 。第 3 部分留下的疑问“直到学生 VM 启动为止,根据 CDI 文档为 10Gi,设计文档约为 9.4GiB — 未实测”的答案就在这里。如果使用 ,则是 10GiB spec.pvc 。9.4GiB 对应于使用

给予 10Gi 时的尺寸(10Gi ÷ 1.06 → 1MiB 向下取整 ≈ 9.43GiB)。 填充器的痕迹。 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 目标 PVC 的注解中留下了

。这是第 3.2 节中填充器将 PVC' 的注解复制到目标的结果。PVC' 和 importer Pod 已经被删除。 设置。 status.filesystemOverhead CDIConfig 的 0.06的全局 0.06 和四个 StorageClass 均为 HonorWaitForFirstConsumer ,且功能门控

已启用。 StorageProfile。 ReadWriteMany·Filesystem 两个共享存储的 CSI 类的 claimPropertySets 填充为 copy (供应商表中的驱动程序)。两个节点本地类由于是表中没有的供应商,因此 claimPropertySets 为空。四个类的 cloneStrategy 均为 ,并且集群中没有任何 VolumeSnapshotClass。也就是说,在该小型集群中复制 PVC 时,执行的不是智能克隆,而是完整复制字节

。这与“直到学生 VM 启动为止”第 4 部分中每个学生选择写入层而不是 PVC 复制的原因是同一个事实。

TT 的总结。 spec.storage 和 spec.pvc 看起来只差一个字,但磁盘大小会相差 6%。如果您希望将客户机磁盘恰好设为 10GiB,请使用 spec.storage ,并且请记住 PVC 可能会稍微大一点!

参考

全部于 2026-10-17 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论