Skip to content
Published on

KubeVirtはどのようにPod上でVMを実行するのか

Authors

はじめに

KubeVirtを初めて見た時に最も違和感があるのはこの点だ。「Kubernetesは元々コンテナオーケストレーターなのに、どうやってVMをPod上で起動できるのか?」この質問に答えるには、まず一つの誤解を解く必要がある。KubeVirtはkubeletの中にVM機能を組み込まない。代わりにKubernetesが既に得意なことと、仮想化スタックが得意なことを分離する。

  • Kubernetesが担うもの:APIストレージ、スケジューリング、Podネットワーク、ボリュームマウント、ノード配置、リトライ
  • KubeVirtが担うもの:VM関連CRD、VM専用controller、ノードエージェント、libvirtとQEMUオーケストレーション
  • Linuxとハイパーバイザーが担うもの:cgroupnamespacetapnetlink/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.mddocs/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.mdvirt-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-handlervirt-launcherが追加で調整する。

運用者がすぐに使えるデバッグチェックポイント

  • VMIが作成されたのにPodがなければ、virt-controller側のreconcileを確認する。
  • Podは起動しているがVMがブートしなければ、virt-handlervirt-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を作成する。

次の記事では、この構造を構成するオブジェクトモデル、すなわちVirtualMachineVirtualMachineInstanceVirtualMachineInstanceMigrationがそれぞれ何を表現するのか、ソーススキーマ基準で整理する。