ステータス:下書き(2026-10-16)、公式ドキュメント + 一部実測。 KubeVirt・CDI・Kubernetesのドキュメントを2026-10-16に開き確認しました(KubeVirt v1.9.0、CDI v1.66.0、Kubernetes v1.34)。ストレージクラスの設定は同日に読み取り専用で照会し、公開記事であるためストレージ製品とドライバ名は「共有ストレージのCSIドライバ」、「ノードローカルクラス」に一般化しました。作成ルール:実測データが基本であり、測定できなかったものは「未測定」、ドキュメントから見つからなかったものは「未確認」と表示します。
学生VMが起動するまで 3:ディスクはPVCだ
目次
- 1. ボリュームの種類
- 2. コンテナディスク:速いが小さい
- 3. DataVolume:大きいが遅い
- 4. アクセスモード:いくつのノードが同時にアタッチできるか
- 5. ボリュームモード:ファイルシステムかブロックか
- 6. バインディングのタイミングとStorageProfile
- 7. まとめ:3つのトレードオフ
- 参考
第1回の⑦「ボリュームのアタッチ」を開きます。結論から言うと、KubeVirtのディスクは**ゲストに見えるデバイス(disk)**と**その内容を収めるKubernetesボリューム(volume)**のペアです。マニフェストにおいて両者は名前で結ばれます。
domain:
devices:
disks:
- name: root # 게스트에게는 virtio 디스크(/dev/vda)
disk: {bus: virtio}
volumes:
- name: root # 내용은 이 볼륨에서 온다
containerDisk: {image: quay.io/containerdisks/ubuntu:24.04}
ボリュームの位置に何を配置するかによって、ディスクの性質が完全に変わります。
1. ボリュームの種類
KubeVirtユーザーガイドが挙げるボリュームのオリジナルのうち、ハンズオンVMで使用するのは5つです。
| オリジナル | 内容 | 削除時 | ハンズオンでの役割 |
|---|---|---|---|
containerDisk |
コンテナイメージの /disk 内に含まれるディスクイメージ |
消える(一時的) | デフォルトのルートディスク |
emptyDisk |
ノード上に作成する空のスパースqcow2。容量を指定する必要がある | ゲストの再起動時には残り、VMを再作成すると消える | Docker・Kubernetesハンズオンのスクラッチ |
persistentVolumeClaim |
既存のPVC | 残る | — |
dataVolume |
CDIが取得して作成したPVC | 残る(VMが所有している場合はVMと一緒に削除される) | 永続ディスクの選択肢 |
ephemeral |
PVCを 読み取り専用のベースとして使い、書き込みはローカル書き込みレイヤーに行う | 書き込みレイヤーのみ消える | ゴールデンディスク(第4回) |
cloudInitNoCloud |
cloud-initデータ(Secret内) | — | 初回起動の設定(第1回 ③) |
2. コンテナディスク:速いが小さい
コンテナディスクは、コンテナイメージのようにレジストリからOSイメージを取得します。ノードが一度取得しておけば、次のVMは取得せずにすぐに使用します。ディスクが一時的なものであるため、「作成し、壊し、削除する」というハンズオンによく適しており、したがってデフォルト値となっています。第1回の実測どおり、20秒前後でRunningになります。
一時的であるということは、 ベースはノードに取得されたイメージファイルであり、ゲストの書き込みは別途積み重なるという意味です。9日間稼働していたオペレーター用VMのvirt-launcher内を読み取ってみると、ルートはベース(ノードに取得されたディスクイメージ、割り当て約596MiB)の上に重ねられたqcow2の書き込みレイヤー(割り当て約1.10GiB)でした(実測、2026-10-14)。第4回で取り上げるゴールデンディスクは、この構造から ベースのみをPVCに変更したものです。
欠点はサイズです。コンテナディスクのルートは、ベースイメージの仮想サイズ、私たちが使用するUbuntuイメージでは 2.4GiB のみです(実測)。ハンズオンを作成する中で、これが原因で2回行き詰まりました。
- Dockerハンズオン。 イメージを数枚プルし、ビルドを2、3回行うとルートがいっぱいになりました。しかし、エラーはすべて「イメージがありません」系のものだったため、原因が容量であることに気づくのが困難でした。20GiBの
emptyDiskをスクラッチとしてアタッチし、/var/lib/dockerを移行したのですが、 またいっぱいになりました。 Docker 29はcontainerdのイメージストアを使用するため、レイヤーが/var/lib/containerd側に蓄積されたためです。両方のパスを移行して初めて解決しました。 - スクラッチディスクの名前。 スクラッチを
/dev/vdbと記述してフォーマットしようとしましたが、cloud-initディスクがその位置を占有したため、スクラッチは/dev/vdcでした(実測)。 名前を間違えるとcloud-initディスクをフォーマットしてしまいます。 そのため、名前ではなく性質(ファイルシステムもパーティションもなく、5GBより大きいディスク)によって選択します。 - kubeadmハンズオン。 パッケージとコントロールプレーンのイメージをプルしている途中で、
No space left on deviceで終了しました。スクラッチに移行しにくいパスが多く、ルート自体が大きい必要がありました。
3. DataVolume:大きいが遅い
ルートが大きい必要があるハンズオンのために、 persistent の選択肢があります。このとき、バックエンドはVMマニフェストに dataVolumeTemplates を記述し、CDI(Containerized Data Importer)がVMごとにPVCを作成してイメージを取得します。
CDIがレジストリイメージを取得する手順は次のとおりです(CDIの「Scratch space」ドキュメント)。インポート用Podがイメージをプルしてレイヤーを展開し、ディスクイメージを見つけた後、 qemu-img に変換してターゲットのPVCに書き込みます。このプロセスにおいて、 ターゲットと同じサイズのスクラッチPVC が一時的に必要になり、終了すると返却されます。したがって、10Giのディスクを1つインポートしている間はPVCが2つ生成されます。
VMがDataVolumeを所有するため、VMを削除するとPVCも一緒に削除されます。問題は時間です。ハンズオンセッションは毎回VMを新規作成する構造であるため、 VMを作成するたびにイメージを新しくインポートし 、初回起動が 最大195秒にまで達しました(実測、ハンズオン開発中の最大値のみを記録)。バックエンドがVMの準備完了を待つ上限を、コンテナディスク(150秒)とDataVolume(300秒)で別々に設定しているのもこのためです。
サイズにも落とし穴が1つあります。ファイルシステムPVCにおいて、KubeVirtはディスクをPVCルートの disk.img というrawファイルとして配置するため、ファイルシステム自体が占有する分だけ容量が空きます。CDIドキュメントによると、DataVolumeを spec.pvc ではなく spec.storage と記述した場合にのみ、CDIはこの分(ファイルシステムのオーバーヘッド)を計算して PVCをより大きく要求します。 TT LabのCDI設定ではオーバーヘッドが6%であるため(2026-10-16確認)、10Giを spec.storage として要求すると、ゲストディスクは10Gi、PVCはその少し大きなサイズになります(計算値、 未測定)。設計書には「10Giゴールデンのゲストルートは約9.4GiBであるはず」と記載されていましたが、CDIドキュメントと食い違っています。どちらが正しいかは第4回の最後の測定で確認します。また、KubeVirtは disk.img が要求より少し小さくても(デフォルトで最大10%まで)受け入れます(ユーザーガイド)。
4. アクセスモード:いくつのノードが同時にアタッチできるか
PVCのアクセスモードはVMにおいて特に重要です。Kubernetesドキュメントの定義を要約すると次のようになります。
| モード | 意味 | VMにおいて |
|---|---|---|
| ReadWriteOnce (RWO) | 1つのノードが読み書き可能としてアタッチする。同一ノード内の複数のPodは一緒に使用できる | VMがそのノードにバインドされる。ライブマイグレーション不可 |
| ReadOnlyMany (ROX) | 複数のノードが読み取り専用としてアタッチする | ベースディスクに適しているように見えるが、下記の注意を参照 |
| ReadWriteMany (RWX) | 複数のノードが読み書き可能としてアタッチする | ライブマイグレーション可能、複数のノードが同じベースを共有する |
| ReadWriteOncePod (RWOP) | クラスター全体でただ1つのPodのみ | CSIボリュームでのみ |
2つのポイントを指摘しておきます。
アクセスモードは書き込み保護ではありません。 Kubernetesドキュメントは、アクセスモードはアタッチされた後の書き込みを阻止せず、ROXで作成したボリュームであっても読み取り専用が保証されないと明記しています。真の保護は、ボリュームを 読み取り専用でマウントすることです。第4回のゴールデンディスクをRWXで作成しつつ、アタッチ時に readOnly: true を外さない理由がこれです。
ライブマイグレーションはRWXを要求します。 KubeVirtユーザーガイドには、PVCを使用するVMはアクセスモードがRWXである場合にのみ移行できると記載されています。移行可能かどうかはVMIの起動時に計算されて LiveMigratable の条件として残り、方式はメモリのみを移行する LiveMigration と、ディスクもコピーしなければならない BlockMigration に分かれます。実際にコンテナディスクとemptyDiskを使用する1つのVMを読み取ってみると LiveMigratable=Trueであり、方式は BlockMigration でした(2026-10-16照会)。ノードローカルディスクは移行する際にごっそりコピーする必要があるということです。TT LabのDataVolumeの選択肢はRWOとして作成されるため、そのVMは移行できません。ハンズオンVMはどうせ移行しないため、受け入れた選択です。
5. ボリュームモード:ファイルシステムかブロックか
| モード | Podにおける見た目 | KubeVirtのディスク |
|---|---|---|
| Filesystem(デフォルト) | ディレクトリとしてマウント | その中の disk.img(raw)ファイル。所有者はUID 107である必要がある |
| Block | ファイルシステムのないブロックデバイス | デバイスをそのままディスクとして使用 |
Kubernetesドキュメントでは、ブロックモードをPodとボリュームの間にファイルシステム層がない最速の道として説明しています。代わりに、使用する側がブロックデバイスを扱える必要がありますが、QEMUは扱うことができます。TT Labの共有ストレージクラスは、CDIがファイルシステムモードのみをサポートすると報告しています(下記の第6節)。したがって、ゴールデンディスクもファイルシステムPVC内の disk.img です。
6. バインディングのタイミングとStorageProfile
PVCの作成と同時にボリュームをプロビジョニングするか(Immediate)、そのPVCを使用するPodがノードに配置されるまで遅延させるか(WaitForFirstConsumer)も、VMにおいて違いを生みます。ノードローカルクラスは遅延させる側である必要があります。ボリュームが特定ノードのディスクであるため、Podがどこに配置されるかを知る前にプロビジョニングすると、見当違いのノードに作成されてしまうためです。
CDIはこの遅延を尊重する機能ゲート(HonorWaitForFirstConsumer)を備えています。これにより、インポートも最初の利用者(VM)が配置されるまで延期されます。ゴールデンディスクのように VMが来る前にインポートが完了していなければならない ボリュームは、そのため「すぐにバインドしてほしい」というアノテーション(ズ)cdi.kubevirt.io/storage.bind.immediate.requestedを付与します。
CDIはストレージクラスごとに StorageProfile を作成し、そのクラスに適合するアクセスモード・ボリュームモード・クローン戦略を設定します。名前が既知のプロビジョナーには推奨値を入力し、未知のプロビジョナーは空のままにします。空のクラスに対してアクセスモードを指定せずにDataVolumeを作成すると失敗します(CDI StorageProfileドキュメント)。記事執筆日にTT Labのクラスを読み取ったところ、このようになっていました。
| クラス | バインディング | 拡張 | StorageProfileが提供したもの | クローン戦略 |
|---|---|---|---|---|
| 共有ストレージ(CSIドライバ) | Immediate | 可能 | RWX · Filesystem | copy |
| ノードローカル | WaitForFirstConsumer | — | なし | copy |
共有ストレージクラスのみが複数のノードにアタッチできるボリュームをプロビジョニングします。そのため、ゴールデンディスクはこのクラスで作成します。複製戦略 copy とスナップショットクラスが存在しないという事実は、第4話における「なぜPVC複製ではなくライトレイヤーなのか」の根拠となります。
7. まとめ:3つのトレードオフ
| コンテナディスク | DataVolume | ゴールデン + ライトレイヤー(第4話) | |
|---|---|---|---|
| ルートサイズ | 2.4GiB(実測) | 希望するサイズ | ゴールデンサイズ(10Gi・20Giメニュー) |
| VM作成時の処理 | ノードにイメージが存在する場合はほぼなし | イメージを新しく取得してPVCに書き込む | 空のライトレイヤーが1つ |
| 初回起動 | 約20秒(実測) | 最大195秒(実測) | 未計測 |
| VMごとに作成されるPVC | なし | 1つ(+取得中のスクラッチ1つ) | なし |
| 停止時 | ライトレイヤーが消滅する | PVCが残る(VM削除時に一緒に削除) | ライトレイヤーが消滅する |
中央の列の「VMごとにイメージを新しく取得する」を「1回だけ取得して共有する」に変更したのが、第4話のゴールデンディスクです。
TTの一言 アクセスモードは「何箇所からアタッチできるか」であり、「書き込みを禁止してくれるもの」ではありません。ベースディスクを保護したい場合は、RWXであれROXであれ、必ず読み取り専用でアタッチしてください。そして、ディスクが一杯になるとエラーメッセージがおかしな内容になるため、おかしいと思ったら
df -hから確認してください!
参考
ドキュメントはすべて2026年10月16日に閲覧しました。
- KubeVirtユーザーガイド、ディスクとボリューム(ボリュームソース、
disk.img・UID 107、pvcTolerateLessSpaceUpToPercent、ephemeral、emptyDisk): https://kubevirt.io/user-guide/storage/disks_and_volumes/ - KubeVirtユーザーガイド、ライブマイグレーション(RWX要件、
LiveMigratable,BlockMigration): https://kubevirt.io/user-guide/compute/live_migration/ - Kubernetesドキュメント、永続ボリューム(アクセスモード、「アクセスモードは書き込み保護ではない」、ボリュームモード): https://kubernetes.io/docs/concepts/storage/persistent-volumes/
- CDI v1.66.0、Scratch space: https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/scratch-space.md
- CDI v1.66.0、StorageProfile: https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/storageprofile.md
- CDI v1.66.0、DataVolumes(
spec.storageとファイルシステムの空き容量): https://github.com/kubevirt/containerized-data-importer/blob/v1.66.0/doc/datavolumes.md - TT Labのコードと記録:
backend/app/vms.py(DISK_SIZE·SCRATCH_SIZE·DOCKER_SCRATCH_PATHSのコメント、cloud_initでのスクラッチの選択)、backend/app/main.py(VM_READY_SEC),docs/plans/2026-08-29-vm-labs-design.md,docs/plans/2026-10-14-vm-golden-overlay.md