TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

学生VMが立ち上がるまで 4: PVC観点のオーバーレイ — ゴールデンディスク1つ、学生ごとに薄い書き込みレイヤー

状態: ドラフト (2026-10-16)、私たちのコード + 公式ドキュメント・ソース + トイコードの実測。ゴールデンVMの実測はまだありません。 ゴールデンディスクはコードとオペレーターツールまで導入されましたが、執筆時点のクラスターにはゴールデンPVCもそれに読み取る権限もまだ適用されていません(読み取り専用の照会で確認)。そのため、この記事におけるゴールデンVMの動作はKubeVirt v1.9.0・CDI v1.66.0のドキュメントとソースで確認したものであり、数値はノートPCで実行したトイコードとコンテナディスク(同じ原理)で測定したものです。作成ルール: 実測データが基本であり、測定できなかったものは「未計測」、ドキュメントから見つからなかったものは「未確認」と表示します。

学生VMが立ち上がるまで 4: PVC観点のオーバーレイ

目次

第3部は2つの方法の空白地帯で終わりました。コンテナディスクは20秒で立ち上がりますがルートが2.4GiBであり、DataVolumeはルートが大きいもののVMごとにイメージを新しく取り直すため初回起動が最大195秒かかります。この記事では、その間を埋める3番目の方法である、 ゴールデンPVC + 書き込みレイヤーを見ていきます。

1. やりたいこと

これら4つを同時に実現する古くからの方法があります。 読み取り専用のベースの上に、人ごとの変更点だけを収める薄いレイヤーを重ねることです。コンテナイメージのレイヤー、仮想化のリンククローン、qcow2のバッキングファイルはすべて同じ考え方です。

2. 原理: ベースと書き込みレイヤー

골든 PVC 하나 위에 학생마다 얇은 쓰기 층 ① 디스크 한 장은 두 겹이다 (칸 = 클러스터) 게스트가 보는 /dev/vda 쓰기 층 disk.qcow2 바탕(골든) disk.img 쓴 칸은 쓰기 층에서, 안 쓴 칸은 바탕에서 읽는다. 처음 쓰는 칸은 바탕에서 통째로 끌어올린 뒤 고친다. ② 여러 학생이 하나의 골든을 나눠 읽는다 노드 1 · virt-launcher 학생 A의 VM QEMU 쓰기 층 emptyDir 노드 1 · virt-launcher 학생 B의 VM QEMU 쓰기 층 emptyDir 노드 2 · virt-launcher 학생 C의 VM QEMU 쓰기 층 emptyDir 안 쓴 칸 읽기 골든 PVC — 공유 스토리지(CSI 드라이버) · RWX disk.img(raw) · 모든 VM 이 readOnly: true 로 붙는다 VM 이 소유하지 않는다 → VM 을 지워도 남는다 세션이 끝나면 파드가 사라지면서 쓰기 층(emptyDir)도 함께 간다.
図: ゴールデンPVC1つに対し学生ごとの薄い書き込みレイヤー

qcow2ディスクイメージは バッキングファイルを指すことができます。 qemu-img create -f qcow2 -b <바탕> -F raw <쓰기 층> として作成すると、新しいイメージはベースと 異なるところだけ を記録します(qemu-imgドキュメント)。このときディスクは一定サイズのブロック(クラスター)に分かれ、ルールは2つです。

そのため、書き込みレイヤーは 空の状態で始まり、ゲストが書き込んだブロックの分だけ成長します。 ベースを変更しないため、複数のディスクが同じベースを一緒に指していても互いに混ざりません。

トイコードで測定してみる

ノートPCにqemu-imgがないため(新しくインストールしていません)、上記の2つのルールをPythonで模倣し、同じ条件下で測定しました。ベース512MiB(ランダムバイト)、ゲストが4KiBの書き込みをランダムな位置に1,000回行うタスクです。「丸ごとコピーしたあとに書き込み」と「空の書き込みレイヤーに書き込み」を5回ずつ実行した中央値です(0回目の試行はウォームアップとして除外しました)。両方式のディスク全体のハッシュが5回とも一致し、ゲストから見えるディスクが同じであることも確認しました。

ブロック 64KiB ブロック 2MiB
丸ごとコピー(512MiB) 0.217秒 0.151秒
空の書き込みレイヤー作成 0.0002秒 0.0002秒
書き込み1,000回後の書き込みレイヤーサイズ 59MiB 504MiB(ベースの98%)

環境: Apple M1 MaxノートPC、内蔵SSD、Python 3.12、標準ライブラリのみ。コードとローデータは docs/experiments/2026-10-16-cow-overlay-toy/ にあります。

数値から読み取るべきポイントは3つです。

  1. 作成は実質タダです。 空の書き込みレイヤーはディスクサイズに関係なく0.2ミリ秒です。コピーはディスクサイズに比例します。ここで0.2秒となっているのは同じSSD内のコピーであり、ベースがファイルキャッシュにあったためであるため、ネットワーク越しのストレージから10Giをコピーする時間をこの数値で語ることはできません(未計測)。計算上だけで言うと、1Gbps回線で10GiBは理論上約86秒です。
  2. 書き込みレイヤーは「書き込んだ量」ではなく「触れたブロック」の分だけ大きくなります。 実際に書き込んだのは3.9MiBですが、64KiBのブロックを945個触ったため59MiBになりました(約15倍)。分散した細かい書き込みはブロックサイズ単位で膨らみます。
  3. ブロックが大きく、書き込みが分散していると、レイヤーがベースと同じくらい大きくなります。 2MiBのブロックでは、512MiBのディスクが256ブロックしかなく、1,000回の書き込みがほぼすべてのブロックに触れました。書き込みレイヤーが薄く維持されるのは、 ゲストがディスクの小さな部分だけを修正するという前提があるからです。OSを丸ごとアップグレードするような実習であれば、この前提は崩れます。

実際のVMではどうでしょうか。同じ構造のコンテナディスクVM1つ(オペレーター用、9日目稼働中)を調べたところ、ベース(約596MiB)上の書き込みレイヤーは約1.10GiBでした(実測、2026-10-14)。9日間パッケージをインストールしてログを蓄積したVMであるため、1時間の実習セッションよりもはるかに多く書き込んだケースです。ゴールデンVMの起動直後・実習後の書き込みレイヤーサイズは 未計測です。

3. KubeVirtのephemeralボリューム: これをPVC上で

KubeVirtには、この構造をPVC上で実現するボリュームソースがすでに存在します。ユーザーガイドの ephemeral ボリュームです。要約すると次のとおりです。

TT Labバックエンドが作成するゴールデンVMのルートボリュームはこの1行です(backend/app/vms.py の vm_manifest).

volumes:
  - name: root
    ephemeral:
      persistentVolumeClaim:
        claimName: golden-ubuntu-24.04-10gi   # OS 와 크기로 정해진 이름
        readOnly: true                        # 빼지 않는다

KubeVirt v1.9.0のソースをたどると、この1行が以下の処理を行います。

  1. virt-controllerがvirt-launcherポッドを作成する際、このPVCをポッドボリュームとして そのまま(readOnly 転記し、書き込みレイヤー用の空ディレクトリ(emptyDir)をもう1つ追加します(pkg/virt-controller/services/rendervolumes.go).
  2. ポッド内でPVCは /var/run/kubevirt-private/vmi-disks/root/ にアタッチされ、ベースはその中の disk.img(raw)です。
  3. 起動時に qemu-img create -f qcow2 -b <바탕 disk.img> -F raw <쓰기 층 disk.qcow2> で書き込みレイヤーを作成します(pkg/ephemeral-disk/ephemeral-disk.go)。書き込みレイヤーの仮想サイズはベースと同じです。 そのため、ベースのサイズがそのままゲストのルートのサイズになります。

コンテナディスクも同じ原理である点が興味深いところです。ベースがレジストリから取得したノードキャッシュなのか、ストレージクラスが提供したPVCなのかの違いだけです(第3部の図)。

4. PVCの目で見ると

「誰がどのPVCを持ち、何がいつ生成され消滅するのか」でまとめると次のようになります。

ゴールデンPVC 書き込みレイヤー
対象 OS・サイズごとに1つ(名前は golden-<OS>-<크기>) VMごとに1つの disk.qcow2 ファイル
場所 共有ストレージ(CSIドライバー)、RWX、ファイルシステムモード virt-launcherポッドのemptyDir — ノードローカルであり、PVCではない
作成者 オペレーターがCDI DataVolumeで一度作成(レジストリ由来) KubeVirtがVMを起動するたび
所有者 なし(VMは所有しない) virt-launcherポッド
アタッチの形式 すべてのVMが readOnly: true として そのVMのQEMUのみ
削除のタイミング オペレーターが削除するときのみ VMが終了してポッドが削除されるとき

いくつかの決定には理由があります。

判定とフォールバック

バックエンドはVMを作成する前にゴールデンを読み取り、現在使用可能かどうかを判定します。存在しないPVCを指すVMは、KubeVirtがポッドを作成できず永遠に起動しないためです(v1.9.0の rendervolumes.go のPVC不在エラー)。

状況 結果
ゴールデンPVCが存在しない・読み取り権限がない・APIエラー コンテナディスクで起動する
削除中である・まだバインドされていない コンテナディスクで起動する
1つのノードにしかアタッチされないボリュームである コンテナディスクで起動する
CDIがまだ取得中である コンテナディスクで起動する
別のイメージから取得したゴールデンである コンテナディスクで起動する
レッスンの値がタイポである エラー(カリキュラムのテストが事前に検出する)

フォールバックした際、学生の端末には「ルートが約2.4GiBと普段より小さい、 df -h / を確認せよ」という案内が表示されます。この案内は対話型シェルでのみ出力されます。採点エージェントもログインシェルでコマンドを実行するためであり、そうしないと案内が採点の出力に混ざってしまいます。なぜ DataVolume ではなくコンテナディスクに戻るのかというと、バックエンドに PVC を作成する権限を再び与えないためであり、195秒かかる最初の起動がゴールデン実習の準備時間の制限を超えてしまい、かえって失敗に終わってしまうからです。そのため ゴールデンは最適化であり、前提ではありません。 ゴールデンを有効にした実習も、コンテナディスクで合格しなければなりません。

5. なぜ CSI クローンやスナップショットではないのか

Kubernetes には、ボリュームを増殖させる公式な方法がすでに2つあります。

CDI は DataVolume で PVC をクローンする際、これら2つを自動的に選択します。スナップショットが使用可能な場合は「スナップショット → リストア → スナップショット削除」で行い(スマートクローン)、CSI クローンが使用可能な場合はそれを使用し、両方とも使用できない場合は 元から対象へデータを流し込むホストコピーにフォールバックします(CDI クローン・StorageProfile ドキュメント)。この選択はストレージクラスの StorageProfile に cloneStrategy として残ります。

학생 셋에게 10Gi 루트를 주는 세 방법 가. PVC 복제(CSI 클론 · 호스트 복사) 골든 A 10Gi B 10Gi C 10Gi 만들 때: 저장소가 클론을 하면 빠르고, 못 하면 10Gi × 3 을 복사 공간: 저장소 구현에 따라 0 ~ 30Gi · VM 마다 PVC 하나 나. 스냅샷에서 복원 골든 스냅샷 A 10Gi B 10Gi C 10Gi 필요: CSI 드라이버의 스냅샷 기능 + VolumeSnapshotClass 공간·시간은 저장소가 복원을 어떻게 하느냐에 달렸다 다. 골든 + 쓰기 층(이 시리즈의 방법) 골든 A 쓴 만큼 B 쓴 만큼 C 쓴 만큼 만들 때: 빈 파일 하나 · 공간: 골든 10Gi + 학생마다 쓴 만큼 대가: 쓰기 층이 노드 로컬이라 VM 을 끄면 사라진다 글 쓰는 날 TT Lab 의 공유 스토리지: 스냅샷 클래스 없음, CDI 복제 전략 copy. 나는 쓸 수 없고, 가는 네트워크로 통째로 복사한다(2026-10-16 확인).
図:3人の受講生に 10Gi ルートを割り当てる3つの方法

この記事を書いている日に TT Lab の共有ストレージクラスを確認したところ、スナップショット CRD はインストールされているものの VolumeSnapshotClass が一つもなく、StorageProfile のクローン戦略は copy(ホストコピー) でした(2026-10-16 取得)。そのため、このストレージで PVC クローンにより受講生ごとに 10Gi を作成すると、VM ごとに 10Gi がネットワーク経由で丸ごとコピーされることになります。スナップショットの道はありません。

ストレージが変わっても、書き込みレイヤーの利点は残ります。

PVC クローン・スナップショットリストア ゴールデン + 書き込みレイヤー
VM ごとに作成される PVC 1つ なし
PVC クォータ(VM ネームスペース 12個)に対する 同時 VM 数が PVC 数によって制限される ゴールデンの数のみをカウントする
バックエンドに必要な権限 PVC の作成・削除 PVC の読み取り1つ
作成にかかる時間 ストレージのクローン実装に依存(ここでは完全コピー) 空のファイル1つ
セッション終了時 PVC を削除する必要がある(削除の失敗 = 容量リーク) Pod とともに消滅する
受講生の VM を停止して再起動した場合 ディスクが残る 書き込みレイヤーが消える

最後の行が、この方式が払う代償です。実習セッションは一度開いて終了するため、これを許容しました。受講生が数日にわたって同じディスクを使い続けなければならない課題の場合、この方式は適していません。

6. セッション終了時:書き込みレイヤーのみが消える

ゴールデン VM マニフェストには dataVolumeTemplates がありません。VM はゴールデンを所有していないため、VM を削除してもゴールデンは残ります。ガベージコレクターが削除するのは VM と cloud-init Secret のみであり(第5回)、VM が削除されると virt-launcher Pod が終了し、その emptyDir、すなわち書き込みレイヤーが一緒に消えます。次の受講生に引き継がれるものは何もありません。

ゴールデン自体を変更する必要がある場合(イメージの更新)は、削除して同じ名前で再作成します。使用中の VM がある場合、Kubernetes の「使用中のストレージオブジェクトの保護」機能により、それらの VM が終了するまで PVC は残り、その間バックエンドは「削除中」を検知して新しい VM をコンテナディスクで起動します。名前にバージョンを付与して新しいゴールデンを隣に作成する方法(CDI の DataImportCron が行う方式)もありますが、そうするとバックエンドが名前を解決するために権限が1つ増え、受講生環境が検証されていないイメージに勝手に切り替わってしまいます。OSが6種類という規模では、手動で置き換える方がコストが低いと判断しました。

7. 代償と制限

計測項目と計測方法

計測スクリプト(scripts/measure_vm_disks.py)は、コンテナディスク・DataVolume・ゴールデンを同じノード上で1台ずつ作成、計測、削除します。開始前と各周回でノードの状態とメモリの空き状況を確認し、0周目はウォームアップとして別途記録し、1周目以降の中央値・最小値・最大値を算出します。計測する項目は、準備時間(リクエストからエージェントの準備完了まで)、VMI Running になるまでの時間、ゲストルートのサイズと空き容量、書き込みレイヤーの実際のサイズ(起動直後と 128MiB 書き込み後)、削除時間です。VM の作成と削除を伴うため、クラスターの書き込み権限を持つユーザーが実行します。

python3 deploy/vms/golden.py manifest --os ubuntu-24.04 --storage-class <공유 스토리지 클래스>   # 골든 하나(적용은 사람)
python3 scripts/measure_vm_disks.py --preflight --node <노드>                                 # 아무것도 만들지 않고 본다
python3 scripts/measure_vm_disks.py --node <노드> --rounds 4                                  # 예열 1 + 3바퀴

ゴールデンを実習で有効にするかどうかはこの結果に基づいて決定します。準備時間の中央値がコンテナディスクの1.5倍以内であり、ルートが 9GiB 以上であり、起動直後の書き込みレイヤーが数百 MB 以内であれば、kubeadm 系の実習から有効化します。

TT の一言。 30人に 10Gi ずつ割り当てていますが、実際に作成するのは 10Gi が1つと空ファイルが30個です。ただし「ベースは絶対に触らないこと」が命綱なので、 readOnly: true は必ずアタッチしておき、書き込みはブロック単位で膨らむことも覚えておいてください!

参考

ドキュメントおよびソースはすべて 2026-10-16 に閲覧しました。

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

コメント

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

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