TT Lab
博客

한국어 日本語 中文

开始
博客

博客

阅读 KubeVirt 源代码 1:仓库地图 —— 从哪里开始读

状态:草稿 (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. 图解:文件夹与运行位置

kubevirt/kubevirt 저장소 지도 (v1.9.0) 왼쪽은 폴더, 오른쪽은 그 코드가 실제로 도는 자리 cmd/ 바이너리 입구 — main 만 얇게 pkg/virt-api 웹훅 · 서브리소스 API pkg/virt-controller VM·VMI 컨트롤러 · 파드 템플릿 pkg/virt-operator 설치 · 업그레이드 pkg/virt-handler 노드 데몬 · 장치 · 네트워크 pkg/virt-launcher 도메인 XML · libvirt 호출 pkg/network · storage … 여러 구성요소가 함께 쓰는 부품 staging/…/api 타입 정의 · client-go 와 짝 tests/ 기능 시험(ginkgo, 진짜 클러스터) hack/ · Makefile 빌드 · 코드 생성 · 개발 클러스터 컨트롤 플레인 virt-api ×2 Deployment virt-controller ×2 리더 1 + 대기 1 virt-operator ×2 Deployment 노드마다 virt-handler DaemonSet VM 마다 virt-launcher 파드 virt-launcher-monitor → virt-launcher → QEMU 실측(v1.9.0 소스, 시험·생성 파일 제외한 Go 줄 수) virt-launcher 26.9k virt-handler 24.5k virt-controller 23.3k virt-operator 21.3k virt-api 13.5k api 타입 11.8k
图解:kubevirt/kubevirt 的文件夹及其代码的运行位置

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 的路径来阅读。在每个阶段,只需抓住「输入是什么,产生了什么」即可。

  1. staging/src/kubevirt.io/api/core/v1/types.go — VirtualMachine(1942行)、 VirtualMachineInstance(47行)、阶段 (phase) 列表 (1098行)。字段名称就是 YAML 名称。
  2. pkg/virt-controller/watch/vm/vm.go 的 execute()(357行) → sync()(3215行) → syncRunStrategy()(970行)。VM → VMI。
  3. pkg/virt-controller/watch/vmi/lifecycle.go 的 sync()(66行) 与 services/template.go 的 renderLaunchManifest()(395行)。VMI → Pod。
  4. pkg/virt-handler/vm.go 的 execute()(321行) → sync()(1433行)。节点上的 Pod → 域。
  5. 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. 局限性

小结。 听说有 9 千个文件是不是觉得很可怕?但 VM 经过的路径只有五个程序。从日志中选出 pos 一行,然后从该行开始倒过来读试试看。地图很快就会在脑海中浮现出来!

参考资料

均于 2026-10-17 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论