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のポッド内ディスクを読み取ったものです。

KubeVirtソースコードリーディング 6: CDI

目次

1. イントロダクション:PVCに「書き込む方法」を付加したもの

PVCは空の器です。VMにはOSがインストールされたディスクが必要なため、誰かがイメージを取得して器に書き込む必要があります。 DataVolume は 「このようなPVCを作成し、このソース(HTTP、レジストリ、別のPVC、アップロード)から書き込め」という内容を1つのオブジェクトに記述した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の仕様を描画しますが、ここで2つのフィールドが分岐します(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を移動させる

Kubernetesのボリュームポピュレータの仕様では、 dataSourceRef が付いたPVCをポピュレータが書き込むまで誰もバインドしません。CDIのimportポピュレータは、 ターゲットPVCを直接操作せず、 同じ仕様を持つ双子の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は最初から最後まで「未バインド → 書き込み完了状態でバインド」の2つの状態しか経由せず、途中で半分満たされたディスクを誰かがアタッチする隙がありません。

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 サイズを計算する2つの関数

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 の2つの関数は互いに逆の処理を行います。

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が作成するオブジェクトです。主に2つの情報を保持します。

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)。対象はレジストリイメージで満たされた1つのルート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 スパースファイル

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、ストレージクラスの4つすべてが 0.06 であり、機能ゲートの HonorWaitForFirstConsumer が有効になっていました。

StorageProfile。 共有ストレージのCSIクラス2つのclaimPropertySetsは ReadWriteMany·Filesystem で埋められていました(プロバイダーテーブルに存在するドライバー)。ノードローカルクラス2つはテーブルに存在しないプロバイダーのため、claimPropertySetsが空でした。4つのクラスすべてでcloneStrategyが copy であり、 クラスターにはVolumeSnapshotClassが1つも存在しませんでした。つまり、その小さなクラスターでPVCを複製する場合、スマートクローンではなく バイト単位で丸ごとコピーを行います。 「学生VMが起動するまで」の第4部で、学生ごとにPVCの複製ではなくオーバーレイ(書き込みレイヤー)を選択した理由と同じ事実です。

5. 制限事項

TTの一言。 spec.storage と spec.pvc は1文字の違いのようですが、ディスクサイズが6%異なります。ゲストディスクをちょうど10GiBで受け取りたい場合は spec.storage を使い、PVCが少し大きくなることは覚えておいてください!

参考

すべて2026-10-17に閲覧しました。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます