TT Lab
Blog

한국어 English 日本語 中文

Get started
Blog

Blog

Reading KubeVirt Source Code 7: Device Pass-through — How a GPU Becomes a Domain XML

Status: Draft (2026-10-17), Source + Read-only Measurement. Based on KubeVirt v1.9.0(commit 79d34c17). The GPU-attached VM is powered off, so its domain XML is an expecteddrawing based on the code (unmeasured). The measurements consist of reading the KubeVirt CR, node allocatables, and virt-handler logs.

Reading KubeVirt Source Code 7: Device Pass-through

Table of Contents

1. Intuition: Three Places Must Know About the Same Device

To pass a PCI device to a VM, three entities must be aware of the same fact.

  1. Host Kernel: The device must be detached from its original driver and bound to vfio-pci . This allows user space (QEMU) to directly manipulate the device under IOMMU protection.
  2. Kubernetes: The device must appear as a "node resource" so that the scheduler can place pods on that node and kubelet can inject device files into the pods. This bridge is the device plugin.
  3. libvirt: The domain XML must contain <hostdev> so that QEMU attaches that PCI address to the guest.

KubeVirt does not handle step 1 (that is the administrator's responsibility). Four components share the work of connecting steps 2 and 3.

2. Diagram: Seven Steps

GPU 한 장이 VMI 스펙에서 도메인 XML 까지 번호 순서. 위 셋은 미리 깔리는 길, 아래 넷은 VM 을 켤 때의 길 ① KubeVirt CR: permittedHostDevices pciVendorSelector 10DE:xxxx → resourceName nvidia.com/<GPU> ② virt-handler 장치 관리자 — sysfs 를 훑는다 vendor:device 가 맞고 드라이버가 vfio-pci 인 장치만 센다 (vfio-pci 가 아니면 vGPU 가능한 VF 인지만 한 번 더 본다) ③ 장치 플러그인 → kubelet 등록 → 노드 allocatable 장치 ID 는 IOMMU 그룹 번호 ④ VMI spec.domain.devices.gpus[].deviceName virt-controller 가 파드 limits 에 그 자원을 1개 넣는다 ⑤ 스케줄러 · kubelet Allocate() /dev/vfio/vfio · /dev/vfio/<그룹> 을 파드에 넣고 환경 변수 PCI_RESOURCE_NVIDIA_COM_<GPU>=0000:01:00.0 ⑥ virt-launcher: 주소 풀 → createPCIHostDevice() 환경 변수에서 PCI 주소를 꺼내 <hostdev> 를 만든다 메모리 오버헤드 +1Gi (VFIO 는 게스트 RAM 전체를 잠근다) ⑦ <hostdev mode='subsystem' type='pci' managed='no'> <source><address domain='0x0000' bus='0x01' …/></source> 실측: ①은 켜져 있는데 그 GPU 노드의 allocatable 은 0 virt-handler 로그(nvidia.go:33): 「Skipping non-vfio-pci device without vGPU profile … driver: nvidia」 — 호스트 드라이버가 쥠 → ② 에서 걸러져 ③ 이 0. vfio-pci 로 바꿔 묶어야 VM 이 받는다
Diagram: The path of a GPU from VMI spec to domain XML and measured bottlenecks

3. Code Paths

① Allowlist — KubeVirt CR

In the KubeVirt CR spec.configuration.permittedHostDevices.pciHostDevices[] , you write the pciVendorSelector(벤더:장치 (hexadecimal) and resourceName . The feature gate HostDevices must also be enabled. Devices not listed here cannot be used even if specified by name in the VMI. virt-controller verifies this using validatePermittedHostDevices() (renderresources.go:493) before drawing the pod.

② Device Manager Scans sysfs — virt-handler

Inside virt-handler, DeviceController.Run()(device_controller.go:481) sets up a callback so that whenever the KubeVirt configuration changes, refreshPermittedDevices() is called (:497). This function calls updatePermittedHostDevicePlugins()(:208via discoverPermittedHostPCIDevices()(pci_device.go:258). What this function does while iterating through /sys/bus/pci/devices consists of three things:

  1. Whether the device's 벤더:장치 is in the allowlist.
  2. Whether the driver is vfio-pci ((:278). Otherwise, shouldPermitNvidia()(nvidia.go:23) checks once more. Even if bound to the NVIDIA driver, if it is a Virtual Function (VF) with a vGPU profile, it accepts it; otherwise, it logs and discards it.
  3. Reads the device's IOMMU group number and NUMA node.

③ Device Plugin — Registering Resources with kubelet

A PCIDevicePlugin starts for each resource name (Start, pci_device.go:62) and registers with kubelet's device plugin socket. The device ID presented to kubelet is not the PCI address but the IOMMU group number(constructDPIdevices, :132). This is because VFIO passes devices by group, meaning devices in the same group cannot be given to different VMs. Once registered, it appears in the node's status.allocatable as nvidia.com/<GPU 이름>: 1 . The same device manager exposes generic devices like devices.kubevirt.io/kvm·tun·vhost-net in the same manner.

④ Pod Requests Resources — virt-controller

If the VMI has spec.domain.devices.gpus[].deviceName: nvidia.com/<GPU 이름> , the template from Part 3 uses the resource renderer option WithGPUsDevicePlugins()(renderresources.go:149) to add 1 of those resources to the compute container's limits. An additional 1Gi is added to the memory overhead (hypervisorbackend.go:130). Comments note this is because VFIO locks the entire guest RAM for DMA. The judgment criterion is vmitrait.HasVFIO() (vmitrait.go:33) — if any of host devices, GPUs, or SR-IOV are present.

⑤ Allocate — Pass the device file and address

When the scheduler places the pod on a node with remaining resources, the kubelet calls the device plugin's Allocate() (pci_device.go:152). There are two answers.

This environment variable is the sole link connecting the node (handler) and the pod (launcher). The launcher does not look at sysfs and relies solely on this value.

⑥ The launcher <hostdev> creates a

5 generateConverterContext() of getGPUDevices()(manager.go:1138) and gpu.CreateHostDevices()(gpu/hostdev.go:39) creates the address pool. The pool is populated by reading the environment variable from step 5 (AddressPool.load, addresspool.go:46). It takes one address per GPU, and createPCIHostDevice()(hostdev.go:141) creates api.HostDevice , and the converter's HostDeviceDomainConfigurator (compute/host_device.go:42) attaches it to the domain. If the same list of GPUs consists of vGPUs (mediated devices), it goes toward the MDEV_PCI_RESOURCE_… variable and the CreateMDEVHostDevices side. In v1.9.0, a path to receive them via DRA (Dynamic Resource Allocation) instead of the device plugin (hostdevice/dra/) is also included behind a feature gate.

⑦ Expected XML (Unmeasured)

According to the code, the domain XML for a single GPU (named gpu1) looks like this.

<hostdev mode='subsystem' type='pci' managed='no'>
  <source>
    <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
  </source>
  <alias name='ua-gpu-gpu1'/>
</hostdev>

managed='no' is important. If it is managed='yes' , libvirt detaches the device from the host driver and binds it to vfio-pci when turning on the VM, but the non-root libvirt inside the pod does not have the permission to do that. Therefore, the binding must be done in advance on the host (Section 1, item 1), and KubeVirt only counts already bound devices (②). The alias uses the user alias prefix ua- are given the GPU prefix gpu- . Also, a VMI with such devices is marked as unmigratable by virt-handler (vmiContainsNonMigratablePCIHostDevices, vm.go:1304).

4. Measurement: Allowlist is enabled but resources are 0

I inspected a setup where a single GPU node in a small cluster was configured to be dedicated to VMs (2026-10-17).

5. Limitations

Titi's note. If you are wondering, "I added it to the allowlist, why isn't it showing up?", look for Skipping in the virt-handler logs. KubeVirt only counts devices already bound to vfio-pci. Binding them is the host's job!

References

All accessed on October 17, 2026.

Sign in to like

Comments

No comments yet.

Sign in to leave a comment