- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- 왜 이런 구조를 택했을까
- 가장 먼저 일어나는 일: Pod가 네트워크를 받는다
- pkg/network/setup이 하는 일
- TAP은 왜 필요한가
- DHCP는 왜 KubeVirt가 직접 띄우는가
- guest IP는 누가 할당하는가
- masquerade binding에서는 NAT가 어떻게 구현되는가
- link와 interface status는 왜 중요할까
- 이 구조가 운영상 주는 장점
- 자주 하는 오해
- 디버깅 체크리스트
- 마무리
들어가며
사용자 입장에서 가장 직관적인 질문은 이것이다. "VM은 IP를 어디서 받는가? CNI가 직접 VM에 IP를 주는가?" KubeVirt 네트워크를 이해하려면 먼저 답을 짧게 말할 수 있어야 한다.
기본적으로 CNI는 먼저 virt-launcher Pod에 네트워크를 준다. KubeVirt는 그 Pod 네트워크 namespace 안에서 TAP, bridge, DHCP, NAT를 구성해 guest NIC를 연결한다.
즉 guest는 보통 CNI를 직접 호출하지 않는다. CNI가 준비한 Pod 네트워크를 KubeVirt가 재배선한다.
왜 이런 구조를 택했을까
KubeVirt 설계 철학은 네트워크를 새로 만들기보다 Kubernetes와 CNI를 최대한 재사용하는 것이다. docs/components.md와 docs/network/libvirt-pod-networking.md는 이를 꽤 분명하게 설명한다.
이 구조를 쓰면:
- Pod 스케줄링과 네트워크 attach는 그대로 Kubernetes가 맡고
- KubeVirt는 guest 관점의 NIC wiring만 담당하면 된다
- Services, NetworkPolicy, Pod 네트워크 생태계와의 정합성이 좋아진다
즉 KubeVirt 네트워크의 핵심은 "VM 전용 네트워크 시스템"이 아니라 Pod 네트워크의 guest-side adaptation이다.
가장 먼저 일어나는 일: Pod가 네트워크를 받는다
virt-launcher Pod가 생성되면 먼저 CNI가 Pod 네트워크를 붙인다. 기본 primary interface는 보통 eth0로 보인다. 이 시점까지는 일반 Pod와 크게 다르지 않다.
중요한 것은 여기서 KubeVirt가 아직 guest NIC를 만들지 않았다는 점이다. 우선 Pod가 네트워크를 갖고, 그 다음 KubeVirt가 이 네트워크 namespace 내부에서 guest용 구성을 더한다.
pkg/network/setup이 하는 일
핵심 코드는 pkg/network/setup/network.go, podnic.go, netpod/*, dhcp/*, link/*에 모여 있다.
여기서 보이는 흐름은 다음과 같다.
- VMI의 network spec과 interface spec를 읽는다.
- Pod에서 실제 인터페이스 이름을 찾는다.
- binding 방식에 따라 bridge, masquerade, passt, SR-IOV 등의 wiring을 결정한다.
- 필요하면 TAP 장치를 만들고 libvirt domain NIC spec를 생성한다.
- 필요하면 KubeVirt 내부 DHCP 서버를 띄운다.
즉 network setup은 "이 VM의 NIC가 guest에서 어떻게 보여야 하는가"를 Pod namespace 안에서 준비하는 단계다.
TAP은 왜 필요한가
guest 운영체제는 가상 NIC를 통해 패킷을 주고받는다. QEMU 쪽에서는 흔히 TAP 디바이스를 backend로 사용한다. cmd/virt-chroot/tap-device-maker.go와 pkg/network/setup/podnic.go를 보면 KubeVirt가 TAP 장치 생성을 중요한 primitive로 사용한다는 점이 드러난다.
단순화하면:
- Pod 쪽 인터페이스 또는 bridge가 host-side endpoint 역할
- TAP이 QEMU 쪽 endpoint 역할
- libvirt domain spec가 둘을 이어주는 구성 정보를 담음
즉 TAP은 guest와 Pod 네트워크 사이의 실질적인 접점 중 하나다.
DHCP는 왜 KubeVirt가 직접 띄우는가
많은 경우 guest는 직접 CNI와 대화하지 않는다. 대신 KubeVirt가 guest에게 네트워크 설정을 알려 준다. pkg/network/setup/podnic.go를 보면 newDHCPConfigurator가 bridge 또는 masquerade binding에서 DHCP configurator를 만들고, EnsureDHCPServerStarted를 호출한다.
이 말은 곧:
- Pod 인터페이스 정보는 host 쪽에 있고
- guest가 받아야 할 IP와 gateway는 KubeVirt가 계산해
- DHCP로 guest에 전달한다
는 뜻이다.
guest IP는 누가 할당하는가
여기서 binding 방식별로 구분해야 한다.
masquerade binding
pkg/network/dhcp/masquerade.go와 pkg/network/link/address.go를 보면, KubeVirt는 guest용 내부 CIDR에서 gateway와 guest IP를 계산한다. IPv4 기준으로 VMNetworkCIDR가 없으면 기본 CIDR을 쓰고, 거기서 gateway와 guest IP를 정한다.
즉 masquerade에서는:
- Pod IP는 여전히 CNI가 Pod에 할당
- guest IP는 KubeVirt가 내부 CIDR에서 계산
- KubeVirt DHCP가 guest에 전달
- egress와 inbound 일부는 NAT로 처리
이게 가장 흔한 "VM은 private IP를 받고 Pod IP 뒤에 숨어 있는" 모델이다.
bridge binding
bridge binding은 guest를 Pod 네트워크에 더 직접 가깝게 붙인다. 여기서는 bridge와 TAP을 이용해 guest가 Pod 네트워크의 endpoint처럼 동작하게 만든다. 역사적으로는 Pod 쪽 IP를 guest가 받아 가는 형태의 모델 설명이 많았고, 현재도 핵심은 guest가 더 직접적인 L2 또는 Pod 네트워크 연결성을 가진다는 데 있다.
즉 bridge binding의 핵심은 "Pod IP를 NAT 뒤에 숨기는 것"보다 guest를 네트워크에 더 직접 노출하는 데 있다.
masquerade binding에서는 NAT가 어떻게 구현되는가
pkg/network/setup/netpod/masquerade/masquerade.go를 보면 KubeVirt는 masquerade에서 nftables 기반 NAT 규칙을 구성한다.
중요한 흐름은 다음과 같다.
- NAT table과 chain 생성
- guest source address에 대한 masquerade 규칙 추가
- 포트 포워딩 또는 DNAT 규칙 추가
- loopback과 migration 관련 예외 포트 처리
즉 masquerade는 단순 "브리지 하나"가 아니라:
- 내부 guest 주소 계산
- DHCP
- nftables NAT
- 필요 포트의 DNAT 또는 SNAT
가 함께 작동하는 조합이다.
link와 interface status는 왜 중요할까
KubeVirt는 guest NIC가 어떤 Pod interface와 연결되었는지 추적해야 한다. 그래서 pkg/network/controllers/vmi.go는 Pod annotation의 Multus network status를 읽어 VMI status의 Interfaces 필드를 갱신한다.
이 status에는:
- guest 쪽 logical network 이름
- Pod interface 이름
- info source가 Pod status인지 domain인지 guest agent인지
같은 정보가 들어간다. 네트워크 디버깅에서 이게 매우 중요하다.
이 구조가 운영상 주는 장점
1. Kubernetes 네트워크 모델을 크게 깨지 않는다
Pod는 여전히 CNI를 통해 attach된다.
2. guest 설정을 제어할 수 있다
KubeVirt가 DHCP와 bridge 또는 NAT를 직접 다루므로, guest 쪽 경험을 일관되게 만들 수 있다.
3. binding별 trade-off를 선택할 수 있다
성능, reachability, 서비스 노출, migration 적합성에 따라 binding을 고를 수 있다.
자주 하는 오해
오해 1: VM이 CNI에서 직접 IP를 받는다
기본적으로는 Pod가 먼저 받고, guest는 KubeVirt가 그 위에서 연결한다.
오해 2: Pod IP와 guest IP는 항상 같다
아니다. 특히 masquerade에서는 둘이 다르다. Pod IP는 CNI가, guest IP는 KubeVirt DHCP가 다룬다.
오해 3: KubeVirt 네트워크는 libvirt 내부 기능만으로 끝난다
아니다. Pod namespace 조작, TAP, DHCP, nftables, netlink가 함께 필요하다.
디버깅 체크리스트
- Pod IP가 있는지와 guest IP가 있는지를 분리해서 본다.
- masquerade면 guest CIDR, DHCP, nftables 규칙을 본다.
- bridge면 Pod interface와 bridge, TAP, guest DHCP 경로를 본다.
- VMI status의
Interfaces와 launcher Pod의 실제 인터페이스 이름이 일치하는지 본다.
마무리
KubeVirt 네트워크의 핵심은 VM이 CNI를 직접 쓰는 것이 아니라, Pod가 먼저 받은 네트워크를 guest 친화적으로 재배선하는 데 있다. 그래서 IP 할당도 binding에 따라 다르다. Pod IP는 CNI가 줄 수 있지만, guest IP는 KubeVirt의 DHCP와 내부 bridge 또는 NAT 로직이 결정할 수 있다. 이 기본 원리를 잡아 두면 다음 글에서 bridge, masquerade, passt, SR-IOV binding의 차이를 훨씬 명확하게 이해할 수 있다.
다음 글에서는 바로 그 binding들의 성격과 trade-off를 비교해 보겠다.