はじめに
KubeVirtを「VMのためのKubernetes拡張」と呼ぶとき、その拡張の最も目に見える表面はAPIだ。ユーザーは`kubectl`やクライアントを通じてVM、VMI、migrationリクエストを送る。しかしこのリクエストはそのままノードに降りていかない。中間には**検証、デフォルト値適用、subresource分岐、権限確認、作業オブジェクト作成**がある。
このレイヤーを担当する代表コンポーネントが`virt-api`だ。`docs/components.md`も`virt-api-server`を仮想化フローのエントリーポイントとして説明する。
`virt-api`は何をするのか
簡単に言えば`virt-api`は以下の役割を担う。
- KubeVirt関連APIの進入点提供
- VMI、VM関連リクエストのvalidationとdefaulting
- start、stop、migrateなどのsubresourceリクエスト処理
- 必要な場合に内部的に他のCR作成
ここで重要な点は`virt-api`がVMを直接起動しないということだ。`virt-api`は**ユーザーの意図をKubernetesオブジェクト操作に翻訳する**ことに集中する。
なぜ別のAPIレイヤーが必要なのか
Kubernetes CRDだけで十分に見えるかもしれない。しかし実際の運用には以下の要求がある。
- 単純なcreateやupdateだけでは表現しにくいアクション性リクエスト
- ゲスト状態を確認し衝突を防ぐ事前検証
- migrationのように別の作業オブジェクトを作成しなければならないリクエスト
- console、VNC、restartなどのsubresourceフロー
このような理由でKubeVirtはCRD定義だけで終わらず、別のAPI処理レイヤーを置く。
ソースから見える`virt-api`の性格
`pkg/virt-api/rest/lifecycle.go`を見ると、start、stop、migrate、reboot、backupなど様々なリクエストhandlerが実装されている。ここでのパターンはかなり一貫している。
1. 対象VMまたはVMIを照会する。
2. 現在の状態がアクションを許可するか検証する。
3. 直接実行せず、ステータスパッチや作業オブジェクト作成を行う。
4. 以降の実際の実行はcontrollerまたはvirt-handlerレイヤーに委譲する。
つまり`virt-api`はオーケストレーションの開始点であり、最終実行点ではない。
migrateリクエストは実際にどう変わるのか
最良の例はmigrate handlerだ。`MigrateVMRequestHandler`を見ると内部フローは非常に明瞭だ。
1. VMとVMIを照会する
まずVMが存在するか、そのVMのVMIが存在するかを確認する。
2. 現在のVMIがRunning状態か検証する
実行中でないVMIはmigrationできないのでconflictを返す。
3. migration CRを作成する
核心はここだ。handlerは直接migrationを開始しない。代わりに`VirtualMachineInstanceMigration`オブジェクトを作成する。
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstanceMigration
metadata:
generateName: kubevirt-migrate-vm-
spec:
vmiName: demo-vm
実際のコードでは`AddedNodeSelector`などのオプションも付くことがある。この瞬間からmigrationはAPIリクエストではなく**controllerが消費するdeclarative work item**になる。
なぜこの方式が重要なのか
この構造の利点は大きい。
1. 作業がKubernetesオブジェクトとして残る
migrationが別のCRになるので監査追跡と状態確認が容易だ。
2. controllerが非同期的に処理できる
APIサーバーは作業を即座に終えずacceptedを返せる。
3. ポリシーと検証がさらに分離される
APIはリクエストの有効性を見て、実際の容量とポリシー判断はcontrollerが継続的に行う。
startやstopがなぜmigrationと違って見えるのか
すべてのアクションがmigration CR作成に翻訳されるわけではない。例えばstopリクエストは場合によってはVM statusパッチやstate change requestとして表現される。`lifecycle.go`の`patchVMStatusStopped`と`getChangeRequestJson`がこのパターンをよく示している。
つまりKubeVirtはアクションごとに最も自然なKubernetes表現を選択する。
- 長時間の移動作業:別のmigration CR
- VMライフサイクル遷移:ステータスパッチまたはstate change request
- ノードサイドの即時接続が必要な作業:virt-handler URIベースのサブリソース
subresourceが必要な理由
一般的なspec updateだけでは「今migrationを開始して」「今soft rebootして」「consoleに接続して」のようなリクエストを自然に表現しにくい。そのためKubeVirtはsubresourceを置く。
この時重要な設計ポイントは以下の通り。
- リクエストはAPIレイヤーで認証と検証を通過しなければならない
- ランタイム状態を確認しconflictを素早く返さなければならない
- 実際の下位動作は適切なコンポーネントに委譲しなければならない
これはKubernetes本体がscale、exec、log、evictionなどのsubresourceを置く理由と似ている。
`virt-api`と`virt-handler`の違い
混乱しやすいポイントなので分離して見よう。
`virt-api`
- ユーザーとKubernetes APIに近い
- validation、defaulting、subresource進入点
- ステータスパッチまたは作業オブジェクト作成
`virt-handler`
- ノードに近い
- 実際のlauncher Podと通信
- ドメイン状態反映
- VM実行、終了、migration handoffに関与
どちらも「VMを制御する」と感じられるかもしれないが、`virt-api`はcontrol planeのエントリーであり、`virt-handler`はnode execution plane側に近い。
実践で見るべきAPIレイヤーの症状
migrateリクエストがすぐに失敗する
APIレイヤーconflictの可能性が大きい。代表的に:
- VMIがRunningでない
- paused状態
- アクション不可条件
リクエストはacceptedされたが何も起こらない
この場合はAPIは通過したがcontroller段階で容量、ポリシー、pending podの問題で止まっている可能性がある。
あるリクエストがパッチで別のリクエストがCR作成なのか混乱する
核心は「このリクエストが状態遷移なのか、独立した作業なのか」を基準に見ればよい。
運用者が覚えるべきデバッグ順序
1. ユーザーが送ったリクエストがどのsubresourceに入るか見る。
2. `virt-api`がconflictを出したか見る。
3. accepted以降ならどのオブジェクトが追加で作成されたか見る。
4. その後にcontrollerやnode agentに降りていく。
この順序を逆に見ると問題を見逃しやすい。
まとめ
`virt-api`の核心的な役割はVMを直接実行することではなく、ユーザーリクエストを**検証可能なKubernetes操作**に変えることだ。migrateリクエストはmigration CR作成に、stopリクエストは状態変更パッチに、一部のランタイムアクションはvirt-handler接続に翻訳される。このレイヤーを理解すると、KubeVirtがなぜKubernetesらしく動作するのか、また要求と実行がなぜ時間的に分離されるのかが鮮明に見える。
次の記事では、このリクエストとオブジェクトを実際のlauncher Pod作成に接続する`virt-controller`のinformer、queue、reconcile構造を見ていく。
현재 단락 (1/68)
KubeVirtを「VMのためのKubernetes拡張」と呼ぶとき、その拡張の最も目に見える表面はAPIだ。ユーザーは`kubectl`やクライアントを通じてVM、VMI、migrationリクエス...