Status: Draft (2026-10-17), Source + read-only actual measurement. The baseline is KubeVirt
v1.9.0(commit79d34c17). The "Measured" label indicates values checked solely by reading in a small cluster with v1.9.0 installed. Builds and tests were not run (see "Limitations" below).
Reading KubeVirt Source Code 1: Repository Map
Table of Contents
- 1. Intuition: Five Programs Attached to Kubernetes
- 2. Diagram: Folders and Execution Locations
- 3. Folder by Folder
- 4. Measurement: Five Programs Observed in the Cluster
- 5. Where to Start Reading
- 6. Limitations
- References
1. Intuition: Five Programs Attached to Kubernetes
KubeVirt does not modify Kubernetes. It simply adds CRDs (VirtualMachine, VirtualMachineInstance, etc.) and launches a few programs in the cluster that watch those CRDs. Even when reading the repository, you will not lose your way if you divide things based on these five programs.
| Program | Execution Location | One-Line Role |
|---|---|---|
| virt-operator | Control Plane (Deployment) | Installs the rest and sets up the platform |
| virt-api | Control Plane (Deployment) | Inspects requests via webhooks and exposes subresources like console and start |
| virt-controller | Control Plane (Deployment, single leader) | Watches VMs to create VMIs, and VMIs to create pods |
| virt-handler | Per node (DaemonSet) | Aligns the VMI assigned to its node with the actual domain |
| virt-launcher | One per VM (Pod) | Converts VMI into a libvirt domain XML and launches QEMU |
If Part 2 of "Until a Student VM Starts" (../kubevirt-lab-vm/02-kubevirt-inside.md) explained the roles of these five from a user's perspective, this post looks at
which folder in the repository contains each role.
2. Diagram: Folders and Execution Locations
3. Folder by Folder
cmd/ — A Thin Entry Point
cmd/ Each folder below has one main , and most are just a few lines long. virt-controller consists entirely of this (cmd/virt-controller/virt-controller.go:26).
func main() {
watch.Execute()
}
, while the core body is in pkg/virt-controller/watch/application.go of Execute()
(:303). It reads flags, creates informers, and once it wins the leader election, it runs controllers for VMs, VMIs, migrations, etc., as goroutines in onStartedLeading()
(:609). The exceptions are the two node-side programs. cmd/virt-handler/virt-handler.go 's Run()
(:209and cmd/virt-launcher/virt-launcher.go 's
main()(:347have long setup code, so there is plenty to read even within
cmd/ . We will follow these two in Parts 4 and 5.
cmd/ Also contains many small helpers besides the five: virt-launcher-monitor(PID 1 that wraps the launcher and watches it to the end), virt-chroot
(when handler runs commands in a different namespace), virt-tail(a sidecar streaming serial console logs), virtctl(user CLI), and more.
pkg/virt-* — The Core of the Five Components
| Folder | File to Read First | Covered Chapter |
|---|---|---|
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/(Generates CRDs, webhooks, and DaemonSets as code) |
2 |
pkg/ The Rest of pkg/ — Shared Parts Used Together
Packages outside the component folders are easy to understand by asking "Who calls them?". pkg/network is used together by virt-handler (preparing pod networks) and virt-launcher (domain's <interface>). pkg/controller contains controller common tools like informer factories and expectations, pkg/virt-config contains KubeVirt CR settings and feature gates, and pkg/hypervisor handles differences between hypervisors (KVM and the newly added MSHV). Part 3's memory overhead calculation is located in this pkg/hypervisor/kvm . pkg/libvmi is a builder for creating VMIs and is heavily used by test code.
staging/src/kubevirt.io/ — Two Separately Distributed Modules
API types (kubevirt.io/api) and clients (kubevirt.io/client-go) are separate Go modules because other projects also use them. In the same manner as Kubernetes, the root go.mod ties these two via replace into the staging/ folder inside the repository. Therefore, a PR that modifies VM fields modifies staging/src/kubevirt.io/api/core/v1/types.go , regenerates the code, and updates them together (Part 2).
tests/ — Functional Tests Running on a Real Cluster
Unit tests are *_test.go next to the code (Measured: pkg/ 717 below), written in Ginkgo and Gomega, and run with a fake client. tests/ consists of
functional tests . They install KubeVirt in a real cluster and launch VMs to verify them. Each test has labels like decorators.SigCompute
attached so you can select and run them by SIG.
hack/ and Makefile — Building Inside Containers
Makefile Almost all targets in hack/dockerized go through KUBEVIRT_NO_BAZEL=true . Since builds occur inside a builder container using Bazel (or go if
| ), your local machine only needs Docker (or Podman) and rsync. | Command | Location |
|---|---|---|
make |
Formatting + Bazel build + Manifests | Makefile:17 |
make generate |
Re-generating all generated code (Part 2) | Makefile:53 |
make test / make go-test |
Unit tests (Bazel / go) | :93 · :90 |
make cluster-up · cluster-sync |
Spinning up a dev cluster inside a VM using kubevirtci and deploying my build | :173 |
make functest |
Functional testing in that cluster | :101 |
To run only a subset of functional tests, pass Ginkgo filters like FUNC_TEST_ARGS='--focus-file=vmi_networking' make functest (Testing section of ). A development cluster spins up nodes as VMs, so you needdocs/getting-started.md a Linux machine that supports nested virtualization nested virtualization. On a Mac, even though you can build, cluster-up is difficult.
4. Measurement: Five programs observed in the cluster
Inspected the kubevirt namespace of a small cluster running v1.9.0 (2026-10-17).
| Component | Count | Image |
|---|---|---|
| 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 (per worker node) | quay.io/kubevirt/virt-handler:v1.9.0 |
| virt-launcher | Equal to the number of running VMs (3) | of VMI status launcherContainerImageVersion: …virt-launcher:v1.9.0 |
In the same namespace, virt-template-apiserver·virt-template-controller(v0.2.2) is also visible. This is a separate project that handles VM templates, and it is brought into the repository as manifests only via hack/virt-template/sync.sh . It is not covered in this series.
Size by folder. Go lines of code excluding tests and generated files (v1.9.0, find … ! -name '*_test.go' ! -name '*generated*').
| Folder | Files | Lines | Test files | Test lines |
|---|---|---|---|---|
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 is large with 23,000 lines across 42 files. watch/vm/vm.go alone has 3,640 lines. Conversely, virt-launcher has many small files. It is also worth noting that there are four folders where test lines outnumber main body lines. This is why reviewers frequently say "Show me the tests first" (Part 8).
How to jump from logs to source code. KubeVirt component logs are in JSON, and the "pos" field contains 파일:줄 . Cross-referencing the measured logs'
pos with the v1.9.0 source code showed that they all match. For example, the "pos":"vm.go:1459" of virt-handler corresponds to the log line at
pkg/virt-handler/vm.go:1459 , and the
VMI is in phase: … | Domain status: … of virt-launcher is "pos":"manager.go:1651" . When reading the code for the first time, Domain defined. picking a single log line and tracing backward picking a single log line and tracing backward from its line number is the fastest approach. However, line numbers vary across versions, so make sure to open the tag matching your cluster's version.
5. Where to start reading
We recommend reading in the order of how a single VM starts up. At each step, you only need to grasp "what the input is and what it produces."
staging/src/kubevirt.io/api/core/v1/types.go—VirtualMachine(1942 lines),VirtualMachineInstance(47 lines), and the list of phases (1098 lines). Field names correspond directly to YAML names.pkg/virt-controller/watch/vm/vm.goofexecute()(357 lines) →sync()(3215 lines) →syncRunStrategy()(970 lines). VM → VMI.pkg/virt-controller/watch/vmi/lifecycle.go'ssync()(66 lines) andservices/template.go'srenderLaunchManifest()(395 lines). VMI → Pod.pkg/virt-handler/vm.go'sexecute()(321 lines) →sync()(1433 lines). Pod on node → Domain.pkg/virt-launcher/virtwrap/manager.go'sSyncVMI()(1380 lines) andconverter/converter.go'sConvert_v1_VirtualMachineInstance_To_api_Domain()(967 lines). Domain → QEMU.
docs/components.md is also a good starting point, but its version is a bit outdated. It described the libvirt daemon running inside the pod as libvirtd , but virt-launcher in v1.9.0 launches libvirtd instead of the monolithic virtqemud (cmd/virt-launcher/virt-launcher.go:432). In our actual measurements, the processes inside the pod were virtqemud and virtlogd (Part 5).
6. Limitations
- Builds and tests were not executed.
makerequires downloading builder images and Bazel dependencies, andmake cluster-uprequires a Linux machine with nested virtualization. You will need Docker or Podman, rsync, and dozens of gigabytes of disk space (Building section of ). Test and build times aredocs/getting-started.mdunmeasured unmeasured. - Line counts are merely rough indicators of size. Empty lines and comments were counted, and generated files were excluded only if their names contained
generated. - Cluster measurements represent the state of a single cluster on a single date. The deployment count (2) might be a configuration specific to that cluster rather than the default.
Titi's note. Intimidated by 9,000 files? Don't be. The path a single VM takes involves only five programs. Pick a single log line from
posand read backward from it. The mental map will form in no time!
References
All accessed on 2026-10-17.
- KubeVirt v1.9.0 source: https://github.com/kubevirt/kubevirt/tree/v1.9.0
- Developer guide (Build, test, dev cluster): https://github.com/kubevirt/kubevirt/blob/v1.9.0/docs/getting-started.md
- Component documentation: https://github.com/kubevirt/kubevirt/blob/v1.9.0/docs/components.md
- Sister series "Until a Student VM Starts" Part 2: Inside KubeVirt