はじめに
`virt-controller`がcluster-wideオーケストレーションであるなら、`virt-handler`は各ノードで実際のVMライフサイクルを引き継ぐnode-localエージェントだ。Kubernetesの世界に例えるとkubeletと性格が少し似ている。もちろんkubeletを置き換えるものではないが、**VMIという新しいワークロードタイプに対してkubelet横で動作するKubeVirt専用の実行調整者**と見ればよい。
`docs/components.md`も`virt-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.go`の`VirtualMachineController`構造体を見ると、このコンポーネントが単純な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.go`は`launcherClients`、`cmd-client`、`handler-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.go`は`netsetup`、`domainspec`、`network/vmispec`と接続される。これは`virt-handler`がゲストNIC構成、domain network spec、ホストサイドワイヤリングの一部を担当するという意味だ。
特にmigration target側ではtarget Podが実際にゲストを受け入れる準備ができているか判断しなければならない。この時ネットワーク準備状態は非常に重要だ。
ストレージ
`containerDiskMounter`、`hotplugVolumeMounter`、`host-disk`、`container-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内部でどのように組織されるか見ていく。
현재 단락 (1/72)
`virt-controller`がcluster-wideオーケストレーションであるなら、`virt-handler`は各ノードで実際のVMライフサイクルを引き継ぐnode-localエージェントだ...