Skip to content
Published on

virt-handler deep dive:kubelet横のVMノードエージェントは何をするのか

Authors

はじめに

virt-controllerがcluster-wideオーケストレーションであるなら、virt-handlerは各ノードで実際のVMライフサイクルを引き継ぐnode-localエージェントだ。Kubernetesの世界に例えるとkubeletと性格が少し似ている。もちろんkubeletを置き換えるものではないが、VMIという新しいワークロードタイプに対してkubelet横で動作するKubeVirt専用の実行調整者と見ればよい。

docs/components.mdvirt-handlerをホストごとに1つ必要なDaemonSetとして説明する。実際のコードであるpkg/virt-handler/vm.goを見るとこの説明がかなり正確であることが分かる。

なぜvirt-handlerがノードごとに必要なのか

クラスタ全体の状態だけ見てはVMを正確に合わせられない作業が多いからだ。

  • 実際のlibvirt domain状態読み取り
  • launcher Pod内部のQEMUとの通信
  • ノードローカルのデバイス、cgroup、SELinux、マウント状態確認
  • ネットワークとディスクattachのホストサイド補助作業
  • migrationのsourceとtargetの切り替え

これらの作業にはnode-localコンテキストが必要だ。したがってKubeVirtは各ノードにvirt-handlerを配置する。

vm.goが示す核心責任

pkg/virt-handler/vm.goVirtualMachineController構造体を見ると、このコンポーネントが単純なstatus watcherではないことが分かる。内部には以下の責任が含まれている。

  • launcherクライアント管理
  • containerディスクマウンター
  • hotplugボリュームマウンター
  • ネットワークセットアップ連携
  • cgroupマネージャー連携
  • デバイスマネージャー
  • ハートビート
  • migrationプロキシ連携
  • domain informerとVMI informerのハンドリング

つまりvirt-handlerは「VMI statusを少し更新するcontroller」ではなく、ノードでVM実行環境を整える総合エージェントだ。

動作フローを大きな絵で見ると

virt-handlerがする仕事を簡単に順序化すると次の通りだ。

  1. このノードに割り当てられたVMIをwatchする。
  2. そのVMIに対応するlauncher Podとdomain状態を見る。
  3. 必要なストレージとデバイス、ネットワーク準備を行う。
  4. launcher Pod内部のlibvirt制御planeと通信する。
  5. domain状態を再びVMI statusに反映する。
  6. 終了、再起動、migrationなどの状態遷移を調整する。

ここで重要なのはvirt-handlerがKubernetes APIとlibvirt worldの間の翻訳通路であるという点だ。

launcher Podとはどのように通信するのか

vm.golauncherClientscmd-clienthandler-launcher-comレイヤーを使用する。つまりvirt-handlerはlauncher Pod内に入っているcommand serverと通信してVMコマンドを伝達する。

代表的に以下の種類のリクエストがこの経路を通る。

  • VM実行または同期
  • シャットダウン
  • pauseまたはunpause
  • migration開始
  • migration finalize
  • ゲスト関連クエリ

重要なのはvirt-handlerがQEMUを直接forkするのではなく、launcher内部のcommand serverを通じてlibvirt domain managerを操作するという点だ。このレイヤー分離があってこそPodサンドボックス内部の実行とnode-sideオーケストレーションがきれいに分かれる。

状態反映はなぜこんなに複雑なのか

virt-handlerはVMI informerとdomain informerの両方を見る。理由は簡単だ。

  • VMIはクラスタが望む状態を含む。
  • domainはlibvirtが現在知っている実際の実行状態を含む。

この2つが異なればvirt-handlerが中間で合わせなければならない。例えば:

  • VMIはRunningであるべきだがdomainがない
  • domainは立ち上がっているがVMI statusがまだ反映されていない
  • migration中にsourceとtargetでdomain検出が異なる

このようなずれを合わせることがvirt-handlerの核心だ。

ネットワークとストレージでの役割

多くの人がネットワークとストレージはPodが既に受け取っているのでvirt-handlerはあまりすることがないと思う。実際にはそうではない。

ネットワーク

vm.gonetsetupdomainspecnetwork/vmispecと接続される。これはvirt-handlerがゲストNIC構成、domain network spec、ホストサイドワイヤリングの一部を担当するという意味だ。

特にmigration target側ではtarget Podが実際にゲストを受け入れる準備ができているか判断しなければならない。この時ネットワーク準備状態は非常に重要だ。

ストレージ

containerDiskMounterhotplugVolumeMounterhost-diskcontainer-diskなどの依存性を見ると、virt-handlerがnode-sideマウント補助とattach経路をかなり多く担当していることが分かる。

つまりPodにボリュームがattachされたからといって、すぐにゲストディスク準備が終わるわけではない。ゲストが理解できるディスク経路とhotplug状態まで繋げなければならない。

device managerとheartbeatがなぜ含まれているのか

この部分はvirt-handlerが単純なwatcherではないという強い証拠だ。

device manager

ハードウェアアクセラレーションと特殊デバイスは適切な権限と公開が必要だ。deviceManagerControllerはこれらのリソース公開と状態を管理する。

heartbeat

ノード別capabilityとhealth状態をクラスタに反映してこそスケジューリングと実行が合う。heartbeatはこの役割を担当する。

つまりvirt-handlerはノードが「VMを実行できる状態か」を持続的に報告する役割も持つ。

cgroupと権限調整までする

vm.goにはcgroup v2 unified modeでのデバイスパーミッション処理に関するコメントがある。ここで分かることは、KubeVirtが単にGoコード数行でVMを起動するのではなく、Linuxホストのリソース制約とセキュリティモデルを細心に扱っているという点だ。

例えばデバイスprobing過程で権限が間違っているとQEMU startup自体が壊れる可能性がある。そのためvirt-handlerはランタイムとカーネルの差異を吸収するバッファ層になる。

migration target controller まで別にある

pkg/virt-handler/migration-target.goを見るとmigration target専用controllerが別に存在する。これはmigrationが単純な「状態コピー」ではなく、targetノードで別の準備と検出が必要だという意味だ。

target側では:

  • target domain検出
  • domain readyタイムスタンプ記録
  • リソース調整
  • finalize呼び出し

のような作業が必要だ。つまりmigrationはvirt-controllerだけの責任でもなくvirt-launcherだけの責任でもない。virt-handlerが中間のオーケストレーションを強く担う。

よくある誤解

誤解1:virt-handlerは単純なstatus reporterだ

違う。launcherと通信し、デバイスとボリュームを扱い、migrationとネットワーク準備を調整する。

誤解2:node-local stateはほとんど重要でない

違う。VM実行は結局ホストカーネルとハイパーバイザーに触れる。node-localコンテキストなしには正確な実行と復旧が難しい。

誤解3:kubeletだけあれば十分だ

Podライフサイクルのみを管理するkubeletと、VM domainライフサイクルまで理解するvirt-handlerは役割が異なる。

デバッグで最初に見るもの

  • launcher PodはRunningだがゲストが見えなければvirt-handlerログを見る。
  • migration targetが準備されなければmigration-target側のイベントと状態を見る。
  • hotplugまたはデバイス問題ならnode-localマウント、cgroup、device manager側の痕跡を見る。
  • VMI statusと実際のdomain状態がずれていればdomain informer反映フローを見る。

まとめ

virt-handlerはKubeVirtのnode execution planeの中心だ。このコンポーネントはVMIとdomainを一緒に見て、launcher Podと通信し、node-localネットワーク、ストレージ、デバイス、migration状態を調整する。KubernetesがPodをノードに置いてくれれば、その後VMらしいライフサイクルを作り出すのはかなりの部分virt-handlerの仕事だ。

次の記事では、このvirt-handlerが命令を伝達する反対側、つまりvirt-launcher、libvirt、QEMUスタックがPod内部でどのように組織されるか見ていく。