상태: 초안 (2026-10-17), 소스 + 읽기 전용 실측. 기준은 CDI
v1.66.0(커밋938af7c5)과 KubeVirtv1.9.0입니다. 실측은 이미 가져오기가 끝난 DataVolume·PVC·CDIConfig·StorageProfile 과, 그 PVC 를 쓰는 VM 의 파드 안 디스크를 읽은 것입니다.
KubeVirt 소스 코드 읽기 6: CDI
목차
1. 직관: PVC 에 「채우는 방법」 을 붙인 것
PVC 는 빈 그릇입니다. VM 은 OS 가 깔린 디스크가 필요하니, 누군가 이미지를 받아 그릇에 부어야 합니다. DataVolume 은
「이런 PVC 를 만들고, 이 출처(HTTP·레지스트리·다른 PVC·업로드)로 채워라」 를 한 객체에 적은 CRD 이고, CDI 는 그 일을 하는
컨트롤러와 일꾼 파드 묶음입니다. KubeVirt 와는 저장소도 판도 따로이고, VM 의 dataVolumeTemplates 로 이어집니다. VM 컨트롤러가
DataVolume 을 만들고(handleDataVolumes, vm.go:512),
VMI 컨트롤러는 DataVolume 이 준비될 때까지 파드를 만들지 않습니다(areDataVolumesReady,
datavolumes.go:116). 3편에서 본
「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 로 채우고(pvcFromStorage, :118), 크기를 파일시스템 여분만큼 부풀립니다(renderPvcSpecVolumeSize, :308).
그다음 볼륨 채우미를 쓸지 정합니다(shouldUseCDIPopulator,
controller-base.go:1316).
스토리지 클래스가 CSI 드라이버의 것이면 쓰고, DataVolume 주석 cdi.kubevirt.io/storage.usePopulator 로 끌 수 있습니다. 쓰면 출처를 담은
VolumeImportSource CR 을 만들고(reconcileVolumeImportSourceCR, import-controller.go:348),
목표 PVC 의 dataSourceRef 가 그것을 가리키게 해서 만듭니다.
3.2 채우미: 쌍둥이 PVC' 에 붓고 PV 를 옮긴다
쿠버네티스의 볼륨 채우미 규약에서는 dataSourceRef 가 붙은 PVC 를 채우미가 채울 때까지 아무도 묶지(bind) 않습니다. CDI 의 import 채우미는
목표 PVC 를 직접 만지지 않고 같은 스펙의 쌍둥이를 만듭니다(createPVCPrime,
populator-base.go:194).
이름은 prime-<목표 PVC UID> 이고, 즉시 묶이도록 주석을 달고, 백업 대상에서 빼는 라벨을 붙입니다.
PVC' 은 예전부터 있던 import 컨트롤러(pkg/controller/import-controller.go)가 봅니다. 주석을 읽어 importer 파드를 만들고
(createImporterPod, :531),
파드의 상태를 다시 PVC' 의 주석으로 옮깁니다. 채우미 쪽 reconcileTargetPVC()
(import-populator.go:154)는
그 주석을 목표 PVC 로 베끼며 진행률을 보고하다가, PVC' 이 끝나면 cc.Rebind(...)
(:206) 로
PV 의 claimRef 를 목표 PVC 로 바꿔 붙입니다. 이미지가 든 PV 가 통째로 주인을 바꾸는 것이라 데이터 복사가 없습니다. 이 설계 덕분에
목표 PVC 는 처음부터 끝까지 「아직 안 묶임 → 다 채워진 채로 묶임」 두 상태만 거치고, 중간에 반쯤 찬 디스크를 누가 붙일 틈이 없습니다.
3.3 importer: 단계별 처리와 크기 조정
importer 파드의 본체는 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 에서 스토리지 클래스별 값이 있으면 그것, 없으면 전역 값을 씁니다
(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 가 만드는 객체입니다. 두 가지를 채웁니다.
- claimPropertySets — 이 클래스에 맞는 접근 모드·볼륨 모드.
spec.storage가 비워 둔 칸을 채우는 데 쓰입니다(3.1). 출처는 공급자 (provisioner) 이름으로 찾는 표CapabilitiesByProvisionerKey(storagecapabilities.go:39) 이고, 표에 없는 공급자면 비어 있습니다. 비어 있는 클래스에서spec.storage에 접근 모드를 적지 않으면 PVC 를 만들 수 없습니다. - cloneStrategy —
reconcileCloneStrategy()(storageprofile-controller.go:247)가 사용자가 정한 값 → 스토리지 클래스 주석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 복원 → Rebind)이고, csi-clone 은 CSI 드라이버의 볼륨 복제(planCSIClone, :739),
copy 는 원본·대상에 파드를 하나씩 띄워 바이트를 흘리는 호스트 복사(planHostAssistedFromPVC, :526)입니다.
4. 실측
v1.66.0 이 깔린 작은 클러스터에서 읽었습니다(2026-10-17). 대상은 레지스트리 이미지로 채운 루트 DataVolume 하나와, 그것을 쓰는 VM 입니다.
크기. DataVolume 은 spec.storage 에 접근 모드 RWO, 요청 10Gi, 공유 스토리지의 CSI 클래스였습니다.
| 무엇 | 값 | 계산 |
|---|---|---|
| DataVolume 요청 | 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 | 같음 |
게스트 디스크(qemu-img info, raw) |
10,737,418,240 B = 10 GiB | ⌈11,381,663,335 ÷ 1.06⌉ → 1MiB 내림 = 10GiB ✓ |
| 디스크 파일이 실제로 차지하는 크기 | 867MiB | 성긴(sparse) 파일 |
VMI 의 status.volumeStatus[].persistentVolumeClaimInfo.filesystemOverhead 에도 "0.06" 이 적혀 있었습니다. 「학생 VM 이 뜨기까지」
3편이 「CDI 문서대로면 10Gi, 설계 문서는 약 9.4GiB — 미실측」 으로 남겨 둔 물음의 답이 여기 있습니다. spec.storage 를 쓰면 10GiB 입니다.
9.4GiB 는 spec.pvc 로 10Gi 를 줬을 때의 크기(10Gi ÷ 1.06 → 1MiB 내림 ≈ 9.43GiB)에 해당합니다.
채우미의 흔적. 목표 PVC 의 주석에 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
가 남아 있었습니다. 3.2절에서 채우미가 PVC' 의 주석을 목표로 베낀 결과입니다. PVC' 과 importer 파드는 이미 지워져 있었습니다.
설정. CDIConfig 의 status.filesystemOverhead 는 전역 0.06, 스토리지 클래스 넷 모두 0.06 이었고, 기능 게이트
HonorWaitForFirstConsumer 가 켜져 있었습니다.
StorageProfile. 공유 스토리지의 CSI 클래스 둘은 claimPropertySets 가 ReadWriteMany·Filesystem 으로 채워져 있었습니다(공급자 표에 있는
드라이버). 노드 로컬 클래스 둘은 표에 없는 공급자라 claimPropertySets 가 비어 있었습니다. 네 클래스 모두 cloneStrategy 가 copy 였고,
클러스터에 VolumeSnapshotClass 가 하나도 없었습니다. 즉 그 작은 클러스터에서 PVC 를 복제하면 스마트 클론이 아니라 바이트를 통째로 복사합니다.
「학생 VM 이 뜨기까지」 4편이 학생마다 PVC 복제 대신 쓰기 층을 고른 까닭과 같은 사실입니다.
5. 한계
- importer 파드는 끝난 뒤 지워져 단계별 로그(Info → … → Resize)를 보지 못했습니다. 주석
cdi.kubevirt.io/storage.pod.retainAfterCompletion을 단 새 DataVolume 으로 다시 재면 볼 수 있지만, 새로 만들지 않기로 해서 미실측입니다. - 「빈 공간이 요청보다 작은」 경우(공급자가 요청보다 작은 볼륨을 준 경우)는 재지 않았습니다. 공유 스토리지는 PVC 크기를 강제하지 않는 경우가 많아 잰 클러스터에서는 일어나지 않았습니다.
- 복제(snapshot·csi-clone·copy)는 코드만 읽었고 돌려 보지 않았습니다. 블록 모드도 미실측입니다.
티티 한마디.
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
- 쿠버네티스 볼륨 채우미: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#volume-populators-and-data-sources
- 자매 시리즈 「학생 VM 이 뜨기까지」 3편: 디스크는 PVC 다, 4편: PVC 관점의 오버레이