- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- 먼저 가져야 할 질문
- 1단계: API 타입부터 읽는다
- 2단계: virt-api에서 사용자 의도가 어떻게 표준화되는지 본다
- 3단계: virt-controller/watch에서 Pod 생성과 handoff를 읽는다
- 4단계: virt-handler를 읽으면 "node에서 실제로 누가 일하는가"가 보인다
- 5단계: cmd/virt-launcher와 virtwrap에서 실제 VM 실행 경로를 읽는다
- 6단계: 네트워크는 pkg/network를 묶어서 읽는다
- 7단계: migration은 controller와 launcher를 왕복하며 읽어야 한다
- 8단계: host 통합 코드는 kernel primitive 관점으로 읽는다
- 내가 추천하는 실제 읽기 순서
- 읽을 때 특히 주의할 점
- 시리즈 전체를 한 문장으로 요약하면
- 마무리
들어가며
KubeVirt는 코드 양도 많고 계층도 깊다. 처음 repo를 열면 virt-api, virt-controller, virt-handler, virt-launcher, pkg/network, staging이 한꺼번에 보여서 어디부터 읽어야 할지 막막해진다.
하지만 실제로는 읽는 순서만 잘 잡으면 구조가 꽤 선명해진다. 이 시리즈 마지막 글에서는 "KubeVirt를 이해하기 위해 어떤 질문을 어떤 패키지에서 해결해야 하는가"를 기준으로 소스 읽기 지도를 정리한다.
먼저 가져야 할 질문
KubeVirt 코드를 읽을 때는 파일 트리보다 질문 순서가 중요하다. 나는 아래 질문 순서를 추천한다.
- VM은 어떤 Kubernetes 객체로 표현되는가
- 그 객체를 누가 감시하고 Pod를 만드는가
- node에 도착한 뒤 누가 libvirt와 QEMU를 실행하는가
- Pod 네트워크가 guest 네트워크로 어떻게 바뀌는가
- migration은 control plane과 data plane에서 각각 어떻게 진행되는가
- 실제로 어떤 kernel primitive가 쓰이는가
이 질문 순서대로 읽으면, KubeVirt를 "엄청 큰 Go repo"가 아니라 "VM 실행 파이프라인"으로 볼 수 있다.
1단계: API 타입부터 읽는다
가장 먼저 읽을 곳은 staging/src/kubevirt.io/api/core/v1이다.
특히 다음 파일이 핵심이다.
types.goschema.go
여기서 이해해야 할 것은 세 가지다.
VirtualMachineVirtualMachineInstanceVirtualMachineInstanceMigration
이 레이어를 먼저 읽으면 이후 controller 코드에서 무엇을 reconcile하는지 보이기 시작한다. 특히 status, conditions, migrationState, interface 타입을 먼저 읽어 두면 뒤에서 나오는 거의 모든 로직이 쉬워진다.
즉 API 타입은 문서가 아니라, 나머지 코드를 해석하는 사전이다.
2단계: virt-api에서 사용자 의도가 어떻게 표준화되는지 본다
다음은 virt-api와 admission 계층이다. 여기서 봐야 할 질문은 이것이다.
"사용자가 넣은 spec가 어떤 검증과 defaulting을 거쳐 controller가 처리 가능한 객체가 되는가"
이 단계를 읽으면:
- 어떤 필드 조합이 허용되는지
- 어떤 subresource가 있는지
- VMI가 바로 실행되는지, VM을 통해 관리되는지
가 잡힌다.
즉 virt-api는 실행 엔진이 아니라, 사용자 선언을 안전한 control-plane 입력으로 바꾸는 문이다.
3단계: virt-controller/watch에서 Pod 생성과 handoff를 읽는다
그다음은 pkg/virt-controller/watch다. 이 단계에서 질문은 바뀐다.
"이 선언을 누가 launcher Pod와 migration Pod로 바꾸는가"
특히 다음 흐름이 중요하다.
- VMI controller
- migration controller
- template service
여기서 이해해야 할 포인트는 다음이다.
- informer와 work queue
- owner reference와 expectation
- launcher Pod manifest 렌더링
- migration target Pod 생성
즉 이 구간은 KubeVirt가 Kubernetes controller 패턴을 가장 정석적으로 사용하는 층이다.
4단계: virt-handler를 읽으면 "node에서 실제로 누가 일하는가"가 보인다
많은 사람이 여기서 비로소 KubeVirt의 실체를 느낀다. pkg/virt-handler는 node-local agent이고, VM 운영의 상당 부분이 여기서 이뤄진다.
읽기 시작할 때는 다음 파일이 좋다.
vm.gomigration-target.gomigration-proxy/migration-proxy.goseccomp/seccomp.go
이 레이어에서 답하는 질문은 이런 것들이다.
- node에 있는 VMI와 domain을 어떻게 sync하는가
- launcher와 어떤 RPC로 통신하는가
- migration source와 target를 어떻게 준비하는가
- cgroup, device, network, seccomp를 어떻게 만지는가
즉 virt-handler는 KubeVirt의 "현장 반장"에 가깝다.
5단계: cmd/virt-launcher와 virtwrap에서 실제 VM 실행 경로를 읽는다
VM이 실제로 어떻게 뜨는지 보려면 결국 cmd/virt-launcher/virt-launcher.go와 pkg/virt-launcher/virtwrap로 내려가야 한다.
여기서 중요한 질문은 이것이다.
"VMI spec가 libvirt domain과 QEMU 실행 상태로 어떻게 변환되는가"
핵심 포인트는 다음이다.
- libvirt 연결 생성
- command server 시작
- domain manager 구현
- guest agent polling
- event 수집
- domain stats 수집
그리고 반드시 함께 봐야 할 패키지가 converter다. pkg/virt-launcher/virtwrap/converter/converter.go는 VMI spec를 domain XML 관점으로 번역하는 핵심 레이어다.
즉 virtwrap은 KubeVirt에서 가장 하이퍼바이저에 가까운 코드다.
6단계: 네트워크는 pkg/network를 묶어서 읽는다
KubeVirt 네트워크는 파일 하나로 이해되지 않는다. 다음 패키지를 묶어서 읽는 편이 좋다.
setupmultuscontrollersdhcpmigration
질문은 단순하다.
"Pod가 이미 받은 네트워크를 guest가 어떻게 쓰게 되는가"
이때 봐야 할 것은:
- Pod NIC 읽기
- TAP와 bridge 준비
- masquerade 주소 계산
- DHCP 응답
- Multus annotation 생성
- interface status 반영
즉 KubeVirt 네트워크는 CNI를 대체하는 게 아니라, Pod network를 guest-visible network로 재가공하는 코드다.
7단계: migration은 controller와 launcher를 왕복하며 읽어야 한다
live migration은 한 파일에서 끝나지 않는다. 꼭 왕복해서 읽어야 한다.
- control plane:
pkg/virt-controller/watch/migration/migration.go - node plane:
pkg/virt-handler/migration-target.go - transport 보조:
pkg/virt-handler/migration-proxy/migration-proxy.go - hypervisor plane:
pkg/virt-launcher/virtwrap/live-migration-source.go
읽을 때는 다음 질문을 붙이면 좋다.
- target Pod는 누가 만들었는가
- sync address와 port는 어디서 채워지는가
- pre-copy에서 post-copy로 누가 전환하는가
- abort와 timeout은 누가 판단하는가
즉 migration은 controller, node agent, launcher를 세로로 관통해서 읽어야 이해된다.
8단계: host 통합 코드는 kernel primitive 관점으로 읽는다
마지막에는 host 통합 코드를 다시 모아 읽는 것이 좋다.
cmd/virt-chrootpkg/virt-handler/cgrouppkg/virt-handler/selinuxpkg/network/driver/virtchroot
이때는 Kubernetes 추상화보다 Linux primitive를 기준으로 읽어야 한다.
- chroot
- namespace 진입
- cgroup v1, v2
- SELinux label 전환
- TAP 생성
- device 접근 허용
이 단계까지 보면 "왜 VM이 Pod 위에서 구현될 수 있었는가"라는 질문의 기술적 답이 거의 완성된다.
내가 추천하는 실제 읽기 순서
처음부터 끝까지 한 번 읽는다면 이 순서를 추천한다.
staging/src/kubevirt.io/api/core/v1/types.gostaging/src/kubevirt.io/api/core/v1/schema.gopkg/virt-controller/watch/vmi/vmi.gopkg/virt-controller/watch/migration/migration.gopkg/virt-handler/vm.gopkg/virt-handler/migration-target.gocmd/virt-launcher/virt-launcher.gopkg/virt-launcher/virtwrap/manager.gopkg/virt-launcher/virtwrap/converter/converter.gopkg/network/setup/network.gopkg/network/setup/podnic.gopkg/network/controllers/vmi.gopkg/network/multus/annotation.gopkg/virt-launcher/virtwrap/live-migration-source.gopkg/virt-handler/migration-proxy/migration-proxy.gopkg/virt-handler/seccomp/seccomp.go
이 순서는 control plane에서 시작해서 node, launcher, network, migration, kernel integration으로 내려가는 흐름이다.
읽을 때 특히 주의할 점
1. 패키지 단위보다 handoff 지점을 보라
KubeVirt는 handoff가 많은 시스템이다.
- API에서 controller로
- controller에서 launcher Pod로
- controller에서 virt-handler로
- virt-handler에서 launcher RPC로
- launcher에서 libvirt와 QEMU로
따라서 패키지 경계보다 "누가 다음 단계로 넘기는가"를 추적하는 편이 이해가 빠르다.
2. status 필드 정의를 계속 다시 보라
코드를 읽다 길을 잃으면 types.go의 status 구조체로 다시 돌아오는 것이 좋다. 실제로 많은 controller 로직은 status를 채우거나 해석하는 코드다.
3. migration은 반드시 source와 target를 동시에 생각하라
단일 VM lifecycle에 익숙한 사람은 migration 코드를 읽다가 자주 헷갈린다. source Pod, target Pod, source node, target node, source state, target state가 동시에 존재하기 때문이다.
시리즈 전체를 한 문장으로 요약하면
KubeVirt는 Kubernetes 위에 또 하나의 하이퍼바이저를 올린 것이 아니라, Kubernetes의 선언형 control plane과 Linux의 가상화 primitive, 그리고 libvirt와 QEMU를 정교하게 이어 붙인 orchestrator다.
마무리
이 시리즈에서는 API 타입에서 시작해 controller, node agent, launcher, networking, migration, kernel, 보안, 관측성, 실패 모드까지 KubeVirt를 한 줄기로 따라가 봤다. 처음에는 복잡해 보여도, 실제로는 선언을 Pod로 바꾸고, Pod 안에서 QEMU를 띄우고, Linux primitive로 네트워크와 장치를 붙이며, migration과 상태 보고를 controller 패턴으로 감싸는 구조다.
결국 "VM이 어떻게 Pod 위에서 가능했는가"라는 질문의 답은 하나의 마법이 아니라, 여러 계층의 handoff를 정확히 이해하는 데 있다. KubeVirt 소스코드는 그 handoff를 가장 솔직하게 보여 주는 지도다.