Skip to content
Published on

virt-controller deep dive:informer、queue、reconcileでVM Podを作る方法

Authors

はじめに

KubeVirtで「cluster-wide brain」に最も近いコンポーネントはvirt-controllerだ。VMを実際に実行するのはノード側のvirt-handlervirt-launcherだが、どのPodをいつ作り、どの状態を次の段階に進めるか調整する中心virt-controllerだ。

この記事はpkg/virt-controller/watch/application.gopkg/virt-controller/watch/vmi/vmi.gopkg/virt-controller/watch/migration/migration.goを中心に、このcontrollerがどのinformerを接続し、どのキューを回し、どのようにlauncher Podを作成するか見る。

virt-controllerは単一のcontrollerではない

名前だけ見ると単一プロセスと単一reconcileループのように感じるが、実際には複数のwatcherとcontrollerがまとめられている。application.goを見ると以下の系列が一箇所に配置されている。

  • VMI controller
  • VM controller
  • Migration controller
  • Replica set、pool、clone controller
  • Snapshot、export、backup関連controller
  • Node、workload update、disruption budget関連controller

つまりvirt-controllerはKubeVirtのcluster-wideオーケストレーション集合だ。

なぜinformerがこんなに多いのか

NewControllerシグネチャを見ると、VMI controllerだけでも複数のinformerを受け取る。

  • VMI informer
  • VM informer
  • Pod informer
  • PVC informer
  • Migration informer
  • Storage class informer
  • DataVolume、CDI informer
  • KubeVirt CR informer

これほど多い理由は、VMIのdesired stateがVMI spec一つだけで決定されないからだ。

  • ストレージが準備できているか
  • 既にPodがあるか
  • migrationが進行中か
  • cluster configがどのfeature gateを有効にしているか
  • ネットワークannotationをどう生成すべきか

controllerはこれらすべての周辺状態を総合してlauncher Podを正確に作れる。

watch/vmi/vmi.goが示す典型的なパターン

VMI controllerはKubernetes標準controllerパターンと非常に似ている。

1. イベントを受ける

VMI、Pod、DataVolume、PVC、VM、KubeVirtオブジェクトにevent handlerを登録する。

2. キーをキューに入れる

実際の作業はevent handlerで直接行わずキューに渡す。

3. syncループが現在の状態を読む

indexerとstoreからVMI、Pod、PVC、migration状態を再読み込みし、desired stateとcurrent stateを比較する。

4. 必要なPodまたはstatusを作る

launcher Pod作成、annotationアップデート、network statusアップデート、expectation管理がここで行われる。

このパターンの利点はrace conditionを緩和し、idempotentなreconcileを可能にする点だ。

launcher Podはどのように作られるか

VMI controllerはPod YAMLを手動で組み立てない。templateServiceを通じてRenderLaunchManifestのようなメソッドを呼び出す。つまりcontrollerは「Podが必要だ」という決定をし、template serviceが実際のlauncher Pod specをレンダリングする。

この分離はかなり重要だ。

  • controllerは状態判断に集中する
  • template serviceはPod spec構成に集中する

おかげでmigration Pod、hotplug attachment Podのような変形も別のレンダリングパスで処理できる。

controllerがネットワークとストレージを直接知っている理由

多くの人がここで質問する。「ネットワークとストレージはノードで付くものではないか?なぜcontrollerがannotation generatorを持っているのか?」

答えは事前配線作業のためだ。

watch/vmi/vmi.goを見ると以下の依存性がある。

  • network annotations generator
  • storage annotations generator
  • network status updater
  • network spec validator
  • migration evaluator

つまりcontrollerは単にPodを作るだけでなく、Podがノードに到着した時にKubeVirtとCNI側が必要なコンテキストを知れるようにannotationと状態を準備する。

expectationパターンが重要な理由

Kubernetes controllerを読んでいるとexpectationsパターンによく出会う。KubeVirtも同様だ。

VMI controllerには以下がある。

  • pod expectations
  • vmi expectations
  • pvc expectations

このパターンは「今Pod作成リクエストを出したので、informerキャッシュにまだ反映されていなくても早すぎるリトライをしない」という問題を解決する。controllerがcreate call直後にstale cacheを読んで重複作成することを減らす。

大規模クラスタではこのパターンは非常に重要だ。VMI一つが通常のPodよりも複雑な周辺リソースを持つため、重複作成ははるかに大きな混乱を引き起こす。

いつvirt-handlerに責任が移るのか

docs/components.mdはPodがノードにスケジュールされnodeNameが決まった後に責任がvirt-handler側に移ると説明する。実際のメンタルモデルもほぼこの通りだ。

  1. virt-controllerがlauncher Podを作る。
  2. Kubernetesがそのpodを特定ノードにスケジューリングする。
  3. controllerはPodとVMI状態を反映する。
  4. 該当ノードのvirt-handlerがそのVMIを見てVMラウンチを引き継ぐ。

つまりvirt-controllerはクラスタスケジューラとノードエージェントの間のhandoff coordinatorだ。

migrationではなぜPodが2つになりうるのか

この点が通常のPod controllerとKubeVirt controllerを区別する。VMIは平常時「VMI一つにlauncher Pod一つ」のように見えるが、migration中はtarget Podが追加で作られる。

そのためmigration controller側では:

  • target Pod作成
  • sourceとtarget状態追跡
  • concurrent migration制限
  • unschedulable timeoutの管理

のようなロジックが追加される。

pkg/virt-controller/watch/migration/migration.goを見るとpending timeout、priority queue、policy store、handoff mapなどの仕組みが登場する。これはmigrationが単純なPod再スケジューリングではなく、生きているゲストを移動する複合作業だからだ。

application.goが示す全体オーケストレーションの姿

VirtControllerApp構造体を見るとKubeVirtがcluster-wideで何を管理しているか分かる。

  • informer factory
  • 各種resource informer
  • template service
  • cluster config
  • migration controller
  • backup、export、snapshot controller

ここで分かる重要な事実は、virt-controllerが単純なVM Pod作成器よりもはるかに広い役割を持つということだ。VMライフサイクル全般の中央オーケストレーション層だ。

よくある誤解

誤解1:virt-controllerがVMを直接実行する

違う。virt-controllerはPodを作り状態を調整する。実際のVMプロセスの起動はノード側で起こる。

誤解2:controllerはVMIだけ見れば十分だ

違う。Pod、PVC、migration、cluster config、CDI状態を一緒に見てこそlauncher Podを正確に作れる。

誤解3:migrationは単にPodを再スケジュールすることだ

違う。生きているゲストの状態とネットワーク、ディスクの可視性を維持しながらtarget Podを準備しなければならないので、別のmigration controllerが必要だ。

運用者が見るべき症状と場所

VMIはあるがlauncher Podが作られない

  • virt-controllerログ
  • DataVolumeまたはPVCの準備状態
  • network spec validationエラー

migrationがpendingで長く止まる

  • target Podスケジューリング可否
  • migration controller timeout
  • cluster-wide parallel migration制限

Podはあるが状態がずれている

  • informerキャッシュ反映遅延
  • expectation関連リトライ
  • VMI statusパッチ衝突

まとめ

virt-controllerはKubeVirtのcluster-wide調整者だ。このコンポーネントは様々なinformerを通じてVMI周辺の世界を読み、queueベースreconcileでlauncher Podを作り、適切なタイミングで責任をvirt-handlerに渡す。migrationが複雑な理由もここで明らかになる。VMIは単純なPodではなく、controllerが複数のリソースを組み合わせて作り出す仮想化ワークロードだからだ。

次の記事では、このhandoffを受けたvirt-handlerがノードでどのようにVMライフサイクルを引き継ぐか見ていく。