はじめに
KubeVirtを初めて見た時に最も違和感があるのはこの点だ。「Kubernetesは元々コンテナオーケストレーターなのに、どうやってVMをPod上で起動できるのか?」この質問に答えるには、まず一つの誤解を解く必要がある。KubeVirtはkubeletの中にVM機能を組み込まない。代わりに**Kubernetesが既に得意なことと、仮想化スタックが得意なことを分離**する。
- Kubernetesが担うもの:APIストレージ、スケジューリング、Podネットワーク、ボリュームマウント、ノード配置、リトライ
- KubeVirtが担うもの:VM関連CRD、VM専用controller、ノードエージェント、libvirtとQEMUオーケストレーション
- Linuxとハイパーバイザーが担うもの:`cgroup`、`namespace`、`tap`、`netlink`、`/dev/kvm`、QEMU仮想化実行
つまりKubeVirtの本質は「コンテナランタイム上でVMをエミュレーションする」ではない。正確には**Podを VM実行のためのサンドボックスと制御単位として使用し、その中でQEMUとlibvirtを動作させる構造**だ。
最初に掴むべきメンタルモデル
KubeVirtの構造を一行でまとめると次の通りだ。
1. ユーザーが`VirtualMachine`または`VirtualMachineInstance`を作成する。
2. `virt-controller`がこれを見て`virt-launcher` Podを作成する。
3. Kubernetesがそのpodを通常のPodと同様にスケジューリングする。
4. 該当ノードの`virt-handler`がPodとVMIを見て実際のVMラウンチを指揮する。
5. `virt-launcher` Pod内部のlibvirtとQEMUがVMプロセスを実行する。
ここで重要なのは**VMがPodの中に「パッケージ」されている**ということであり、ゲストOSがコンテナになるという意味ではないという点だ。ゲストOSは依然としてQEMUが提供する仮想ハードウェア上で動作する。ただし、そのQEMUプロセスがPodのリソース境界内で実行される。
KubeVirt公式アーキテクチャドキュメントの`docs/architecture.md`と`docs/components.md`もこの構造を明確に示している。そこではKubeVirtはKubernetes上に載る追加的なコントロールプレーンとノードエージェントの集合として説明されている。
なぜわざわざPodをVM実行単位として使うのか
この設計は非常に実用的だ。KubeVirtが独自にスケジューラ、ボリュームアタッチロジック、ネットワークアロケーターを作り直す必要がないからだ。
1. スケジューリングを作り直す必要がない
VMも結局どのノードに配置されなければならない。Kubernetesはリソース要求、affinity、taint、topology spread、priorityに基づいてPodを適切に配置する。KubeVirtはこの機能を再利用する。
2. ネットワークを新しく作る必要がない
デフォルトモデルでは、まず`virt-launcher` PodがCNIを通じてネットワークを受け取る。その後KubeVirtがそのPodのネットワークnamespace内部でbridge、masquerade、TAPなどの追加ワイヤリングを行い、ゲストNICを接続する。
3. ストレージを新しく作る必要がない
PVC、DataVolume、container disk、secret、config mapなどのリソースはすべてPodボリュームモデルを通じて`virt-launcher`に渡される。KubeVirtはその上でこれらをディスクイメージやブロックデバイスとしてゲストに接続する。
Pod内部で実際に何が動いているのか
コアPodは`virt-launcher` Podだ。このPodは「VM一台につき一つ」という感覚で理解すればよい。このPod内で重要なプロセスは以下の通り。
- `virt-launcher`
- libvirtdまたはvirtqemud系の制御コンポーネント
- QEMU
- 場合によってはsidecarやhookコンテナ
ユーザーは通常「VMが実行されている」と言うが、ノードの観点では実際には**QEMUプロセスがPodのcgroupとnamespace内で実行**されている。これがKubeVirtがVMをPod上で実行できる最も実質的な理由だ。
`docs/components.md`は`virt-launcher` Podの目的を「VMIプロセスのためのcgroupとnamespaceを提供すること」と説明している。この表現は非常に重要だ。ここでのPodは単なるデプロイ単位ではなく**VM実行境界**だ。
KubernetesとKubeVirtの境界
KubeVirtを理解する際に最も混乱しやすい部分は「誰が何を担当するのか」だ。
Kubernetesが引き続き担当する部分
- Podスケジューリング
- ボリュームマウント準備
- Podネットワークアタッチ
- コンテナライフサイクル
- ノード状態反映
KubeVirtが追加で担当する部分
- VM関連API種類の提供
- VM specをlauncher Pod specに変換
- ノードでのVMプロセスライフサイクル調整
- ゲスト用ネットワークバインディングとDHCP補助
- ライブマイグレーションオーケストレーション
libvirtとQEMUが担当する部分
- ドメインXML解釈
- 仮想ハードウェアモデル構成
- CPU、メモリ、ディスク、NIC仮想化
- ライブマイグレーションデータ転送
この分離がうまくできているため、KubeVirtはKubernetesをフォークしたりkubeletを大規模に修正したりせずにVMワークロードを追加できる。
ソースコードでこの構造が現れる場所
このシリーズ全般で繰り返し見ることになる核心パッケージは以下の通りだ。
staging/src/kubevirt.io/api/core/v1
pkg/virt-controller/watch
pkg/virt-handler
pkg/virt-launcher/virtwrap
pkg/network
各レイヤーを一行でまとめると:
- `staging/src/kubevirt.io/api/core/v1`:VM関連CRDスキーマ
- `pkg/virt-controller/watch`:クラスタワイドのreconcileロジック
- `pkg/virt-handler`:ノード別VMエージェント
- `pkg/virt-launcher/virtwrap`:libvirt、QEMU制御
- `pkg/network`:PodネットワークをゲストNICに接続するコード
「Pod上でVM」が成立する核心メカニズム
さて質問に戻ろう。一体どうやってVM機能をPodで実現できたのか?
核心は3つだ。
第一に、Podは元々プロセス分離境界である
Podはネットワークnamespace、マウントnamespace、PID namespace、cgroupなどの境界を提供する。QEMUは結局Linuxプロセスなので、この境界内で実行できる。
第二に、`/dev/kvm`のようなホスト機能をPodに公開できる
ハードウェア仮想化の高速化にはKVMデバイスアクセスが必要だ。KubeVirtは適切なデバイスと権限を`virt-launcher`側に接続してゲスト実行パフォーマンスを確保する。
第三に、Kubernetesリソースモデルと仮想化モデルの間にtranslation layerを作る
ユーザーはVMI specにCPU、メモリ、ディスク、NICを宣言する。KubeVirtはこれをPod spec、libvirtドメインspec、ゲスト可視デバイス構成へと順次変換する。つまりKubeVirtの本質は**translation engine**だ。
よくある誤解
誤解1:VMがコンテナ内で動くのだからコンテナと同じだ
違う。実行境界はPodを再利用するが、ゲストOSはQEMUが提供する仮想ハードウェア上で動作する。プロセスモデルとゲストOSモデルは異なる。
誤解2:KubeVirtがネットワークとストレージをすべて自前で実装している
違う。設計哲学上、KubernetesとCNI、ボリュームシステムを最大限再利用する。KubeVirtはその上にVM向けのワイヤリングを追加する。
誤解3:kubeletがVMライフサイクルを理解している
直接理解していない。kubeletは`virt-launcher` Podを管理する。VMライフサイクルの詳細な状態は`virt-handler`と`virt-launcher`が追加で調整する。
運用者がすぐに使えるデバッグチェックポイント
- VMIが作成されたのにPodがなければ、`virt-controller`側のreconcileを確認する。
- Podは起動しているがVMがブートしなければ、`virt-handler`と`virt-launcher`の通信を確認する。
- ゲストネットワークがおかしければ、Pod NIC、bridge、TAP、DHCPの順に確認する。
- マイグレーション問題が発生したら、controllerステージとlibvirtマイグレーションステージが分離されていることを覚えておく。
まとめ
KubeVirtが「Pod上でVM」を実現できた理由は、Kubernetesを変更したからではなく、Kubernetesの強みをそのまま活用し、VMに必要なtranslation layerを追加したからだ。Podは実行サンドボックスとなり、`virt-controller`はオーケストレーションを担い、`virt-handler`はノード別の実行を調整し、`virt-launcher`内のlibvirtとQEMUが実際のVMを作成する。
次の記事では、この構造を構成するオブジェクトモデル、すなわち`VirtualMachine`、`VirtualMachineInstance`、`VirtualMachineInstanceMigration`がそれぞれ何を表現するのか、ソーススキーマ基準で整理する。
현재 단락 (1/66)
KubeVirtを初めて見た時に最も違和感があるのはこの点だ。「Kubernetesは元々コンテナオーケストレーターなのに、どうやってVMをPod上で起動できるのか?」この質問に答えるには、まず一つの...