Skip to content
Published on

KubeVirt 네트워크 1: Pod 네트워크를 VM 네트워크로 바꾸는 기본 원리

Authors

들어가며

사용자 입장에서 가장 직관적인 질문은 이것이다. "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.mddocs/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/*에 모여 있다.

여기서 보이는 흐름은 다음과 같다.

  1. VMI의 network spec과 interface spec를 읽는다.
  2. Pod에서 실제 인터페이스 이름을 찾는다.
  3. binding 방식에 따라 bridge, masquerade, passt, SR-IOV 등의 wiring을 결정한다.
  4. 필요하면 TAP 장치를 만들고 libvirt domain NIC spec를 생성한다.
  5. 필요하면 KubeVirt 내부 DHCP 서버를 띄운다.

즉 network setup은 "이 VM의 NIC가 guest에서 어떻게 보여야 하는가"를 Pod namespace 안에서 준비하는 단계다.

TAP은 왜 필요한가

guest 운영체제는 가상 NIC를 통해 패킷을 주고받는다. QEMU 쪽에서는 흔히 TAP 디바이스를 backend로 사용한다. cmd/virt-chroot/tap-device-maker.gopkg/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.gopkg/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를 비교해 보겠다.