- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- virt-launcher Pod를 어떻게 이해해야 하나
- cmd/virt-launcher/virt-launcher.go가 하는 일
- 왜 command server가 필요한가
- DomainManager 인터페이스가 보여주는 책임 범위
- libvirt가 하는 일, QEMU가 하는 일
- readiness와 socket 전환이 의미하는 것
- guest agent와 이벤트 루프까지 launcher에 있는 이유
- 종료 시 왜 Pod가 바로 죽지 않을 수 있는가
- 자주 하는 오해
- 디버깅 힌트
- 마무리
들어가며
이제 KubeVirt의 가장 중요한 실행 지점을 보자. virt-controller는 Pod를 만들고, virt-handler는 노드에서 조율한다. 그렇다면 실제 VM은 어디서 뜰까? 답은 virt-launcher Pod 안이다.
정확히 말하면, virt-launcher Pod는 VM 하나를 위한 실행 셸이고, 그 안에서 libvirt와 QEMU가 guest를 실제로 구동한다. cmd/virt-launcher/virt-launcher.go와 pkg/virt-launcher/virtwrap/manager.go는 이 실행 계층의 중심이다.
virt-launcher Pod를 어떻게 이해해야 하나
많은 초보 설명은 "virt-launcher가 VM을 띄운다"라고 끝난다. 하지만 더 정확한 설명은 이렇다.
virt-launcherPod는 VM 실행에 필요한 namespace와 cgroup 경계를 제공한다.- 그 Pod 내부의
virt-launcher프로세스는 command server와 domain manager를 준비한다. - libvirt 연결이 생성된다.
- QEMU domain이 정의되고 실행된다.
- 이후 이벤트와 guest agent, stats가 계속 감시된다.
즉 virt-launcher는 단순 container entrypoint가 아니라 VM 실행 제어 plane의 local runtime이다.
cmd/virt-launcher/virt-launcher.go가 하는 일
이 엔트리 포인트 파일을 보면 초기화 순서가 꽤 명확하다.
1. libvirt event 구현 등록
프로세스 시작 시 libvirt.EventRegisterDefaultImpl()가 호출된다. 이는 이후 domain event monitoring을 위해 필요하다.
2. 각종 디렉터리 초기화
initializeDirs는 cloud-init, ignition, container disk, hotplug disk, secret, config map, downward metrics 등 guest 런타임에 필요한 여러 디렉터리를 준비한다.
여기서 이미 중요한 사실이 보인다. VM 부팅은 단순히 QEMU 바이너리 하나 띄우는 일이 아니라, 여러 디스크 소스와 메타데이터 채널을 정리하는 준비 작업과 함께 간다.
3. libvirt 연결 생성
createLibvirtConnection은 기본적으로 qemu:///system에 연결하고, 비 root 모드에서는 session URI를 사용한다. 즉 virt-launcher는 단순히 libvirt 라이브러리를 링크하는 것이 아니라, 실제 libvirt 제어 plane에 client로 붙어 domain을 정의한다.
4. command server 시작
startCmdServer는 unix socket 기반 command server를 띄운다. virt-handler는 이 소켓을 통해 launcher 측 domain manager를 호출한다.
5. domain event monitoring 시작
startDomainEventMonitoring은 libvirt 이벤트 루프를 돌리고 notifier를 시작한다. guest 상태 변화, 종료, agent 연동 상태가 여기서 반영된다.
왜 command server가 필요한가
이건 KubeVirt 구조를 이해하는 핵심 포인트다. virt-handler는 node agent이지만, QEMU는 launcher Pod 내부에 있다. 두 프로세스는 서로 다른 격리 경계에 있으므로 직접 함수 호출을 할 수 없다. 그래서 KubeVirt는 command server와 client 계층을 둔다.
흐름은 대략 이렇다.
virt-handler가 VMI를 reconcile한다.- launcher Pod 내부 socket에 요청을 보낸다.
- command server가
DomainManager메서드를 호출한다. LibvirtDomainManager가 libvirt API를 통해 domain을 define, start, pause, migrate한다.
이 구조 덕분에 cluster node 쪽 orchestration과 Pod 내부 하이퍼바이저 제어가 분리된다.
DomainManager 인터페이스가 보여주는 책임 범위
pkg/virt-launcher/virtwrap/manager.go의 DomainManager 인터페이스를 보면, launcher가 단순 boot만 담당하는 것이 아니라는 점이 선명하다.
대표 메서드는 다음과 같다.
SyncVMIPauseVMIUnpauseVMIKillVMIDeleteVMIMigrateVMIPrepareMigrationTargetFinalizeVirtualMachineMigrationHotplugHostDevicesGetDomainStatsGuestPing
즉 launcher는 guest lifecycle 전체를 담당하는 local hypervisor adapter다.
libvirt가 하는 일, QEMU가 하는 일
이 둘은 자주 한 덩어리로 말해지지만 역할이 다르다.
libvirt
- domain 정의와 관리 API 제공
- XML 기반 domain model 유지
- migration API 제공
- stats와 event 수집 인터페이스 제공
QEMU
- 실제 guest CPU, 메모리, 디스크, NIC 에뮬레이션
- KVM과 결합한 실행 엔진
- virtio 장치 구현
KubeVirt는 대체로 libvirt를 주 제어 인터페이스로 사용하고, libvirt가 내부적으로 QEMU를 관리하도록 한다.
readiness와 socket 전환이 의미하는 것
markReady는 uninitialized socket 이름을 실제 socket 이름으로 바꾼다. 이 작은 동작이 중요한 이유는, virt-handler가 launcher가 아직 초기화 중인지 아니면 실제 명령을 받을 준비가 되었는지 구분할 수 있게 해주기 때문이다.
즉 launcher는 Pod가 Running이라고 곧바로 "VM 제어 준비 완료"라고 가정하지 않는다. command plane readiness가 별도로 있다.
guest agent와 이벤트 루프까지 launcher에 있는 이유
guest 내부 정보를 다루는 책임도 launcher에 많다. agent poller, guest info, filesystem, user, time sync 같은 기능이 manager.go 인터페이스와 virt-launcher.go 초기화 경로에 보인다.
이건 자연스럽다. guest agent는 결국 libvirt 또는 QEMU가 노출하는 guest 통신 채널과 가장 가깝기 때문이다. node agent인 virt-handler는 이를 직접 다루기보다 launcher에게 묻는 편이 맞다.
종료 시 왜 Pod가 바로 죽지 않을 수 있는가
docs/components.md도 언급하지만, Kubernetes가 virt-launcher Pod 종료를 시도할 때 guest가 아직 종료되지 않았다면 launcher는 signal을 guest 쪽으로 전달하고, 가능한 한 정상 종료를 기다린다.
이 동작은 매우 중요하다. 그렇지 않으면 Pod 종료가 guest 강제 종료와 거의 동일해져서 데이터 손상 위험이 커진다. KubeVirt는 Pod lifecycle과 VM lifecycle이 완전히 같은 것이 아니라는 점을 이 지점에서 드러낸다.
자주 하는 오해
오해 1: virt-launcher는 그냥 sidecar 없는 일반 컨테이너다
아니다. 이 Pod는 VM 실행 경계이며, 내부에 libvirt event loop, command server, guest agent 관련 로직이 있다.
오해 2: QEMU는 virt-handler가 직접 띄운다
아니다. virt-handler는 launcher의 command server를 호출하고, 실제 domain define과 실행은 launcher 내부 domain manager가 libvirt를 통해 수행한다.
오해 3: Pod Running이면 guest도 이미 정상이다
그렇지 않다. launcher 준비, libvirt 연결, domain define, agent 초기화는 별도 단계다.
디버깅 힌트
- launcher Pod는 살아 있는데 guest가 안 뜨면 command server readiness와 libvirt 연결을 본다.
- guest agent 관련 정보가 비면 launcher 쪽 poller와 event notifier를 본다.
- 종료가 오래 걸리면 guest shutdown 전달과 grace period를 함께 본다.
- migration 이상이면
PrepareMigrationTarget,MigrateVMI,FinalizeVirtualMachineMigration경로를 분리해서 본다.
마무리
virt-launcher는 KubeVirt에서 가장 실행에 가까운 레이어다. 이 Pod는 VM을 위한 격리 경계를 제공하고, 그 내부의 launcher 프로세스는 libvirt 연결, command server, event monitor, guest agent poller를 세팅한다. 그 위에서 LibvirtDomainManager가 QEMU domain lifecycle을 제어한다. 결국 "VM이 Pod 위에서 돈다"는 말의 가장 구체적인 실체는 바로 이 launcher Pod 내부 실행 구조다.
다음 글에서는 launcher가 받은 VMI spec가 어떤 변환 과정을 거쳐 libvirt domain XML과 실제 guest 장치 구성으로 바뀌는지 보겠다.