Skip to content
Published on

KubeVirtソースコード読み取りマップ:どこから読めば全体構造が掴めるか

Authors

はじめに

KubeVirtはコード量も多く階層も深い。初めてrepoを開くとvirt-apivirt-controllervirt-handlervirt-launcherpkg/networkstagingが一斉に見えてどこから読むべきか途方に暮れる。

しかし実際は読む順序さえ正しければ構造はかなり鮮明になる。このシリーズ最後の記事では「KubeVirtを理解するためにどの質問をどのパッケージで解決すべきか」を基準にソース読み取りマップを整理する。

まず持つべき質問

KubeVirtコードを読むときはファイルツリーより質問の順序が重要である。以下の順序を推奨する。

  1. VMはどのKubernetesオブジェクトで表現されるか
  2. そのオブジェクトを誰が監視しPodを作るか
  3. ノードに到着した後、誰がlibvirtとQEMUを実行するか
  4. Podネットワークがゲストネットワークにどう変わるか
  5. マイグレーションはコントロールプレーンとデータプレーンでそれぞれどう進むか
  6. 実際にどのカーネルプリミティブが使われるか

この質問順序で読めば、KubeVirtを「巨大なGoリポジトリ」ではなく「VM実行パイプライン」として見ることができる。

ステップ1:API型から読む

最初に読むべき場所はstaging/src/kubevirt.io/api/core/v1である。

特に以下のファイルが重要である。

  • types.go
  • schema.go

ここで理解すべきは三つ。

  • VirtualMachine
  • VirtualMachineInstance
  • VirtualMachineInstanceMigration

このレイヤーを先に読めば、以降のcontrollerコードで何をreconcileしているかが見え始める。特にstatusconditionsmigrationState、インターフェース型を先に読んでおけば後に出てくるほぼすべてのロジックが楽になる。

つまり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.go
  • migration-target.go
  • migration-proxy/migration-proxy.go
  • seccomp/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.gopkg/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ネットワークは一つのファイルでは理解できない。以下のパッケージをまとめて読むほうがよい。

  • setup
  • multus
  • controllers
  • dhcp
  • migration

質問は単純である。

「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-chroot
  • pkg/virt-handler/cgroup
  • pkg/virt-handler/selinux
  • pkg/network/driver/virtchroot

このときはKubernetes抽象化よりLinuxプリミティブを基準に読むべきである。

  • chroot
  • namespace進入
  • cgroup v1、v2
  • SELinuxラベル切り替え
  • TAP作成
  • デバイスアクセス許可

この段階まで見れば「なぜVMがPod上で実装できたのか」という質問の技術的答えがほぼ完成する。

推奨する実際の読む順序

最初から最後まで一回読むなら以下の順序を推奨する。

  1. staging/src/kubevirt.io/api/core/v1/types.go
  2. staging/src/kubevirt.io/api/core/v1/schema.go
  3. pkg/virt-controller/watch/vmi/vmi.go
  4. pkg/virt-controller/watch/migration/migration.go
  5. pkg/virt-handler/vm.go
  6. pkg/virt-handler/migration-target.go
  7. cmd/virt-launcher/virt-launcher.go
  8. pkg/virt-launcher/virtwrap/manager.go
  9. pkg/virt-launcher/virtwrap/converter/converter.go
  10. pkg/network/setup/network.go
  11. pkg/network/setup/podnic.go
  12. pkg/network/controllers/vmi.go
  13. pkg/network/multus/annotation.go
  14. pkg/virt-launcher/virtwrap/live-migration-source.go
  15. pkg/virt-handler/migration-proxy/migration-proxy.go
  16. pkg/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を最も正直に見せてくれるマップである。