- Published on
virt-controller deep dive: informer, queue, reconcile로 VM Pod를 만드는 법
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- virt-controller는 하나의 controller가 아니다
- 왜 informer가 এত 많을까
- watch/vmi/vmi.go가 보여주는 전형적인 패턴
- launcher Pod는 어떻게 만들어지는가
- controller가 network와 storage를 직접 아는 이유
- expectation 패턴이 중요한 이유
- 언제 virt-handler로 책임이 넘어가는가
- migration에서는 왜 Pod가 둘일 수 있는가
- application.go가 보여주는 전체 오케스트레이션 그림
- 자주 하는 오해
- 운영자가 봐야 할 증상과 위치
- 마무리
들어가며
KubeVirt에서 "cluster-wide brain"에 가장 가까운 컴포넌트는 virt-controller다. VM을 실제로 실행하는 것은 노드 쪽의 virt-handler와 virt-launcher지만, 어떤 Pod를 언제 만들고 어떤 상태를 다음 단계로 넘길지 조율하는 중심은 virt-controller다.
이번 글은 pkg/virt-controller/watch/application.go와 pkg/virt-controller/watch/vmi/vmi.go, pkg/virt-controller/watch/migration/migration.go를 중심으로 이 controller가 어떤 informer를 연결하고, 어떤 큐를 돌리며, 어떻게 launcher Pod를 생성하는지 본다.
virt-controller는 하나의 controller가 아니다
이름만 보면 단일 프로세스와 단일 reconcile loop처럼 느껴지지만, 실제로는 여러 watcher와 controller가 묶여 있다. application.go를 보면 다음 계열이 한곳에 배치된다.
- VMI controller
- VM controller
- migration controller
- replica set, pool, clone controller
- snapshot, export, backup 관련 controller
- node, workload update, disruption budget 관련 controller
즉 virt-controller는 KubeVirt의 cluster-wide orchestration 집합이다.
왜 informer가 এত 많을까
NewController 시그니처를 보면 VMI controller만 해도 여러 informer를 받는다.
- VMI informer
- VM informer
- Pod informer
- PVC informer
- migration informer
- storage class informer
- DataVolume, CDI informer
- KubeVirt CR informer
이렇게 많은 이유는 VMI의 desired state가 VMI spec 하나만으로 결정되지 않기 때문이다.
- 스토리지가 준비되었는가
- 이미 Pod가 있는가
- migration이 진행 중인가
- cluster config가 어떤 feature gate를 켰는가
- 네트워크 annotation을 어떻게 생성해야 하는가
controller는 이 모든 주변 상태를 종합해야 launcher Pod를 정확히 만들 수 있다.
watch/vmi/vmi.go가 보여주는 전형적인 패턴
VMI controller는 Kubernetes 표준 controller 패턴과 매우 닮아 있다.
1. 이벤트를 받는다
VMI, Pod, DataVolume, PVC, VM, KubeVirt 오브젝트에 event handler를 등록한다.
2. 키를 큐에 넣는다
실제 작업은 event handler에서 바로 하지 않고 queue로 넘긴다.
3. sync loop가 현재 상태를 읽는다
indexer와 store에서 VMI, Pod, PVC, migration 상태를 다시 읽어 desired state와 current state를 비교한다.
4. 필요한 Pod 또는 status를 만든다
launcher Pod 생성, annotation 업데이트, network status 업데이트, expectation 관리가 여기서 이뤄진다.
이 패턴의 장점은 race condition을 완화하고 idempotent한 reconcile을 가능하게 만든다는 점이다.
launcher Pod는 어떻게 만들어지는가
VMI controller는 직접 Pod YAML을 손으로 조립하지 않는다. templateService를 통해 RenderLaunchManifest 같은 메서드를 호출한다. 즉 controller는 "Pod가 필요하다"는 결정을 하고, template service가 실제 launcher Pod spec를 렌더링한다.
이 분리는 꽤 중요하다.
- controller는 상태 판단에 집중한다
- template service는 Pod spec 구성에 집중한다
덕분에 migration Pod, hotplug attachment Pod 같은 변형도 별도 rendering path로 처리할 수 있다.
controller가 network와 storage를 직접 아는 이유
많은 사람이 여기서 묻는다. "네트워크와 스토리지는 node에서 붙는 거 아닌가? 왜 controller가 annotation generator를 들고 있지?"
정답은 사전 배선 작업 때문이다.
watch/vmi/vmi.go를 보면 다음 의존성이 있다.
- network annotations generator
- storage annotations generator
- network status updater
- network spec validator
- migration evaluator
즉 controller는 단순히 Pod를 만들기만 하는 것이 아니라, Pod가 노드에 도착했을 때 KubeVirt와 CNI 쪽이 필요한 컨텍스트를 알 수 있도록 annotation과 상태를 준비한다.
expectation 패턴이 중요한 이유
Kubernetes controller를 읽다 보면 expectations 패턴이 자주 나온다. KubeVirt도 마찬가지다.
VMI controller에는 다음이 있다.
- pod expectations
- vmi expectations
- pvc expectations
이 패턴은 "내가 방금 Pod 생성 요청을 냈으니 informer 캐시가 아직 반영되지 않았더라도 너무 일찍 재시도하지 말자"는 문제를 해결한다. controller가 create call 직후 stale cache를 읽어서 중복 생성하는 일을 줄여준다.
대규모 클러스터에서 이 패턴은 매우 중요하다. VMI 하나가 일반 Pod보다 더 복잡한 주변 리소스를 갖기 때문에 중복 생성은 훨씬 더 큰 혼란을 만든다.
언제 virt-handler로 책임이 넘어가는가
docs/components.md는 Pod가 노드에 스케줄되어 nodeName이 정해진 뒤 책임이 virt-handler 쪽으로 넘어간다고 설명한다. 실제 mental model도 거의 이렇다.
virt-controller가 launcher Pod를 만든다.- Kubernetes가 Pod를 특정 node에 스케줄링한다.
- controller는 Pod와 VMI 상태를 반영한다.
- 해당 노드의
virt-handler가 그 VMI를 보고 VM 런치를 이어받는다.
즉 virt-controller는 cluster scheduler와 node agent 사이의 handoff coordinator다.
migration에서는 왜 Pod가 둘일 수 있는가
이 점이 일반 Pod controller와 KubeVirt controller를 구분 짓는다. VMI는 평상시 "VMI 하나에 launcher Pod 하나"처럼 보이지만, migration 중에는 target Pod가 추가로 생긴다.
그래서 migration controller 쪽에서는:
- target Pod 생성
- source와 target 상태 추적
- concurrent migration 제한
- unschedulable timeout 관리
같은 로직이 추가된다.
pkg/virt-controller/watch/migration/migration.go를 보면 pending timeout, priority queue, policy store, handoff map 같은 장치가 등장한다. 이건 migration이 단순 Pod 재스케줄이 아니라, 살아 있는 guest를 이동하는 복합 작업이기 때문이다.
application.go가 보여주는 전체 오케스트레이션 그림
VirtControllerApp 구조체를 보면 KubeVirt가 cluster-wide에서 무엇을 관리하는지 감이 온다.
- informer factory
- 각종 resource informer
- template service
- cluster config
- migration controller
- backup, export, snapshot controller
여기서 알 수 있는 중요한 사실은 virt-controller가 단순 VM Pod 생성기보다 훨씬 넓은 역할을 가진다는 점이다. VM lifecycle 전반의 중앙 orchestration 계층이다.
자주 하는 오해
오해 1: virt-controller가 VM을 직접 실행한다
아니다. virt-controller는 Pod를 만들고 상태를 조율한다. 실제 VM 프로세스 런치는 node 쪽에서 일어난다.
오해 2: controller는 VMI만 보면 충분하다
아니다. Pod, PVC, migration, cluster config, CDI 상태를 함께 봐야 launcher Pod를 정확히 만들 수 있다.
오해 3: migration은 그냥 Pod 하나 다시 스케줄하는 일이다
아니다. 살아 있는 guest state와 네트워크, 디스크 가시성을 유지하면서 target Pod를 준비해야 하므로 별도의 migration controller가 필요하다.
운영자가 봐야 할 증상과 위치
VMI는 있는데 launcher Pod가 안 생긴다
virt-controller로그- DataVolume 또는 PVC 준비 상태
- network spec validation 오류
migration이 pending에서 오래 멈춘다
- target Pod scheduling 가능 여부
- migration controller timeout
- cluster-wide parallel migration 제한
Pod는 있는데 상태가 어긋난다
- informer 캐시 반영 지연
- expectation 관련 재시도
- VMI status patch 충돌
마무리
virt-controller는 KubeVirt의 cluster-wide 조율자다. 이 컴포넌트는 다양한 informer를 통해 VMI 주변 세계를 읽고, queue 기반 reconcile로 launcher Pod를 만들며, 적절한 시점에 책임을 virt-handler로 넘긴다. migration이 복잡한 이유도 여기서 드러난다. VMI는 단순한 Pod가 아니라 controller가 여러 리소스를 엮어 만들어내는 가상화 워크로드이기 때문이다.
다음 글에서는 이 handoff를 받은 virt-handler가 노드에서 어떤 식으로 VM lifecycle을 이어받는지 살펴보겠다.