- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- ゲストリソースとPodリソースはなぜ異なるのか
- APIスキーマがすでにこの問題を示している
- launcher Podのリソースは誰が計算するのか
- メモリオーバーヘッドはなぜ重要なのか
- CPUトポロジーがゲストの数値と異なる場合があるのはなぜか
- NUMAはなぜAPIに含まれているのか
- HugePagesで何が変わるのか
- マイグレーションとリソースモデルはどう繋がるのか
- よくある誤解
- 運用者がまず見るべきこと
- まとめ
はじめに
VMのリソースモデルはコンテナよりも要求が厳しい。ユーザーはゲストに4 vCPUと16 GiBのメモリを割り当てたいと思うが、実際にはlauncher Pod、QEMUオーバーヘッド、エミュレータスレッド、hugepages、NUMAローカリティまで一緒に考慮する必要がある。KubeVirtはこのギャップを埋めるために、ゲストリソースモデルとPodリソースモデルを同時に管理している。
本記事では、staging/src/kubevirt.io/api/core/v1/schema.go、pkg/virt-controller/services/template.go、pkg/virt-launcher/virtwrap/manager.goを中心に、このリソース変換レイヤーを見ていく。
ゲストリソースとPodリソースはなぜ異なるのか
コンテナの場合、プロセスが使うリソースがそのままPodリソースになることが多い。しかしVMは異なる。
- ゲストが見るメモリ
- QEMUと仮想化インフラが追加で使うメモリ
- I/Oスレッドとエミュレータスレッド
- ページテーブル、デバイスエミュレーション、virtioキューのオーバーヘッド
これらの要因により、launcher Podはゲストメモリよりも多くのメモリをリクエストする必要がある場合がある。
つまり、KubeVirtがこれを処理しなければ、スケジューラはVMを過度に楽観的に配置してしまう。
APIスキーマがすでにこの問題を示している
schema.goを見ると、CPUとメモリ関連のフィールドがかなり豊富であることがわかる。
CPUCPUTopologyNUMAHugepagesMemoryOverhead
これはKubeVirtが「CPUをいくつかください」程度の単純な抽象化に留まらず、実行パフォーマンスと配置の安定性までAPIで扱おうとするシステムであることを意味する。
launcher Podのリソースは誰が計算するのか
この役割は主にpkg/virt-controller/services/template.goが担う。ここでCalculateMemoryOverheadが呼び出され、launcher Podに必要な実際のリソースrequestとlimitが生成される。
重要なポイントは以下の通り。
- ゲストメモリだけを反映するわけではない
- 仮想化インフラのオーバーヘッドを加算する
- ネットワークバインディングプラグインが要求する追加メモリも考慮できる
- hugepagesの有無によってPodリソースの種類自体が変わる
つまり、VMI specがそのままPod specにはならない。間にリソース補正段階がある。
メモリオーバーヘッドはなぜ重要なのか
schema.goにはMemoryOverheadの説明があり、template.goはメモリオーバーヘッドをアノテーションやstatusでも扱う。マイグレーション状態にはターゲットメモリオーバーヘッドも別途ある。
これは非常に重要である。例えば、ゲストが8 GiBしか見えないのに、launcher Podも8 GiBだけリクエストすると:
- ノードプレッシャーに脆弱になり
- QEMUや補助スレッドがOOMに陥る可能性があり
- マイグレーションターゲットでもリソース計算がずれる可能性がある
つまり、KubeVirtは「ゲストメモリ」と「launcherエンベロープメモリ」を分離して見ている。
CPUトポロジーがゲストの数値と異なる場合があるのはなぜか
KubeVirtのCPUTopologyはソケット、コア、スレッドを表現する。しかし、Kubernetesスケジューラが見るのは結局launcher PodのCPU requestとlimitである。
ここで重要なケースがdedicated CPUである。dedicated CPUをリクエストすると:
- CPUピニングが必要になり
- launcher Podにはより厳格なリソース保証が必要になり
- マイグレーションターゲットも適切なCPUトポロジーを持つノードを見つける必要がある
manager.goのUpdateVCPUsを見ると、dedicated CPUの場合にdomain specとpod cpusetを読み取ってPinVcpuFlagsとPinEmulatorを呼び出している。つまりこれは単純なクォータの問題ではなく、pCPU配置の問題である。
NUMAはなぜAPIに含まれているのか
schema.goのNUMAとNUMAGuestMappingPassthroughの説明は非常に意味深い。KubeVirtはゲストNUMAトポロジーをホストCPUピニングと互換性があるようにモデル化しようとしている。
これが重要な理由はパフォーマンスである。
- NUMAローカリティが合っていればメモリアクセスレイテンシが低下する
- CPUとメモリが異なるNUMAノードに散らばるとパフォーマンスが不安定になる可能性がある
- デバイスパススルーと組み合わさるとさらに敏感になる
つまり、KubeVirtはNUMAを「高度なオプション」ではなく、高パフォーマンスVM運用に必須のトポロジー制約として扱っている。
HugePagesで何が変わるのか
HugePagesを有効にすると、メモリは通常のページとは異なるリソースクラスとして扱われる。schema.goとtemplate.goはhugepagesのページサイズをPodリソースにも反映する。
これは次のことを意味する:
- ゲストメモリポリシーがPodスケジューリングのリソース種類に直接影響を与え
- ノードに該当するhugepageプールがなければスケジューリング自体ができない可能性があり
- フリーページレポーティングや一部のメモリ機能の動作も変わる可能性がある
つまり、hugepagesは「パフォーマンスのチェックボックス」ではなく、スケジューリングとカーネルメモリモデル全体を変える選択である。
マイグレーションとリソースモデルはどう繋がるのか
マイグレーション時、ターゲットノードはソースと同じゲストを収容する必要がある。しかし、dedicated CPU、NUMA、hugepages、メモリオーバーヘッドがあるとターゲット条件はかなり厳しくなる。
実際にマイグレーションstatusには:
- ターゲットノードトポロジー
- ターゲットメモリオーバーヘッド
などの情報が含まれる。これはマイグレーションが単に「空いているノードならどこでも」という作業ではなく、同じパフォーマンス特性を維持できるノードへ移す作業であることを示している。
よくある誤解
誤解1:ゲストが8 GiBならPodも8 GiBで十分
いいえ。仮想化オーバーヘッドがあり、バインディングプラグインや付加機能も追加メモリを消費する可能性がある。
誤解2:CPUリクエストさえ合えばdedicated CPUも問題ない
いいえ。ピニング、トポロジー、cpusetが合っている必要がある。
誤解3:NUMAとhugepagesはパフォーマンスチューニング用のオプションに過ぎない
いいえ。スケジューリング条件とマイグレーションの可否まで変わる。
運用者がまず見るべきこと
- ゲストメモリとlauncherメモリオーバーヘッドを区別して見る。
- dedicated CPUの場合はcpusetとピニングパスを確認する。
- hugepagesリクエストの場合はノードのhugepageプールを先に確認する。
- マイグレーション失敗時はターゲットノードトポロジーとターゲットメモリオーバーヘッドを確認する。
まとめ
KubeVirtはVMIのCPUとメモリリクエストをそのままPod requestとして投げるわけではない。代わりにメモリオーバーヘッドを加算し、dedicated CPUとNUMA、hugepagesを反映してlauncher Podとゲストハードウェアモデルを同時に合わせる。この構造のおかげで、KubernetesスケジューラはVMをある程度正しく配置でき、ゲストはより予測可能なパフォーマンス特性を得られる。
次の記事では、このリソースモデルを実際に可能にするホストプリミティブ、すなわち/dev/kvm、namespace、cgroup、TAP、netlinkといったカーネル技術を見ていく。