状态:草案(2026-10-17),源码 + 只读实测。 基准为 CDI
v1.66.0(提交938af7c5)与 KubeVirtv1.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. 图解:导入之路
3. 代码路径
3.1 DataVolume 控制器:决定 PVC 的形态与大小
用于导入的 DataVolume 由 pkg/controller/datavolume/import-controller.go 的 syncImport()
(:195负责。公共部分 syncDvPvcState()(controller-base.go:486)绘制要创建的 PVC 规范,在此处分支为两个字段(renderPvcSpec,
util.go:108).
spec.pvc原样复制所写内容。spec.storage使用 StorageProfile 填充空白的访问模式、卷模式和 StorageClass(pvcFromStorage, :118), 并将大小 扩容文件系统的预留空间大小(renderPvcSpecVolumeSize, :308).
。然后决定是否使用卷填充器(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 两个大小计算函数
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)使用。总结如下:
- PVC 请求 = ⌈ 1MiB 对齐(请求) × (1 + 预留比例) ⌉
- 镜像大小 = 1MiB 向下取整( ⌈ min(空闲空间, PVC 请求) ÷ (1 + 预留比例) ⌉ )
预留比例使用 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 创建的对象。它填充两个内容。
- claimPropertySets — 适合该类的访问模式和卷模式。用于填充
spec.storage留空的格子(3.1)。来源是通过供应商(provisioner)名称查找到的表(CapabilitiesByProvisionerKey(storagecapabilities.go:39),如果是表中没有的供应商则为空。如果在空白的类中不在spec.storage中编写访问模式,则无法创建 PVC。 - cloneStrategy —
reconcileCloneStrategy()(storageprofile-controller.go:247)按以下顺序选择:用户指定的值 → StorageClass 注解cdi.kubevirt.io/clone-strategy→ 供应商表的推荐值 → “如果存在 VolumeSnapshotClass 则为 snapshot,否则为 copy(主机复制)”。
当复制 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 复制的原因是同一个事实。
- 5. 局限性
cdi.kubevirt.io/storage.pod.retainAfterCompletionimporter Pod 在结束后被删除,因此未能查看分阶段日志(Info → … → Resize)。虽然可以通过带有注解 的新 DataVolume 重新测量来查看,但由于决定不再新建,因此属于未实测 - 对于“空白空间小于请求”的情况(即提供方给予的卷小于请求的大小),我们没有进行测量。由于共享存储往往不强制限制 PVC 大小,所以在我们测试的集群中并未发生这种情况。
- 关于克隆(snapshot、csi-clone、copy),我们仅阅读了代码而没有实际运行。块模式也是 未实测状态。
TT 的总结。
spec.storage和spec.pvc看起来只差一个字,但磁盘大小会相差 6%。如果您希望将客户机磁盘恰好设为 10GiB,请使用spec.storage,并且请记住 PVC 可能会稍微大一点!
参考
全部于 2026-10-17 查阅。
- CDI v1.66.0 源码: https://github.com/kubevirt/containerized-data-importer/tree/v1.66.0
- DataVolume 文档: https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/datavolumes.md
- CDIConfig 文档 (filesystemOverhead): https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/cdi-config.md
- 将大小计算加入文档的 PR #4318(审查中): https://github.com/kubevirt/containerized-data-importer/pull/4318
- Kubernetes 卷填充器: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#volume-populators-and-data-sources
- 姊妹系列《学生 VM 的诞生》第 3 篇: 磁盘即 PVC, 第 4 篇: PVC 视角下的 Overlay