Skip to content

필사 모드: virt-launcher, libvirt, QEMU: 실제 VM 프로세스는 어디서 어떻게 뜨는가

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

이제 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-launcher` Pod는 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 계층을 둔다.

흐름은 대략 이렇다.

1. `virt-handler`가 VMI를 reconcile한다.

2. launcher Pod 내부 socket에 요청을 보낸다.

3. command server가 `DomainManager` 메서드를 호출한다.

4. `LibvirtDomainManager`가 libvirt API를 통해 domain을 define, start, pause, migrate한다.

이 구조 덕분에 cluster node 쪽 orchestration과 Pod 내부 하이퍼바이저 제어가 분리된다.

`DomainManager` 인터페이스가 보여주는 책임 범위

`pkg/virt-launcher/virtwrap/manager.go`의 `DomainManager` 인터페이스를 보면, launcher가 단순 boot만 담당하는 것이 아니라는 점이 선명하다.

대표 메서드는 다음과 같다.

- `SyncVMI`

- `PauseVMI`

- `UnpauseVMI`

- `KillVMI`

- `DeleteVMI`

- `MigrateVMI`

- `PrepareMigrationTarget`

- `FinalizeVirtualMachineMigration`

- `HotplugHostDevices`

- `GetDomainStats`

- `GuestPing`

즉 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 장치 구성으로 바뀌는지 보겠다.

현재 단락 (1/61)

이제 KubeVirt의 가장 중요한 실행 지점을 보자. `virt-controller`는 Pod를 만들고, `virt-handler`는 노드에서 조율한다. 그렇다면 실제 VM은 어...

작성 글자: 0원문 글자: 4,084작성 단락: 0/61