- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- まず持つべき質問
- ステップ1:API型から読む
- ステップ2:virt-apiでユーザーの意図がどう標準化されるか見る
- ステップ3:virt-controller/watchでPod作成とhandoffを読む
- ステップ4:virt-handlerを読めば「ノードで実際に誰が働くか」が見える
- ステップ5:cmd/virt-launcherとvirtwrapで実際のVM実行パスを読む
- ステップ6:ネットワークはpkg/networkをまとめて読む
- ステップ7:マイグレーションはcontrollerとlauncherを往復しながら読む
- ステップ8:ホスト統合コードはカーネルプリミティブ観点で読む
- 推奨する実際の読む順序
- 読むときに特に注意すべき点
- シリーズ全体を一文で要約すると
- まとめ
はじめに
KubeVirtはコード量も多く階層も深い。初めてrepoを開くとvirt-api、virt-controller、virt-handler、virt-launcher、pkg/network、stagingが一斉に見えてどこから読むべきか途方に暮れる。
しかし実際は読む順序さえ正しければ構造はかなり鮮明になる。このシリーズ最後の記事では「KubeVirtを理解するためにどの質問をどのパッケージで解決すべきか」を基準にソース読み取りマップを整理する。
まず持つべき質問
KubeVirtコードを読むときはファイルツリーより質問の順序が重要である。以下の順序を推奨する。
- VMはどのKubernetesオブジェクトで表現されるか
- そのオブジェクトを誰が監視しPodを作るか
- ノードに到着した後、誰がlibvirtとQEMUを実行するか
- Podネットワークがゲストネットワークにどう変わるか
- マイグレーションはコントロールプレーンとデータプレーンでそれぞれどう進むか
- 実際にどのカーネルプリミティブが使われるか
この質問順序で読めば、KubeVirtを「巨大なGoリポジトリ」ではなく「VM実行パイプライン」として見ることができる。
ステップ1:API型から読む
最初に読むべき場所はstaging/src/kubevirt.io/api/core/v1である。
特に以下のファイルが重要である。
types.goschema.go
ここで理解すべきは三つ。
VirtualMachineVirtualMachineInstanceVirtualMachineInstanceMigration
このレイヤーを先に読めば、以降のcontrollerコードで何をreconcileしているかが見え始める。特にstatus、conditions、migrationState、インターフェース型を先に読んでおけば後に出てくるほぼすべてのロジックが楽になる。
つまりAPI型はドキュメントではなく、残りのコードを解釈する辞書である。
ステップ2:virt-apiでユーザーの意図がどう標準化されるか見る
次はvirt-apiとadmissionレイヤーである。ここで見るべき質問はこれである。
「ユーザーが入れたspecがどのような検証とdefaultingを経てcontrollerが処理可能なオブジェクトになるか」
この段階を読めば:
- どのフィールド組み合わせが許可されるか
- どのsubresourceがあるか
- VMIが直接実行されるか、VMを通じて管理されるか
が掴める。
つまりvirt-apiは実行エンジンではなく、ユーザー宣言を安全なcontrol-plane入力に変える門である。
ステップ3:virt-controller/watchでPod作成とhandoffを読む
その次はpkg/virt-controller/watchである。この段階で質問は変わる。
「この宣言を誰がlauncher PodとマイグレーションPodに変えるか」
特に以下のフローが重要。
- VMI controller
- Migration controller
- Template service
理解すべきポイント:
- informerとwork queue
- owner referenceとexpectation
- launcher Podマニフェストレンダリング
- migration target Pod作成
つまりこの区間はKubeVirtがKubernetes controllerパターンを最も正統的に使うレイヤーである。
ステップ4:virt-handlerを読めば「ノードで実際に誰が働くか」が見える
多くの人がここで初めてKubeVirtの実体を感じる。pkg/virt-handlerはnode-localエージェントであり、VM運用のかなりの部分がここで行われる。
読み始めに良いファイル:
vm.gomigration-target.gomigration-proxy/migration-proxy.goseccomp/seccomp.go
このレイヤーで答える質問:
- ノードにあるVMIとdomainをどうsyncするか
- launcherとどのRPCで通信するか
- マイグレーションのソースとターゲットをどう準備するか
- cgroup、device、network、seccompをどう扱うか
つまりvirt-handlerはKubeVirtの「現場監督」に最も近い。
ステップ5:cmd/virt-launcherとvirtwrapで実際のVM実行パスを読む
VMが実際にどう立ち上がるかを見るには結局cmd/virt-launcher/virt-launcher.goとpkg/virt-launcher/virtwrapまで降りる必要がある。
ここで重要な質問はこれである。
「VMI specがlibvirtドメインとQEMU実行状態にどう変換されるか」
重要ポイント:
- libvirt接続作成
- コマンドサーバー起動
- ドメインマネージャー実装
- ゲストエージェントポーリング
- イベント収集
- ドメインstats収集
そして必ず一緒に見るべきパッケージがconverterである。pkg/virt-launcher/virtwrap/converter/converter.goはVMI specをドメインXML観点に翻訳する核心レイヤーである。
つまりvirtwrapはKubeVirtでハイパーバイザーに最も近いコードである。
ステップ6:ネットワークはpkg/networkをまとめて読む
KubeVirtネットワークは一つのファイルでは理解できない。以下のパッケージをまとめて読むほうがよい。
setupmultuscontrollersdhcpmigration
質問は単純である。
「Podがすでに受け取ったネットワークをゲストがどう使えるようになるか」
見るべきもの:
- Pod NIC読み取り
- TAPとブリッジ準備
- masqueradeアドレス計算
- DHCPレスポンス
- Multusアノテーション生成
- インターフェースstatus反映
つまりKubeVirtネットワークはCNIを置き換えるものではなく、PodネットワークをゲストがVisibleなネットワークに再加工するコードである。
ステップ7:マイグレーションはcontrollerとlauncherを往復しながら読む
ライブマイグレーションは一つのファイルで終わらない。必ず往復して読む必要がある。
- コントロールプレーン:
pkg/virt-controller/watch/migration/migration.go - ノードプレーン:
pkg/virt-handler/migration-target.go - transport補助:
pkg/virt-handler/migration-proxy/migration-proxy.go - ハイパーバイザープレーン:
pkg/virt-launcher/virtwrap/live-migration-source.go
読むときに付けるとよい質問:
- target Podは誰が作ったか
- sync addressとportはどこで埋められるか
- pre-copyからpost-copyへ誰が切り替えるか
- abortとtimeoutは誰が判断するか
つまりマイグレーションはcontroller、nodeエージェント、launcherを縦に貫通して読まないと理解できない。
ステップ8:ホスト統合コードはカーネルプリミティブ観点で読む
最後にはホスト統合コードをまとめて読み直すのがよい。
cmd/virt-chrootpkg/virt-handler/cgrouppkg/virt-handler/selinuxpkg/network/driver/virtchroot
このときはKubernetes抽象化よりLinuxプリミティブを基準に読むべきである。
- chroot
- namespace進入
- cgroup v1、v2
- SELinuxラベル切り替え
- TAP作成
- デバイスアクセス許可
この段階まで見れば「なぜVMがPod上で実装できたのか」という質問の技術的答えがほぼ完成する。
推奨する実際の読む順序
最初から最後まで一回読むなら以下の順序を推奨する。
staging/src/kubevirt.io/api/core/v1/types.gostaging/src/kubevirt.io/api/core/v1/schema.gopkg/virt-controller/watch/vmi/vmi.gopkg/virt-controller/watch/migration/migration.gopkg/virt-handler/vm.gopkg/virt-handler/migration-target.gocmd/virt-launcher/virt-launcher.gopkg/virt-launcher/virtwrap/manager.gopkg/virt-launcher/virtwrap/converter/converter.gopkg/network/setup/network.gopkg/network/setup/podnic.gopkg/network/controllers/vmi.gopkg/network/multus/annotation.gopkg/virt-launcher/virtwrap/live-migration-source.gopkg/virt-handler/migration-proxy/migration-proxy.gopkg/virt-handler/seccomp/seccomp.go
この順序はコントロールプレーンから始めてnode、launcher、network、migration、カーネル統合へと下っていく流れである。
読むときに特に注意すべき点
1. パッケージ単位よりhandoffポイントを見る
KubeVirtはhandoffが多いシステムである。
- APIからcontrollerへ
- controllerからlauncher Podへ
- controllerからvirt-handlerへ
- virt-handlerからlauncher RPCへ
- launcherからlibvirtとQEMUへ
したがってパッケージ境界より「誰が次の段階に渡すか」を追跡するほうが理解が早い。
2. statusフィールド定義を何度も見返す
コードを読んでいて道に迷ったらtypes.goのstatus構造体に戻るのがよい。実際に多くのcontrollerロジックはstatusを埋めるか解釈するコードである。
3. マイグレーションは必ずソースとターゲットを同時に考える
単一VMライフサイクルに慣れた人はマイグレーションコードを読んでいてよく混乱する。ソースPod、ターゲットPod、ソースノード、ターゲットノード、ソース状態、ターゲット状態が同時に存在するからである。
シリーズ全体を一文で要約すると
KubeVirtはKubernetes上にもう一つのハイパーバイザーを載せたのではなく、Kubernetesの宣言型コントロールプレーンとLinuxの仮想化プリミティブ、そしてlibvirtとQEMUを精巧に繋ぎ合わせたオーケストレーターである。
まとめ
このシリーズではAPI型から始めてcontroller、nodeエージェント、launcher、ネットワーキング、マイグレーション、カーネル、セキュリティ、可観測性、障害モードまでKubeVirtを一本の筋で追った。最初は複雑に見えても、実際は宣言をPodに変え、Pod内でQEMUを立ち上げ、Linuxプリミティブでネットワークとデバイスを接続し、マイグレーションと状態報告をcontrollerパターンで包む構造である。
結局「VMがどうやってPod上で可能だったか」という質問の答えは一つの魔法ではなく、複数レイヤーのhandoffを正確に理解することにある。KubeVirtソースコードはそのhandoffを最も正直に見せてくれるマップである。