TT Lab
开始
学习 学习路径 课程

GPU Operator 与时间片

workload.config — 一个标签决定节点的身份

在 TT Lab 中继续学习

一句话总结

GPU Operator 会根据 nvidia.com/gpu.workload.config 标签的值(container、vm-passthrough、vm-vgpu),整体替换要在该节点上启动的 operand,结果连节点上报的资源名称也随之改变。容器节点上报 nvidia.com/gpu,直通节点上报 nvidia.com/GA102GL_A10 这样的设备型号名称,vGPU 节点则上报 nvidia.com/NVIDIA_A10-12Q 这样的 profile 名称。

为什么需要它

使用 GPU 的工作负载并不全是容器。许可证要求特定的操作系统,需要 Windows 驱动,或者遗留的仿真整个就在虚拟机里,这样的情况在现实中确实很多。这样的组织长期把集群拆成两个来运维——一个用于容器的 Kubernetes,一个用于虚拟机的 Hypervisor。把同一批 GPU 库存分放在两处,一边闲着,另一边也借不到。

KubeVirt 把虚拟机变成 Kubernetes 对象,消除了这堵墙,而 GPU Operator 则得以在此之上,把工作节点分别准备成两种用途。 官方文档用一句话概括了这个区别——容器需要数据中心驱动,GPU 直通需要 vfio-pci 驱动,vGPU 需要 NVIDIA vGPU Manager。所需驱动不同,是一切差异的根源。

一个标签就能彻底改变节点的软件

做法只是给节点加上标签。

kubectl label node <노드이름> --overwrite nvidia.com/gpu.workload.config=vm-vgpu

(占位符为节点名称)

这样一来,Operator 在该节点上启动的 operand 就不同了。

标签值 在该节点上启动的内容 作用
container 数据中心驱动、Container Toolkit、Kubernetes 设备插件、DCGM 与 DCGM Exporter 让容器使用 GPU,并输出指标
vm-passthrough VFIO Manager、沙箱设备插件 加载 vfio-pci 并绑定到该节点的所有 GPU,把直通 GPU 上报给 kubelet
vm-vgpu vGPU Manager、vGPU Device Manager、沙箱设备插件 安装 vGPU 驱动并创建 vGPU 设备,再上报给 kubelet

这里必须抓住两句话。

第一,没有标签时,默认值是 container。 Operator 会把没有标签的节点准备成容器用途。要改变默认值,就修改 ClusterPolicy 的 sandboxWorkloads.defaultWorkload。

第二,这个标签只有在 sandboxWorkloads.enabled 开启时才会被使用。 这个开关默认是关闭的,关闭时所有节点都被准备成容器用途,标签根本不会被读取。 只贴了标签,却看了好几个小时也不明白为什么没有变化,问题就出在这里。标签既不是语法错误,也不会给出警告。

而且官方文档还明确规定了一条限制。一个工作节点只能运行一种 GPU 工作负载。 容器和直通虚拟机不能在同一个节点上混用。因为 vfio-pci 会把该节点的 GPU 全部拿走,所以主机上的数据中心驱动必须放手这些卡。这意味着库存规划会以节点为单位固化下来,这也是实际工作中感受最深的限制。

资源名称会改变

容器节点上报的是我们熟悉的 nvidia.com/gpu。沙箱节点则不同。官方文档中的输出示例已经说明得很清楚。

# 패스스루 노드
$ kubectl get node <노드> -o json | jq '.status.allocatable | with_entries(select(.key | startswith("nvidia.com/")))'
{ "nvidia.com/GA102GL_A10": "1" }

# vGPU 노드 (기본 설정: 카드마다 절반 크기 Q 프로파일 두 개)
{ "nvidia.com/NVIDIA_A10-12Q": "4" }

前一个来自 PCI 设备型号名称,后一个来自 vGPU profile 名称。对两块 A10 卡应用默认配置,每块卡会生成两个 12Q,合计 4 个。在此之上,可以用节点标签 nvidia.com/vgpu.config 更换 profile——选择 A10-4Q 的话,每块卡会生成六个,两块卡就会上报 12 个。同样的硬件,只靠一个标签,上报量和资源名称就会一起改变。

名称不同这一事实带来的结果既简单又严酷。要求 nvidia.com/gpu 的 Pod 绝对去不了直通节点。 即使还有空位,在调度器看来,那个节点就是没有这种资源的节点。反过来也一样。集群把显卡库存分开之后,即使一边的队列很长,另一边的空位仍然空着。

把卡插进虚拟机的样子

设备被上报了,并不意味着虚拟机可以直接使用。必须在 KubeVirt 一侧另外写上允许列表——这是人们最容易漏掉的步骤。在 KubeVirt 自定义资源的 permittedHostDevices 下,直通写在 pciHostDevices,vGPU 写在 mediatedDevices,两者都要加上 externalResourceProvider: true。这个开关的意思是“这个资源不是我们创建的,而是由外部设备插件(这里是沙箱设备插件)上报的”。

spec:
  configuration:
    developerConfiguration:
      featureGates:
        - GPU
        - DisableMDEVConfiguration
    permittedHostDevices:
      pciHostDevices:
        - externalResourceProvider: true
          pciVendorSelector: 10DE:2236
          resourceName: nvidia.com/GA102GL_A10

在那之后,虚拟机一侧才能申请这张卡。它与 Pod 的 resources.limits 位置不同——VirtualMachineInstance 写在 spec.domain.devices.gpus 中。

spec:
  domain:
    devices:
      gpus:
        - deviceName: nvidia.com/GA102GL_A10
          name: gpu1

deviceName 是资源名称,name 是在虚拟机内部称呼这个设备的别名。虽然外观不同,但在调度阶段发生的事与 Pod 相同——替虚拟机运行的 Pod 要求这个资源,并前往上报该资源的节点。

最后是官方文档明确写出的一个事实。GPU Operator 不会自动安装虚拟机内部的 NVIDIA 驱动。 它的工作只到把卡插进虚拟机为止,客户机操作系统内部的驱动,由制作虚拟机镜像的一方负责。

在现场相遇的样子

第一,BIOS 和内核参数是前置条件。 虚拟化扩展和 IOMMU 必须在 BIOS 中开启,内核命令行中必须有 intel_iommu=on 或 amd_iommu=on。要在 Ampere 之后的卡上使用 vGPU,还必须在 BIOS 中开启 SR-IOV。这些都是需要重启的变更,如果不在节点加入集群之前,在镜像阶段处理好,以后就得把节点一台一台腾空来修改。

第二,在不同用途之间转移节点,并非不停机。 修改标签会使 operand 被替换,驱动也会改变。官方文档写明,修改 vGPU 配置时,要先关闭或迁移该节点上运行的虚拟机。想灵活调度库存的计划,在这一点上会与现实相遇——以天为单位转移可以,以分钟为单位则不行。

第三,资源名称与显卡型号绑定,清单也就被绑在硬件上。 要求 nvidia.com/GA102GL_A10 的虚拟机清单,在没有 A10 的集群中会永远处于 Pending。这与容器一侧的 nvidia.com/gpu 与型号无关形成对比。所以使用沙箱工作负载的组织,不会把型号名称直接写进清单,而是通过 Helm 值或 kustomize 补丁集中放在一处。

第四,本环境诚实的局限。 实验集群里既没有 KubeVirt,也没有 vfio-pci,无法启动虚拟机。所以下一个实验把虚拟机请求搭建成要求同一个资源名称的 Pod。 因为在调度阶段发生的事实际上相同(二者都是要求扩展资源的 Pod),所以去哪里、在哪里被拦住,可以通过真实的组件来学习。不过,虚拟机真正启动并占住显卡的后半部分无法确认,这一部分用上面的 VMI 清单的样子来代替。

参考文档

下一项实验要做什么

把三个节点分别标记为 container、vm-passthrough、vm-vgpu,并在节点 status 中搭建与各标签相符的资源名称。把每个标签值应该启动哪些 operand 整理成表格文件,再提交三种工作负载(容器 Pod、直通请求、vGPU 请求),用真实的调度器确认它们各自去了自己的节点,以及要求别人的资源时,哪里也启动不了。 最后获取节点转储,制作判定“这个标签与这个资源上报是否相符”的检查器,并看看它能否抓出故意弄得不一致的节点。