상태: 초안 (2026-10-17), 소스 + 읽기 전용 실측. 기준은 KubeVirt
v1.9.0(커밋79d34c17)입니다. 「실측」 표시는 v1.9.0 이 깔린 작은 클러스터에서 읽기로만 확인한 값입니다. 빌드와 시험은 돌리지 않았습니다(아래 「한계」).
KubeVirt 소스 코드 읽기 1: 저장소 지도
목차
1. 직관: 쿠버네티스에 붙는 다섯 프로그램
KubeVirt 는 쿠버네티스를 고치지 않습니다. CRD(VirtualMachine, VirtualMachineInstance 등)를 더하고, 그 CRD 를 보는 프로그램 몇 개를 클러스터에 띄울 뿐입니다. 저장소를 읽을 때도 이 다섯 프로그램을 기준으로 나누면 길을 잃지 않습니다.
| 프로그램 | 도는 자리 | 한 줄 역할 |
|---|---|---|
| virt-operator | 컨트롤 플레인(Deployment) | 나머지를 설치하고 판을 올린다 |
| virt-api | 컨트롤 플레인(Deployment) | 웹훅으로 요청을 검사하고, console·start 같은 서브리소스를 내준다 |
| virt-controller | 컨트롤 플레인(Deployment, 리더 하나) | VM 을 보고 VMI 를, VMI 를 보고 파드를 만든다 |
| virt-handler | 노드마다(DaemonSet) | 자기 노드에 온 VMI 를 실제 도메인과 맞춘다 |
| virt-launcher | VM 마다 하나(파드) | 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)입니다. 플래그를 읽고,
인포머를 만들고, 리더 선출에서 이기면 onStartedLeading()
(:609)에서 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(시리얼 콘솔 로그를 흘려보내는 사이드카), 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·웹훅·데몬셋을 코드로 만든다) |
2 |
pkg/ 의 나머지 — 여럿이 함께 쓰는 부품
구성요소 폴더 밖의 패키지는 「누가 부르나」 로 보면 쉽습니다. pkg/network 는 virt-handler(파드 네트워크 준비)와
virt-launcher(도메인의 <interface>)가 함께 씁니다. pkg/controller 는 인포머 공장과 기대값(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 모듈입니다.
쿠버네티스와 같은 방식으로, 루트 go.mod 가 이 둘을 replace 로 저장소 안의 staging/ 폴더에 묶습니다. 그래서 VM 의 필드를
고치는 PR 은 staging/src/kubevirt.io/api/core/v1/types.go 를 고치고, 생성 코드를 다시 만들어 함께 올립니다(2편).
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 으로 띄우므로 중첩 가상화가 되는 리눅스 기계가
필요합니다. 맥에서는 빌드는 되어도 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 → 파드.pkg/virt-handler/vm.go의execute()(321행) →sync()(1433행). 노드에서 파드 → 도메인.pkg/virt-launcher/virtwrap/manager.go의SyncVMI()(1380행)와converter/converter.go의Convert_v1_VirtualMachineInstance_To_api_Domain()(967행). 도메인 → QEMU.
docs/components.md 도 좋은 출발점이지만 판이 조금 낡았습니다. 파드 안에서 도는 libvirt 데몬을 libvirtd 라고 적었는데,
v1.9.0 의 virt-launcher 는 모놀리식 libvirtd 대신 virtqemud 를 띄웁니다
(cmd/virt-launcher/virt-launcher.go:432).
실측에서도 파드 안 프로세스는 virtqemud 와 virtlogd 였습니다(5편).
6. 한계
- 빌드와 시험을 돌리지 않았습니다.
make는 빌더 이미지와 Bazel 의존성을 내려받아야 하고,make cluster-up은 중첩 가상화가 되는 리눅스 기계가 필요합니다. 필요한 것은 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 안쪽