TT Lab
요금제

한국어

시작하기
블로그

블로그

KubeVirt 소스 코드 읽기 1: 저장소 지도 — 어디부터 읽을까

상태: 초안 (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. 그림: 폴더와 실행 자리

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)입니다. 플래그를 읽고, 인포머를 만들고, 리더 선출에서 이기면 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 하나가 뜨는 길을 따라 읽는 순서를 권합니다. 각 단계에서 「입력은 무엇이고 무엇을 만들어 내는가」 만 잡으면 됩니다.

  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 → 파드.
  4. pkg/virt-handler/vm.go 의 execute()(321행) → sync()(1433행). 노드에서 파드 → 도메인.
  5. 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. 한계

티티 한마디. 9천 개 파일이라니 겁나죠? 그런데 VM 하나가 지나가는 길은 다섯 프로그램뿐이에요. 로그에서 pos 한 줄을 골라 그 줄부터 거꾸로 읽어 보세요. 지도가 금방 머릿속에 그려져요!

참고

모두 2026-10-17 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다