状态:草稿 (2026-10-17),源码 + 只读实测。 基准为 KubeVirt
v1.9.0(提交79d34c17)。「实测」标记表示仅在安装了 v1.9.0 的小集群中通过阅读确认的值。未运行构建与测试(见下文「局限性」)。
阅读 KubeVirt 源代码 1:仓库地图
目录
1. 直觉:附加到 Kubernetes 上的五个程序
KubeVirt 不会修改 Kubernetes。它只是添加了 CRD(VirtualMachine、VirtualMachineInstance 等),并在集群中运行几个观察这些 CRD 的程序。在阅读仓库时,如果也以这五个程序为基准进行划分,就不会迷失方向。
| 程序 | 运行位置 | 一句话角色 |
|---|---|---|
| virt-operator | 控制平面 (Deployment) | 安装其余组件并搭建平台 |
| virt-api | 控制平面 (Deployment) | 通过 Webhook 检查请求,并提供 console、start 等子资源 |
| virt-controller | 控制平面 (Deployment,单个领导者) | 观察 VM 创建 VMI,观察 VMI 创建 Pod |
| virt-handler | 每个节点 (DaemonSet) | 将到达其节点的 VMI 与实际域进行匹配 |
| virt-launcher | 每个 VM 一个 (Pod) | 将 VMI 转换为 libvirt 域 XML 并启动 QEMU |
如果说「学生 VM 是如何启动的」第 2 篇(../kubevirt-lab-vm/02-kubevirt-inside.md)从用户的视角解释了这五个角色的作用,那么本文则
探讨这些角色位于仓库的哪个文件夹中。
2. 图解:文件夹与运行位置
3. 逐个文件夹解析
cmd/ —— 入口很轻量
cmd/ 下面的每个文件夹中都有一个 main ,大部分只有几行。virt-controller 的全部内容就是这个
(cmd/virt-controller/virt-controller.go:26).
func main() {
watch.Execute()
}
主体是 pkg/virt-controller/watch/application.go 的 Execute()
(:303)。它读取标志,创建 Informer,如果在领导者选举中获胜,就会在 onStartedLeading()
(:609)中通过 Goroutine 运行 VM、VMI、迁移等控制器。例外的是节点侧的两个程序。 cmd/virt-handler/virt-handler.go 的 Run()
(:209)和 cmd/virt-launcher/virt-launcher.go 的
main()(:347)的组装代码很长,所以在
cmd/ 里面也有很多值得一读的内容。我们在第 4、5 篇中跟随这两者。
cmd/ 中除了这五个之外,还有许多小型辅助工具。例如 virt-launcher-monitor(包裹 launcher 并监视到最后的 PID 1)、 virt-chroot
(当 handler 在其他命名空间中运行命令时)、 virt-tail(传输串行控制台日志的 Sidecar)、 virtctl(用户 CLI) 等。
pkg/virt-* —— 五个组件的主体
| 文件夹 | 首先要看的文件 | 涉及的篇章 |
|---|---|---|
pkg/virt-api |
api.go, webhooks/validating-webhook/admitters/, rest/ |
2 |
pkg/virt-controller |
watch/vm/vm.go, watch/vmi/lifecycle.go, services/template.go |
3 |
pkg/virt-handler |
vm.go, cache/domain-watcher.go, device-manager/ |
4·7 |
pkg/virt-launcher |
virtwrap/manager.go, virtwrap/converter/converter.go |
5 |
pkg/virt-operator |
resource/generate/components/(通过代码创建 CRD、Webhook、DaemonSet) |
2 |
pkg/ pkg/ 的其余部分 —— 共同使用的部件
组件文件夹之外的包可以通过「谁在调用」来理解。 pkg/network 被 virt-handler(准备 Pod 网络)和
virt-launcher(域的 <interface>)共同使用。 pkg/controller 是 Informer 工厂和期望值 (expectations) 等控制器通用工具, pkg/virt-config 是 KubeVirt CR 的配置和功能开关, pkg/hypervisor 是每个虚拟机监控程序的差异(KVM 和新引入的 MSHV)。第 3 篇中内存开销的计算就在这个 pkg/hypervisor/kvm 中。 pkg/libvmi 是创建 VMI 的构建器,测试代码经常使用它。
staging/src/kubevirt.io/ —— 独立发布的两个模块
API 类型(kubevirt.io/api)和客户端(kubevirt.io/client-go)由于其他项目也会引用,因此是独立的 Go 模块。与 Kubernetes 方式相同,根目录的 go.mod 将这两者作为 replace 绑定到仓库内的 staging/ 文件夹中。因此,修改 VM 字段的 PR 会修改 staging/src/kubevirt.io/api/core/v1/types.go ,重新生成代码并一同提交(第 2 篇)。
tests/ tests/ —— 在真实集群中运行的功能测试
单元测试位于代码旁边的 *_test.go 中(实测: pkg/ 下方 717 个),使用 Ginkgo 和 Gomega 编写,并在伪客户端上运行。 tests/ 是
功能测试 (functional test) 。它在真实集群中安装 KubeVirt 并启动 VM 进行验证。每个测试都附加了诸如 decorators.SigCompute
之类的标签,可以按 SIG 进行选择运行。
hack/ 与 Makefile —— 在容器内构建
Makefile 的几乎所有目标都会通过 hack/dockerized 。由于是在构建器容器内使用 Bazel(如果是 KUBEVIRT_NO_BAZEL=true 则使用 go)进行构建,因此您的计算机上只需要 Docker(或 Podman)和 rsync。
| 命令 | 所做的事情 | 位置 |
|---|---|---|
make |
格式化 + Bazel 构建 + 清单 | Makefile:17 |
make generate |
重新生成所有代码(第 2 篇) | Makefile:53 |
make test / make go-test |
单元测试 (Bazel / go) | :93 · :90 |
make cluster-up · cluster-sync |
使用 kubevirtci 在 VM 中启动开发集群并上传我的构建 | :173 |
make functest |
在该集群中进行功能测试 | :101 |
只运行部分功能测试时,可以像 FUNC_TEST_ARGS='--focus-file=vmi_networking' make functest 那样传递 Ginkgo 过滤器
(docs/getting-started.md 的 Testing 节)。由于开发集群将节点作为 VM 启动,因此需要 支持嵌套虚拟化的 Linux 机器。在 Mac 上,虽然可以进行构建,但 cluster-up 很困难。
4. 实测:在集群中看到的五个程序
读取了安装了 v1.9.0 的小集群的 kubevirt 命名空间(2026-10-17)。
| 内容 | 数量 | 镜像 |
|---|---|---|
| virt-operator | Deployment 2/2 | quay.io/kubevirt/virt-operator:v1.9.0 |
| virt-api | Deployment 2/2 | quay.io/kubevirt/virt-api:v1.9.0 |
| virt-controller | Deployment 2/2 | quay.io/kubevirt/virt-controller:v1.9.0 |
| virt-exportproxy | Deployment 2/2 | quay.io/kubevirt/virt-exportproxy:v1.9.0 |
| virt-handler | DaemonSet 5/5(每个工作节点) | quay.io/kubevirt/virt-handler:v1.9.0 |
| virt-launcher | 与运行的 VM 数量相同(3个) | VMI status 的 launcherContainerImageVersion: …virt-launcher:v1.9.0 |
在相同的命名空间中也能看到 virt-template-apiserver·virt-template-controller(v0.2.2)。这是一个处理 VM 模板的独立项目,在仓库中仅通过 hack/virt-template/sync.sh 匹配清单引入。本系列不予讨论。
各文件夹大小。 这是剔除了测试和生成文件后的 Go 代码行数(v1.9.0, find … ! -name '*_test.go' ! -name '*generated*').
| 文件夹 | 文件 | 行数 | 测试文件 | 测试行数 |
|---|---|---|---|---|
pkg/virt-launcher |
119 | 26,910 | 128 | 32,700 |
pkg/virt-handler |
74 | 24,520 | 62 | 17,174 |
pkg/virt-controller |
42 | 23,313 | 44 | 34,602 |
pkg/virt-operator |
57 | 21,260 | 48 | 14,596 |
pkg/storage |
45 | 18,007 | 47 | 24,614 |
pkg/virt-api |
49 | 13,514 | 43 | 20,893 |
staging/…/api |
55 | 11,831 | 2 | 52 |
pkg/network |
75 | 9,913 | 74 | 16,213 |
virt-controller 有 42 个文件,共 2.3 万行,因此文件较大。 watch/vm/vm.go 单个文件就有 3,640 行。相反,virt-launcher 有许多小文件。有四个文件夹的测试行数多于正文,这也值得注意。这就是在评审中经常听到「请先展示测试」这句话的原因(第 8 篇)。
从日志跳转到源码的方法。 KubeVirt 组件的日志是 JSON 格式, "pos" 字段中包含 파일:줄 。将实测日志的
pos 与 v1.9.0 源码进行对照,发现完全吻合。例如,virt-handler 的 "pos":"vm.go:1459" 是
pkg/virt-handler/vm.go:1459 的
VMI is in phase: … | Domain status: … 日志行,virt-launcher 的 "pos":"manager.go:1651" 是 Domain defined. 。
初次阅读代码时, 选择日志中的一行并从该行号开始 倒推是最快的方法。不过,如果版本不同,行号也会不同,因此请打开与集群版本相同的标签。
5. 从哪里开始读
建议按照启动一个 VM 的路径来阅读。在每个阶段,只需抓住「输入是什么,产生了什么」即可。
staging/src/kubevirt.io/api/core/v1/types.go—VirtualMachine(1942行)、VirtualMachineInstance(47行)、阶段 (phase) 列表 (1098行)。字段名称就是 YAML 名称。pkg/virt-controller/watch/vm/vm.go的execute()(357行) →sync()(3215行) →syncRunStrategy()(970行)。VM → VMI。pkg/virt-controller/watch/vmi/lifecycle.go的sync()(66行) 与services/template.go的renderLaunchManifest()(395行)。VMI → Pod。pkg/virt-handler/vm.go的execute()(321行) →sync()(1433行)。节点上的 Pod → 域。pkg/virt-launcher/virtwrap/manager.go的SyncVMI()(1380行) 与converter/converter.go的Convert_v1_VirtualMachineInstance_To_api_Domain()(967行)。域 → QEMU。
docs/components.md 也是一个不错的起点,但版本稍微有些旧。将 Pod 内运行的 libvirt 守护进程描述为 libvirtd ,而 v1.9.0 的 virt-launcher 启动的是单体式 libvirtd 而非 virtqemud (cmd/virt-launcher/virt-launcher.go:432)。
实测中,Pod 内的进程也是 virtqemud 和 virtlogd (第 5 篇)。
6. 局限性
- 未运行构建与测试。
make需要下载构建器镜像和 Bazel 依赖项,make cluster-up需要支持嵌套虚拟化的 Linux 机器。所需的是 Docker 或 Podman、rsync 以及几十 GB 的磁盘空间(docs/getting-started.md的 Building 节)。测试时间和构建时间是 未实测的。 - 代码行数仅用来衡量规模。计算了空行和注释,生成文件仅排除了名称中包含
generated的文件。 - 集群实测是一个集群、一个日期的状态。Deployment 数量 (2) 可能不是默认值,而是该集群的配置。
小结。 听说有 9 千个文件是不是觉得很可怕?但 VM 经过的路径只有五个程序。从日志中选出
pos一行,然后从该行开始倒过来读试试看。地图很快就会在脑海中浮现出来!
参考资料
均于 2026-10-17 查阅。
- KubeVirt v1.9.0 源码: https://github.com/kubevirt/kubevirt/tree/v1.9.0
- 开发指南(构建·测试·开发集群): https://github.com/kubevirt/kubevirt/blob/v1.9.0/docs/getting-started.md
- 组件文档: https://github.com/kubevirt/kubevirt/blob/v1.9.0/docs/components.md
- 姊妹系列「学生 VM 是如何启动的」第 2 篇: 深入 KubeVirt