TT Lab
博客

한국어 日本語 中文

开始
博客

博客

KubeVirt 源码阅读 7:设备直通 — GPU 如何成为域 XML

状态:草稿 (2026-10-17),源码 + 只读实测。 基于 KubeVirt v1.9.0(提交 79d34c17)。连接了 GPU 的虚拟机处于关闭状态, 其域 XML 是通过代码绘制的 预测值(未实测)。实测部分读取了 KubeVirt CR、节点的 allocatable 和 virt-handler 日志。

KubeVirt 源码阅读 7:设备直通

目录

1. 直观理解:三个地方必须了解同一设备

要将 PCI 设备传递给虚拟机,三个地方必须了解同一事实。

  1. 宿主机内核:必须将该设备从原有驱动程序中剥离并绑定到 vfio-pci 。只有这样,用户空间 (QEMU) 才能在 IOMMU 的保护下直接操作设备。
  2. Kubernetes:设备必须显示为“节点的资源”,调度程序才能将 Pod 调度到该节点,并且 kubelet 会将设备文件注入 Pod 中。这座桥梁就是设备插件。
  3. libvirt:域 XML 中必须包含 <hostdev> ,QEMU 才能将该 PCI 地址附加到客户机。

KubeVirt 不做第 1 点(由管理员负责)。连接第 2 点和第 3 点的工作由四个组件分摊。

2. 图解:七个步骤

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 이 받는다
图解:单张 GPU 从 VMI 规格到域 XML 的路径以及实测中的阻塞点

3. 代码路径

① 允许列表 — KubeVirt CR

在 KubeVirt CR 的 spec.configuration.permittedHostDevices.pciHostDevices[] 中编写 pciVendorSelector(벤더:장치 (十六进制)和 resourceName 。还必须启用功能门控 HostDevices 。即使 VMI 写了名称,这里没有的设备也无法使用。virt-controller 在绘制 Pod 之前通过 validatePermittedHostDevices() (renderresources.go:493进行检查。

② 设备管理器扫描 sysfs — virt-handler

virt-handler 中的 DeviceController.Run()(device_controller.go:481每次 KubeVirt 配置更改时都会注册回调以调用 refreshPermittedDevices() (:497)。此函数通过 updatePermittedHostDevicePlugins()(:208调用 discoverPermittedHostPCIDevices()(pci_device.go:258。此函数遍历 /sys/bus/pci/devices 并执行三项操作:

  1. 检查设备的 벤더:장치 是否在允许列表中。
  2. 检查驱动程序是否为 vfio-pci ((:278)。否则, shouldPermitNvidia()(nvidia.go:23会再次检查。即使是绑定到 NVIDIA 驱动程序的设备,如果是带有 vGPU 配置文件的虚拟功能 (VF) 也会被接受,否则会记录日志并丢弃。
  3. 读取设备的 IOMMU 组号和 NUMA 节点。

③ 设备插件 — 向 kubelet 注册资源

每个资源名称都会启动一个 PCIDevicePlugin (Start, pci_device.go:62)并向 kubelet 的设备插件套接字注册。向 kubelet 提供的设备 ID 不是 PCI 地址,而是 IOMMU 组号(constructDPIdevices, :132)。由于 VFIO 按组单位传递设备,因此不能将同一组的设备分配给不同的虚拟机。注册完成后,它会像 status.allocatable 一样出现在节点的 nvidia.com/<GPU 이름>: 1 中。同一设备管理器也会以相同的方式暴露诸如 devices.kubevirt.io/kvm·tun·vhost-net 等通用设备。

④ Pod 请求资源 — virt-controller

如果 VMI 中存在 spec.domain.devices.gpus[].deviceName: nvidia.com/<GPU 이름> ,则第 3 部分中的模板使用资源渲染器选项 WithGPUsDevicePlugins()(renderresources.go:149将该资源逐个添加到 compute 容器的 limits 中。内存开销会增加 1Gi(hypervisorbackend.go:130)。注释中写道,这是因为 VFIO 会为 DMA 锁定整个客户机 RAM。判断标准位于 vmitrait.HasVFIO() (vmitrait.go:33— 即宿主机设备、GPU 或 SR-IOV 中存在任意一个时。

⑤ Allocate — 传入设备文件和地址

当调度员将 Pod 安排到资源有剩余的节点上时,kubelet 会调用设备插件的 Allocate() (pci_device.go:152)。答案有两个:

这个环境变量是连接节点(handler)和 Pod(launcher)的唯一纽带。launcher 不会查看 sysfs,只相信这个值。

⑥ launcher <hostdev> 的创建者

5篇 generateConverterContext() 的 getGPUDevices()(manager.go:1138)并调用, gpu.CreateHostDevices()(gpu/hostdev.go:39)负责创建地址池。该池会通过读取⑤中的环境变量来填充(AddressPool.load, addresspool.go:46)。针对每个 GPU 提取一个地址, createPCIHostDevice()(hostdev.go:141)会创建 api.HostDevice ,接着 converter 的 HostDeviceDomainConfigurator (compute/host_device.go:42)会将其挂载到虚拟机域上。如果相同的 GPU 列表是 vGPU(mediated device),则会走向 MDEV_PCI_RESOURCE_… 变量与 CreateMDEVHostDevices 方向。在 v1.9.0 中,通过 DRA(Dynamic Resource Allocation)而非设备插件来接收的方式(hostdevice/dra/)也已在功能门控(Feature Gate)之后引入。

⑦ 预期 XML(未实际测量)

根据代码,单个 GPU(名称为 gpu1)的虚拟机域 XML 应该类似于这样。

<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' 至关重要。如果是 managed='yes' ,当 libvirt 启动虚拟机时,会将设备从宿主机驱动中分离并绑定到 vfio-pci,但 Pod 内的非 root libvirt 没有这样的权限。因此, 绑定的工作必须在宿主机上提前 完成(第 1 节的 1),而 KubeVirt 只会统计已经绑定好的设备(②)。别名使用用户自定义别名,前缀为 ua- 的 GPU 前缀 gpu- 命名。此外,带有这种设备的 VMI 会被 virt-handler 标记为不可进行实时迁移(vmiContainsNonMigratablePCIHostDevices, vm.go:1304).

4. 实测:允许列表已启用但资源为 0

读取了为将小型集群中的一个 GPU 节点专用于 VM 而进行的配置状态(2026-10-17)。

5. 局限性

TT 的一句话总结。 如果纳闷“明明加进白名单了,为什么还是看不到?”,请在 virt-handler 日志中搜索 Skipping 。KubeVirt 只会统计已经绑定到 vfio-pci 的设备。绑定设备是宿主机的事情!

参考资料

所有资料均于 2026-10-17 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论