ステータス: ドラフト (2026-10-17)、ソース + 読み取り専用の実測。 基準は KubeVirt
v1.9.0(コミット79d34c17)です。Webhook の警告文はソースから読み取ったものであり、実際の kubectl 画面で確認したものではありません(VM を新しく作成しませんでした)。
KubeVirt ソースコード読み解き 2: API と CRD
目次
1. 直感: 3 つの場所で決まる
VM の YAML の 1 行が受け入れられるまでに 3 つの場所が関与します。
- 型 —
types.goの Go 構造体がフィールド名と形を決定します。 - スキーマ — その構造体から生成された OpenAPI スキーマが CRD に載せられ、kube-apiserver が形の誤ったリクエストを事前にフィルタリングします。
- Webhook — 形は合っていても意味が誤っているリクエスト(同時に使用してはいけないフィールド、無効化された機能ゲート)は virt-api の Webhook が弾きます。拒否せずに 警告のみ を付与することも可能です。
2. 型: types.go
staging/src/kubevirt.io/api/core/v1/ 以下の types.go という 1 ファイルは 4,090 行あります。まずは 2 つの構造体だけを探せば十分です。
VirtualMachineInstance(types.go:47) —Spec VirtualMachineInstanceSpecとStatus VirtualMachineInstanceStatus。上記の+genclient:noStatusマーカーは「status を個別に修正するクライアントメソッドを作成するな」というジェネレーターへの指示です。VMI の status は virt-controller と virt-handler が複数で共有します(第 3・4 回)。VirtualMachine(:1942) —Spec VirtualMachineSpec(:2007)の中に VMI の枠組み(Template)と起動しておくべきか(RunningまたはRunStrategy)があります。
Running フィールドには Deprecated: コメントが付いています(:2010)。それでも削除せずに残しておいた理由は 1 つのメソッドに表れています。2 つのフィールドを 1 つの実行戦略にまとめる RunStrategy()
(:1957です。
RunStrategy := RunStrategyHalted
if vm.Spec.Running != nil {
if (*vm.Spec.Running) == true {
RunStrategy = RunStrategyAlways
}
} else if vm.Spec.RunStrategy != nil {
RunStrategy = *vm.Spec.RunStrategy
}
出典: kubevirt/kubevirt v1.9.0 types.go 1961–1968 行(Apache-2.0)。コントローラーはこのメソッドのみを呼び出すため、 running: true はコード内で直ちに Always になります。実行戦略の定数はドキュメントに 5 つ記載されていますが、コードには 6 番目の WaitAsReceiver
(:1995も存在します。クラスター間マイグレーションを受け取る側の VM のための値であり、機能ゲートが有効な場合のみ Webhook が受け入れます(以下の第 4 節)。
VMI のフェーズ一覧(:1098)は、第 3・4 回を読む際の地図になります。 Scheduled に付いているコメントが「ここで virt-controller の責任が終了し、virt-handler に引き継がれる」と述べています(:1109).
3. 生成コード: 手を加えないファイル群
make generate(Makefile:53)はビルダーコンテナー内で
hack/generate.sh を実行します。このスクリプトが順番に呼び出すジェネレーターは以下の通りです。
| ジェネレーター | 入力 | 出力 | 実測行数 |
|---|---|---|---|
swagger-doc |
types.go のコメント |
types_swagger_generated.go(API ドキュメントの説明) |
1,312 |
deepcopy-gen(generate.sh:27) |
+k8s:deepcopy-gen マーカー |
deepcopy_generated.go |
7,410 |
defaulter-gen |
デフォルト値関数 | zz_generated.defaults.go |
— |
openapi-gen |
型全体 | client-go/api/openapi_generated.go |
40,507 |
client-gen |
+genclient マーカー |
client-go の型付きクライアント |
— |
controller-gen crd(:139) |
+kubebuilder: マーカー |
CRD YAML → crd-validation-generator(:177) → virt-operator/…/validations_generated.go |
33,701 |
最後の行が特徴的です。KubeVirt は CRD YAML をリポジトリに保存せず、controller-gen が生成したスキーマを Go の文字列に変換して virt-operator の中に埋め込みます。virt-operator がインストール時に NewVirtualMachineCrd()
(crds.go:187)で CRD をコードとして構築し、 CRDsValidation["virtualmachine"]
(validations_generated.go:4444)を openAPIV3Schema として付加します。そのため、 フィールドを 1 つ追加する PR では、types.go の 1 行に対して数百行の生成ファイルが追随します。 レビュワーは生成ファイルを読み込まず、CI が make generate を再実行して差分がないかどうかだけを確認します(generate-verify)。手動で修正された生成ファイルはここで弾かれます。
4. Webhook: リクエストが通過する 5 つの関門
誰が Webhook を登録するのか
Webhook 設定オブジェクト(ValidatingWebhookConfiguration)も virt-operator がコードで作成します。
NewVirtAPIValidatingWebhookConfiguration()(webhooks.go:296)はこのパスごとに Webhook を 1 つずつ配置し、 failurePolicy を Fail に設定します。VM のパスは定数 VMValidatePath = "/virtualmachines-validate"
(:945)です。反対側で virt-api が同じ定数で HTTP ハンドラーを登録します(api.go:994)。パス名を片方だけで変更すると Webhook が 404 を受け取り、 Fail ポリシーのために誰も VM を作成できなくなってしまいます。2 つのファイルが同じ定数を使用している理由はここにあります。
VMsAdmitter.Admit() を 1 行ずつ
vms-admitter.go:87
の Admit() は以下の順序で検証を行います。
- リクエストされたリソースが実際に
virtualmachinesであるか、スキーマに適合しているか(ValidateSchema). - 削除中の VM であれば即座に許可します。他の削除処理との競合を避けるためです。
- コピーに instancetype と preference を適用し、デフォルト値を埋めた上でその結果を検証します。コメントには「保存したくないためコピーに適用する」と記載されています。つまり、検証は「実際に起動する VMI」を基準に行われますが、etcd にはユーザーが送信したそのままの内容が格納されます。
- 作成時(
Create)のみ: 機能ゲートが無効化されたフィールドを使用していないか、ネットワーク設定が正しいか。 ValidateVirtualMachineSpec()— テンプレート、メタデータ、VMI スペック全体、DataVolume の枠組み、実行戦略、ライブアップデート機能。- ストレージ関連の検査(
storageadmitters.Admit)とボリューム要求。 - 通過すると警告を収集して許可します。
最後のステップが spec.running の警告が発生する箇所です(:175–178).
warnings := warnDeprecatedAPIs(&vm.Spec.Template.Spec, admitter.ClusterConfig)
if vm.Spec.Running != nil {
warnings = append(warnings, "spec.running is deprecated, please use spec.runStrategy instead.")
}
出典: kubevirt/kubevirt v1.9.0 vms-admitter.go 175–178 行(Apache-2.0)。拒否ではなく AdmissionResponse.Warnings であるため VM はそのまま作成され、kubectl はレスポンスヘッダーの警告を Warning: 行で表示します。 warnDeprecatedAPIs()
(vmi-create-admitter.go:141)は、非推奨状態になった機能ゲートをスペックが使用している場合に警告を追加します。
実行戦略の検査
validateRunStrategy()(:231)では 3 つの点を確認します。 running と runStrategy を同時に使用している場合は拒否、両方とも指定されていない場合は「RunStrategy must be specified」として拒否、そして
runStrategy の値が許可リスト validRunStrategies(:53)の 5 つのいずれかであるかです。 WaitAsReceiver はこのリストに含まれておらず、 DecentralizedLiveMigration 機能ゲートが有効な場合のみ個別に通過します。
Webhook 以外のもう一つの入口: サブリソース
virt-api は Webhook サーバーでありながら 集約 API サーバー(v1.subresources.kubevirt.io)でもあります。 virtctl start は VM を直接修正するのではなく
…/virtualmachines/<이름>/start を呼び出します。ハンドラー StartVMRequestHandler()
(lifecycle.go:43)は実行戦略を確認し、
Halted の場合は VM の spec.runStrategy(または旧 spec.running)を JSON パッチに変換し、 Manual·RerunOnFailure の場合は status に起動リクエストを挿入します。 Always のようにすでに起動していなければならない戦略には意味がないとコメントに記載されています。結局のところ、サブリソースも VM オブジェクトを修正するだけであり、実際に VMI を作成する作業は第 3 回の VM コントローラーが行います。
5. 実測
v1.9.0 がインストールされた小規模なクラスターで読み取りました(2026-10-17)。
- Webhook の設定。
virt-api-validatorに検証 Webhook が 23 個、virt-api-mutatorに変更 Webhook が 4 個あり、 27 個すべてがfailurePolicy: Failです。VM のものはvirtualmachine-validator.kubevirt.io→/virtualmachines-validate、操作がCREATE·UPDATEであり、上記の定数と一致します。VMI には作成(/virtualmachineinstances-validate-create)と更新(…-update)が個別に存在します。同じクラスターの CDI も Webhook 設定を 4 つ別個に保持しています。 - CRD のバージョン。
virtualmachines.kubevirt.ioとvirtualmachineinstances.kubevirt.ioはどちらもv1·v1alpha3の 2 つのバージョンを提供しており、ストレージバージョン(status.storedVersions)はv1の 1 つです。 - サブリソース。
/apis/subresources.kubevirt.io/v1が 31 個提供されています。VM 側はstart·stop·restart·migrateなど、VMI 側はconsole·vnc·pause·freeze·addvolumeなどです。「スチューデント VM が起動するまで」の第 2 回で見た通り、execは存在しません。 running: trueで作成された VM。 実習バックエンドによって作成された 1 つの VM はまだspec.running: trueを使用しています。その VM のstatus.runStrategyはAlwaysでした。上記のRunStrategy()の統合が status にまで及んでいることになります(status に記述する箇所は vm.go:2478)。作成時に受け取った警告はバックエンドが記録していないため 未実測です。
6. 制限事項
- 警告文はソースから読み取りました。同じ VM をサーバー側の dry-run として送信すれば VM を作成せずに警告を確認することも可能ですが、今回はクラスターへの書き込みリクエストを一切送信しない方針としたため実施していません。
- 変更 Webhook(
vm-mutator.go)が埋め込むデフォルト値のリストは追いかけていません。デフォルト値の大半は VMI 側(vmi-mutator.go,pkg/defaults)に存在します。 - ジェネレーターのバージョンと実行時間は 未実測です(
make generateを実行しなかったため)。
ティティの一言。 フィールドを 1 つ追加する PR が数百行に及んでいても驚かないでください。実際に人間が書いたのは types.go の数行だけで、残りは機械が書いたものです。レビューする際は types.go と Webhook、そしてテストを見るだけで十分です!
参考
すべて 2026-10-17 に閲覧しました。
types.go: https://github.com/kubevirt/kubevirt/blob/v1.9.0/staging/src/kubevirt.io/api/core/v1/types.govms-admitter.go: https://github.com/kubevirt/kubevirt/blob/v1.9.0/pkg/virt-api/webhooks/validating-webhook/admitters/vms-admitter.gohack/generate.sh: https://github.com/kubevirt/kubevirt/blob/v1.9.0/hack/generate.sh- Kubernetes ドキュメント、動的アドミッション制御(警告を含む): https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
- 姉妹シリーズ「スチューデント VM が起動するまで」第 2 回: KubeVirt の内部