Skip to content
Published on

virt-launcher、libvirt、QEMU:実際のVMプロセスはどこでどのように起動するのか

Authors

はじめに

ここでKubeVirtの最も重要な実行地点を見よう。virt-controllerはPodを作り、virt-handlerはノードで調整する。では実際のVMはどこで起動するのか?答えはvirt-launcher Pod内だ。

正確に言えば、virt-launcher PodはVM一台のための実行シェルであり、その中でlibvirtとQEMUがゲストを実際に駆動する。cmd/virt-launcher/virt-launcher.gopkg/virt-launcher/virtwrap/manager.goがこの実行層の中心だ。

virt-launcher Podをどう理解すべきか

多くの初心者向け説明は「virt-launcherがVMを起動する」で終わる。しかしより正確な説明はこうだ。

  • virt-launcher PodはVM実行に必要なnamespaceとcgroup境界を提供する。
  • そのPod内部のvirt-launcherプロセスはcommand serverとdomain managerを準備する。
  • libvirt接続が作成される。
  • QEMUドメインが定義され実行される。
  • 以降イベントとguest agent、statsが継続的に監視される。

つまりvirt-launcherは単純なcontainer entrypointではなくVM実行制御planeのlocal runtimeだ。

cmd/virt-launcher/virt-launcher.goがする仕事

このエントリーポイントファイルを見ると初期化順序がかなり明確だ。

1. libvirtイベント実装登録

プロセス開始時にlibvirt.EventRegisterDefaultImpl()が呼ばれる。これは以降のdomain event monitoringのために必要だ。

2. 各種ディレクトリ初期化

initializeDirsはcloud-init、ignition、container disk、hotplug disk、secret、config map、downward metricsなどゲストランタイムに必要な複数のディレクトリを準備する。

ここで既に重要な事実が見える。VMブートは単にQEMUバイナリ一つ起動することではなく、複数のディスクソースとメタデータチャネルを整理する準備作業と一緒に行われる。

3. libvirt接続作成

createLibvirtConnectionは基本的にqemu:///systemに接続し、非rootモードではsession URIを使用する。つまりvirt-launcherは単にlibvirtライブラリをリンクするのではなく、実際のlibvirt制御planeにクライアントとして接続してドメインを定義する。

4. command server起動

startCmdServerはunixソケットベースのcommand serverを起動する。virt-handlerはこのソケットを通じてlauncher側のdomain managerを呼び出す。

5. domain event monitoring開始

startDomainEventMonitoringはlibvirtイベントループを回しnotifierを開始する。ゲスト状態変化、終了、agent連動状態がここで反映される。

なぜcommand serverが必要なのか

これはKubeVirt構造を理解する核心ポイントだ。virt-handlerはnode agentだが、QEMUはlauncher Pod内部にある。2つのプロセスは異なる分離境界にあるため直接関数呼び出しができない。そのためKubeVirtはcommand serverとclientレイヤーを置く。

フローはおおよそこうだ。

  1. virt-handlerがVMIをreconcileする。
  2. launcher Pod内部のソケットにリクエストを送る。
  3. command serverがDomainManagerメソッドを呼び出す。
  4. LibvirtDomainManagerがlibvirt APIを通じてdomainをdefine、start、pause、migrateする。

この構造のおかげでクラスタノード側オーケストレーションとPod内部ハイパーバイザー制御が分離される。

DomainManagerインターフェースが示す責任範囲

pkg/virt-launcher/virtwrap/manager.goDomainManagerインターフェースを見ると、launcherが単にブートだけ担当するのではないことが鮮明だ。

代表メソッドは以下の通り。

  • SyncVMI
  • PauseVMI
  • UnpauseVMI
  • KillVMI
  • DeleteVMI
  • MigrateVMI
  • PrepareMigrationTarget
  • FinalizeVirtualMachineMigration
  • HotplugHostDevices
  • GetDomainStats
  • GuestPing

つまりlauncherはゲストライフサイクル全体を担当するlocal hypervisor adapterだ。

libvirtがすること、QEMUがすること

この2つはよく一塊で語られるが役割が異なる。

libvirt

  • ドメイン定義と管理API提供
  • XMLベースのドメインモデル維持
  • migration API提供
  • statsとイベント収集インターフェース提供

QEMU

  • 実際のゲストCPU、メモリ、ディスク、NICエミュレーション
  • KVMと結合した実行エンジン
  • virtioデバイス実装

KubeVirtは概ねlibvirtを主制御インターフェースとして使用し、libvirtが内部的にQEMUを管理するようにしている。

readinessとソケット切り替えが意味すること

markReadyは未初期化ソケット名を実際のソケット名にリネームする。この小さな動作が重要な理由は、virt-handlerがlauncherがまだ初期化中なのか実際にコマンドを受け取る準備ができているのか区別できるようにするからだ。

つまりlauncherはPodがRunningだからといってすぐに「VM制御準備完了」と仮定しない。command plane readinessが別にある。

guest agentとイベントループまでlauncherにある理由

ゲスト内部情報を扱う責任もlauncherに多い。agent poller、guest info、filesystem、user、time syncなどの機能がmanager.goインターフェースとvirt-launcher.go初期化パスに見える。

これは自然だ。guest agentは結局libvirtまたはQEMUが公開するゲスト通信チャネルに最も近いからだ。node agentであるvirt-handlerが直接扱うよりlauncherに聞く方が適切だ。

終了時になぜPodがすぐに死なないことがあるのか

docs/components.mdも言及しているが、Kubernetesがvirt-launcher Pod終了を試みる時にゲストがまだ終了していなければ、launcherはシグナルをゲスト側に伝達し、可能な限り正常終了を待つ。

この動作は非常に重要だ。そうでなければPod終了がゲスト強制終了とほぼ同一になり、データ損傷リスクが大きくなる。KubeVirtはPodライフサイクルとVMライフサイクルが完全に同じではないという点をこの地点で示す。

よくある誤解

誤解1:virt-launcherはサイドカーのない普通のコンテナだ

違う。このPodはVM実行境界であり、内部にlibvirt event loop、command server、guest agent関連ロジックがある。

誤解2:QEMUはvirt-handlerが直接起動する

違う。virt-handlerはlauncherのcommand serverを呼び出し、実際のdomain defineと実行はlauncher内部のdomain managerがlibvirtを通じて行う。

誤解3:Pod Runningならゲストも既に正常だ

そうとは限らない。launcher準備、libvirt接続、domain define、agent初期化は別の段階だ。

デバッグヒント

  • launcher Podは生きているがゲストが起動しなければcommand server readinessとlibvirt接続を見る。
  • guest agent関連情報が空ならlauncher側のpollerとevent notifierを見る。
  • 終了が長引けばゲストshutdown伝達とgrace periodを一緒に見る。
  • migration異常ならPrepareMigrationTargetMigrateVMIFinalizeVirtualMachineMigrationパスを分離して見る。

まとめ

virt-launcherはKubeVirtで最も実行に近いレイヤーだ。このPodはVMのための分離境界を提供し、その内部のlauncherプロセスはlibvirt接続、command server、event monitor、guest agent pollerをセットアップする。その上でLibvirtDomainManagerがQEMU domainライフサイクルを制御する。結局「VMがPod上で動く」という言葉の最も具体的な実体はまさにこのlauncher Pod内部の実行構造だ。

次の記事では、launcherが受け取ったVMI specがどのような変換過程を経てlibvirt domain XMLと実際のゲストデバイス構成に変わるか見ていく。