TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

KubeVirt 源码阅读 2:API 与 CRD —— 类型、生成代码、Webhook

状态:草稿 (2026-10-17),源码 + 只读实测。 基准为 KubeVirt v1.9.0(提交 79d34c17)。Webhook 警告文本是从源码中读取的,并非在实际 kubectl 界面中看到(未创建新 VM)。

KubeVirt 源码阅读 2:API 与 CRD

目录

1. 直觉:由三个地方决定

在 VM 的 YAML 一行被接受之前,有三个地方参与其中。

  1. 类型 — types.go 的 Go 结构体决定了字段的名称和形态。
  2. 架构 ——从该结构体生成的 OpenAPI 架构加载到 CRD 中,使 kube-apiserver 能够率先过滤掉形态错误的请求。
  3. Webhook ——形态正确但语义错误的请求(例如不能同时使用的两个字段、未开启的功能门槛)会被 virt-api 的 Webhook 过滤。也可以选择不拒绝请求,而只附加 警告 。

2. 类型:types.go

staging/src/kubevirt.io/api/core/v1/ 下面的 types.go 单个文件长达 4,090 行。首先只需找到两个结构体。

Running 字段带有 Deprecated: 注释(:2010)。之所以没有直接删除并保留至今,原因可以从一个方法中看出。即将这两个字段折叠为单一运行策略的 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 。文档中提到了五种运行策略常量,但在代码中还有第六种 WaitAsReceiver (:1995)。这是针对跨集群迁移接收端 VM 的值,只有在开启功能门槛时 Webhook 才会接受它(见下文第 4 节)。

VMI 阶段(phase)列表(:1098)将成为阅读第 3、4 篇时的地图。 Scheduled 附加的注释说明了“在此处 virt-controller 的责任结束,并由 virt-handler 接管”(:1109).

3. 生成代码:无需手动修改的文件

types.go 한 파일에서 생겨나는 것들 make generate → hack/generate.sh. 생성 파일은 손으로 고치지 않는다 staging/src/kubevirt.io/api/core/v1/types.go Go 구조체 + 주석 마커(+genclient, +kubebuilder…) · 4,090줄 deepcopy-gen deepcopy_generated.go 7,410줄 swagger-doc types_swagger_generated.go 1,312줄 openapi-gen openapi_generated.go 40,507줄 · client-go/api client-gen client-go 클라이언트 kubevirt.io/client-go controller-gen crd → crd-validation-generator controller-gen 결과는 virt-operator 의 validations_generated.go(33,701줄)로 들어가 설치 때 CRD 의 openAPIV3Schema 가 된다(4절 그림의 ③ 관문). CI 는 make generate 뒤 차이가 있으면 떨어뜨린다(generate-verify).
图:从单个 types.go 生成的文件

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 附加。因此, 添加一个字段的 PR 会伴随 types.go 的一行改动带来数百行的生成文件。 审查者不会去阅读生成的文件,而只需检查 CI 是否重新运行了 make generate 并确认没有差异(generate-verify)。手动修改的生成文件在此会被剔除。

4. Webhook:请求经过的五道关卡

VM 하나를 apply 하면 — API 서버 안의 관문 왼쪽이 kube-apiserver 의 순서, 오른쪽이 그 관문을 맡은 KubeVirt 코드 ① 인증 · 인가 RBAC ② 변형 웹훅 기본값 채우기 virt-api /virtualmachines-mutate VMsMutator.Mutate ③ 스키마 검증 CRD 의 openAPIV3Schema virt-operator CRD 를 설치 validations_generated.go ④ 검증 웹훅 허용 · 거절 · 경고 virt-api /virtualmachines-validate VMsAdmitter.Admit ⑤ etcd 에 저장 그 뒤 컨트롤러가 본다 ④ 가 돌려준 경고는 kubectl 에 「Warning:」 줄로 보인다 spec.running is deprecated, please use spec.runStrategy instead. 두 웹훅 모두 failurePolicy: Fail — virt-api 가 응답하지 않으면 VM 을 만들지도 고치지도 못한다(실측: 검증 23개 · 변형 4개 모두 Fail)
图:apply 的 VM 在 kube-apiserver 中经过的关卡及其代码

谁来注册 Webhook

Webhook 配置对象(ValidatingWebhookConfiguration)也由 virt-operator 通过代码生成。 NewVirtAPIValidatingWebhookConfiguration()(webhooks.go:296)为每个路径添加一个 Webhook,并将 failurePolicy 设为 Fail 。VM 的路径是常量 VMValidatePath = "/virtualmachines-validate" (:945)。在另一端,virt-api 使用相同的常量挂载 HTTP 处理器(api.go:994)。如果仅在一端更改路径名称,Webhook 将收到 404,并且由于 Fail 策略,将导致任何人都无法创建 VM。这就是两个文件使用相同常量的原因。

VMsAdmitter.Admit() 逐行解析

vms-admitter.go:87 中的 Admit() 按以下顺序进行检查。

  1. 请求的资源是否确实是 virtualmachines ,是否符合架构(ValidateSchema).
  2. 如果是正在删除中的 VM,则直接放行。这是为了避免与其他删除操作产生竞争。
  3. 副本中应用 instancetype·preference 并填充默认值,然后对结果进行验证。注释中写道:“由于不想保存它,所以将其应用在副本上”。也就是说,验证基于“实际将要运行的 VMI”,但 etcd 中保存的仍然是用户发送的原始内容。
  4. 仅在创建时(Create):是否使用了关闭功能门槛的字段,网络配置是否正确。
  5. ValidateVirtualMachineSpec() ——模板、元数据、VMI 整体规格、DataVolume 模板、运行策略、实时更新功能。
  6. 存储方面的检查(storageadmitters.Admit)与卷请求。
  7. 如果通过,则汇集警告并予以放行。

最后一个阶段是产生 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 警告 Warning: 而非拒绝,所以 VM 照常创建,kubectl 会在响应头中显示 warnDeprecatedAPIs() (vmi-create-admitter.go:141)会在规格使用了已废弃的功能门槛状态时添加警告。

检查运行策略

validateRunStrategy()(:231)检查三项内容。如果同时使用 running 和 runStrategy 则予以拒绝,如果两者皆无则以 “RunStrategy must be specified” 拒绝,并且 runStrategy 的值必须是允许列表( validRunStrategies(:53)中的五种之一。 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 Patch;如果是 Manual·RerunOnFailure 则将启动请求写入 status。注释中写道,对于像 Always 这样本来就必须处于开启状态的策略,这样做没有意义。归根结底,子资源也 只是修改了 VM 对象,而实际创建 VMI 的工作由第 3 篇中的 VM 控制器完成。

5. 实测

在安装了 v1.9.0 的小型集群中进行读取 (2026-10-17)。

6. 局限性

Titi 的寄语。 看到一个增加字段的 PR 包含数百行代码时,请不要惊讶。真正由人工编写的只有 types.go 的几行,其余都是由机器生成的。在进行代码审查时,只需关注 types.go、Webhook 以及测试即可!

参考

均于 2026-10-17 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论