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

GPU Operator 与时间片

沙箱工作负载 — 标签决定资源名,库存随之被切开

在 TT Lab 中继续学习

目标

把三个节点分别搭建成不同的 GPU 工作负载用途,用真实的调度器确认每种用途的资源名称不同,以及由此造成库存被分开,然后制作判定标签与上报是否相符的检查器。

为什么重要

使用 GPU 的工作负载并不全是容器。因为许可证或遗留系统,有些必须在虚拟机里运行,而要把卡交给这样的虚拟机,节点上安装的驱动首先就得不同——容器需要数据中心驱动,直通需要 vfio-pci,vGPU 需要 vGPU Manager。GPU Operator 通过一行节点标签来接收这个选择。而这个选择的结果,连节点上报的资源名称也会改变。名称不同,对调度器来说就是不同的资源,所以即使一边的队列很长,另一边的空位仍然空着。为什么按用途划分库存是难以撤销的决定,原因就在这里。

步骤

  1. 创建 /root/gpuwl/out、/root/gpuwl/bin、/root/gpuwl/k8s,并创建命名空间 gpu-vm。给三个节点添加标签 nvidia.com/gpu.workload.config——lab-node-0 为 container,lab-node-1 为 vm-passthrough,lab-node-2 为 vm-vgpu。然后在 /root/gpuwl/out/01-nodes.txt 中写三行——每行是 <노드이름> <라벨값>(占位符依次为节点名称与标签值,按节点名称升序)。
  2. 在 /root/gpuwl/out/operands.txt 中写五行。前三行是 <라벨값>=<오퍼랜드 목록>(占位符依次为标签值与 operand 列表),列表用逗号连接(顺序无关)——container 对应 datacenter-driver、container-toolkit、device-plugin、dcgm-exporter,vm-passthrough 对应 vfio-manager、sandbox-device-plugin,vm-vgpu 对应 vgpu-manager、vgpu-device-manager、sandbox-device-plugin。第四行在 NO_LABEL= 中写没有标签时 Operator 所假定的值,第五行在 ENABLE_FLAG= 中写让这个标签生效的 ClusterPolicy 开关的名称。
  3. 在三个节点的 status.capacity 和 status.allocatable 两处,分别写入各自的资源——lab-node-0 的 nvidia.com/gpu 为 "4",lab-node-1 的 nvidia.com/GA102GL_A10 为 "2",lab-node-2 的 nvidia.com/NVIDIA_A10-12Q 为 "4"。然后在 /root/gpuwl/out/03-resources.txt 中写三行——每行是 <노드이름> <자원이름> <광고량>(占位符依次为节点名称、资源名称与上报量,按节点名称升序)。
  4. 在 /root/gpuwl/k8s/pod-container.yaml 中写出 Pod job-container——命名空间为 gpu-vm,容器名称为 cuda,镜像为 nvcr.io/nvidia/cuda:12.4.1-base-ubuntu22.04,limits 中为 nvidia.com/gpu: 1。不要使用 nodeSelector。 应用后,在 /root/gpuwl/out/04-container.txt 中用一行写出它启动在哪个节点上——NODE=<노드이름>(占位符为节点名称)。
  5. 在 /root/gpuwl/k8s/pod-passthrough.yaml 中写出 Pod vmi-passthrough——命名空间为 gpu-vm,limits 中为 nvidia.com/GA102GL_A10: 1,没有 nodeSelector。其余与第 4 步相同。应用后,在 /root/gpuwl/out/05-passthrough.txt 中写两行——NODE= 和 RESOURCE=(原样写出所请求的资源名称)。
  6. 在 /root/gpuwl/k8s/pod-vgpu.yaml 中写出 Pod vmi-vgpu——命名空间为 gpu-vm,limits 中为 nvidia.com/NVIDIA_A10-12Q: 1,没有 nodeSelector。应用后,在 /root/gpuwl/out/06-placement.txt 中写三行——到目前为止创建的三个 Pod 各自启动在哪里,按 <파드이름> <노드이름>(占位符依次为 Pod 名称与节点名称)的形式,按 Pod 名称升序。
  7. 在 /root/gpuwl/k8s/pod-cross.yaml 中写出 Pod job-cross——命名空间为 gpu-vm,用 nodeSelector 设置 nvidia.com/gpu.workload.config: vm-passthrough,并在 limits 中要求 nvidia.com/gpu: 1。这相当于对直通节点索要容器用途的资源。应用后,在 /root/gpuwl/out/07-cross.txt 中写三行——PHASE=、NODE=(为空时写 none)、MESSAGE=(PodScheduled 条件的消息)。
  8. 把 kubectl get nodes -o json 的结果保存到 /root/gpuwl/out/nodes.json,并创建 /root/gpuwl/bin/check-config.sh <노드JSON파일>(占位符为节点 JSON 文件)。只查看文件内的节点中带有 nvidia.com/gpu.workload.config 标签的,判定它的值与所上报的 nvidia.com/ 资源名称是否相符——container 必须是 nvidia.com/gpu,vm-vgpu 必须是 vGPU profile 名称(以编号和大写字母结尾的),vm-passthrough 必须是这两者都不是的设备型号名称。每个不一致的节点输出一行 MISMATCH=<노드이름> <이유>(占位符依次为节点名称与原因)并以 1 结束;如果全部相符,就输出 OK=<검사한 노드 수>(占位符为所检查的节点数)并以 0 结束。创建之后,把它接到保存的转储上,并把输出保存到 /root/gpuwl/out/consistency.txt。

参考

给三个节点写上用途

创建 /root/gpuwl/out、/root/gpuwl/bin、/root/gpuwl/k8s,并创建命名空间 gpu-vm。给三个节点添加标签 nvidia.com/gpu.workload.config——lab-node-0 为 container,lab-node-1 为 vm-passthrough,lab-node-2 为 vm-vgpu。然后在 /root/gpuwl/out/01-nodes.txt 中写三行——每行是 <노드이름> <라벨값>(占位符依次为节点名称与标签值,按节点名称升序)。

这一个标签会彻底改变将要在该节点上启动的 Operator 软件。标签用 kubectl label node <이름> --overwrite <키>=<값>(占位符依次为节点名称、键与值)添加。值只有三个,即使拼错也没有任何警告——所以添加之后需要养成确认的习惯。用 kubectl get nodes -L <키>(占位符为键)可以在一个画面里看到。

把每个标签值会启动什么整理成表格

在 /root/gpuwl/out/operands.txt 中写五行。前三行是 <라벨값>=<오퍼랜드 목록>(占位符依次为标签值与 operand 列表),列表用逗号连接(顺序无关)——container 对应 datacenter-driver、container-toolkit、device-plugin、dcgm-exporter,vm-passthrough 对应 vfio-manager、sandbox-device-plugin,vm-vgpu 对应 vgpu-manager、vgpu-device-manager、sandbox-device-plugin。第四行在 NO_LABEL= 中写没有标签时 Operator 所假定的值,第五行在 ENABLE_FLAG= 中写让这个标签生效的 ClusterPolicy 开关的名称。

把三行并排放在一起,就能看出共同点只有 sandbox-device-plugin 一个——虚拟机一侧的两种用途,上报设备的方法相同,准备驱动的方法不同。第五行最重要。如果这个开关关闭(默认是关闭),无论标签贴得多准确,都不会被读取,所有节点都被准备成容器用途。开关的名称是用点连接的两个单词。

为每种用途上报不同的资源名称

在三个节点的 status.capacity 和 status.allocatable 两处,分别写入各自的资源——lab-node-0 的 nvidia.com/gpu 为 "4",lab-node-1 的 nvidia.com/GA102GL_A10 为 "2",lab-node-2 的 nvidia.com/NVIDIA_A10-12Q 为 "4"。然后在 /root/gpuwl/out/03-resources.txt 中写三行——每行是 <노드이름> <자원이름> <광고량>(占位符依次为节点名称、资源名称与上报量,按节点名称升序)。

在真实的集群中,由各节点上不同的设备插件来填写这个位置——容器节点由 Kubernetes 设备插件填写,虚拟机一侧的两个节点由沙箱设备插件填写。直通资源名称来自 PCI 设备型号,vGPU 资源名称来自 profile——只有后者以编号和大写字母结尾,这条规则就是最后一步的判定依据。在 JSON 补丁中,不需要对资源名称里的点和斜杠做转义。只有用 jsonpath 读取时才要对点转义。

容器工作负载去容器节点

在 /root/gpuwl/k8s/pod-container.yaml 中写出 Pod job-container——命名空间为 gpu-vm,容器名称为 cuda,镜像为 nvcr.io/nvidia/cuda:12.4.1-base-ubuntu22.04,limits 中为 nvidia.com/gpu: 1。不要使用 nodeSelector。 应用后,在 /root/gpuwl/out/04-container.txt 中用一行写出它启动在哪个节点上——NODE=<노드이름>(占位符为节点名称)。

明明没有给出任何条件,能去的节点却只有一个。是资源名称本身选出了节点——整个实验的要点都包含在这一行里。扩展资源只写在 limits 中。因为有“requests 与 limits 必须相同”这条规则,所以只写 limits 就够了。

直通请求调用的是设备型号名称

在 /root/gpuwl/k8s/pod-passthrough.yaml 中写出 Pod vmi-passthrough——命名空间为 gpu-vm,limits 中为 nvidia.com/GA102GL_A10: 1,没有 nodeSelector。其余与第 4 步相同。应用后,在 /root/gpuwl/out/05-passthrough.txt 中写两行——NODE= 和 RESOURCE=(原样写出所请求的资源名称)。

在真实的集群中,这个位置是 VirtualMachineInstance,资源写在 spec.domain.devices.gpus[].deviceName 中。这个 Pod 里没有 KubeVirt,无法启动虚拟机,但在调度阶段发生的事是一样的——因为虚拟机最终也是由要求该资源的 Pod 代为运行的。资源名称来自 PCI 设备型号。哪怕只错一个字符,该节点也会从候选中消失。

vGPU 请求调用的是 profile 名称

在 /root/gpuwl/k8s/pod-vgpu.yaml 中写出 Pod vmi-vgpu——命名空间为 gpu-vm,limits 中为 nvidia.com/NVIDIA_A10-12Q: 1,没有 nodeSelector。应用后,在 /root/gpuwl/out/06-placement.txt 中写三行——到目前为止创建的三个 Pod 各自启动在哪里,按 <파드이름> <노드이름>(占位符依次为 Pod 名称与节点名称)的形式,按 Pod 名称升序。

把三行并排放在一起,就能一眼看出一个资源名称决定了整个放置结果。没有给任何一个 Pod 设置 nodeSelector,三个 Pod 却各自去了不同的节点。vGPU profile 名称以编号和大写字母结尾(例如 12Q)。这个形态就是它与直通名称区分开的地方,也是第 8 步检查器要用的信号。放置结果可以用 kubectl get pods -n <ns> -o custom-columns=(占位符为命名空间)一次性提取。

调用别人的资源,哪里也去不了

在 /root/gpuwl/k8s/pod-cross.yaml 中写出 Pod job-cross——命名空间为 gpu-vm,用 nodeSelector 设置 nvidia.com/gpu.workload.config: vm-passthrough,并在 limits 中要求 nvidia.com/gpu: 1。这相当于对直通节点索要容器用途的资源。应用后,在 /root/gpuwl/out/07-cross.txt 中写三行——PHASE=、NODE=(为空时写 none)、MESSAGE=(PodScheduled 条件的消息)。

那个节点上明明上报了两块卡,也有空位。却依然去不了——因为调度器只是把资源名称当作字符串来比对。集群按用途把库存分开之后,即使一边的队列很长,另一边的空位仍然空着。这是引入沙箱工作负载时最先要规划的限制。请留意消息是怎么说的——它说的是“资源不足”。

判定标签与资源上报是否相符的检查器

把 kubectl get nodes -o json 的结果保存到 /root/gpuwl/out/nodes.json,并创建 /root/gpuwl/bin/check-config.sh <노드JSON파일>(占位符为节点 JSON 文件)。只查看文件内的节点中带有 nvidia.com/gpu.workload.config 标签的,判定它的值与所上报的 nvidia.com/ 资源名称是否相符——container 必须是 nvidia.com/gpu,vm-vgpu 必须是 vGPU profile 名称(以编号和大写字母结尾的),vm-passthrough 必须是这两者都不是的设备型号名称。每个不一致的节点输出一行 MISMATCH=<노드이름> <이유>(占位符依次为节点名称与原因)并以 1 结束;如果全部相符,就输出 OK=<검사한 노드 수>(占位符为所检查的节点数)并以 0 结束。创建之后,把它接到保存的转储上,并把输出保存到 /root/gpuwl/out/consistency.txt。

这个检查器不连接集群,只读取一个文件。 这样,无论是别人发来的转储,还是部署前的评审,都能使用。判定的核心是名称的样子——vGPU profile 像 -12Q、-4Q 那样,在连字符之后以数字和一个大写字母结尾。一个节点上报了多种 nvidia 资源,也属于不一致。一个工作节点只运行一种 GPU 工作负载。上报量为 "0" 的资源,必须视为不存在。评分器也会用故意弄得不一致的转储来试一试。