TT Lab
요금제

한국어

시작하기
블로그

블로그

KubeVirt 소스 코드 읽기 6: CDI — DataVolume, importer, 크기 계산, 복제

상태: 초안 (2026-10-17), 소스 + 읽기 전용 실측. 기준은 CDI v1.66.0(커밋 938af7c5)과 KubeVirt v1.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. 그림: 가져오기의 길

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). 스토리지 클래스가 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 크기 계산 두 함수

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 에서 스토리지 클래스별 값이 있으면 그것, 없으면 전역 값을 씁니다 (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 가 만드는 객체입니다. 두 가지를 채웁니다.

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. 한계

티티 한마디. spec.storage 와 spec.pvc 는 한 글자 차이 같지만 디스크 크기가 6% 달라져요. 게스트 디스크를 딱 10GiB 로 받고 싶으면 spec.storage 를 쓰고, PVC 가 조금 더 크다는 건 기억해 두세요!

참고

모두 2026-10-17 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다