沙箱工作负载 — 标签决定资源名,库存随之被切开
目标
把三个节点分别搭建成不同的 GPU 工作负载用途,用真实的调度器确认每种用途的资源名称不同,以及由此造成库存被分开,然后制作判定标签与上报是否相符的检查器。
为什么重要
使用 GPU 的工作负载并不全是容器。因为许可证或遗留系统,有些必须在虚拟机里运行,而要把卡交给这样的虚拟机,节点上安装的驱动首先就得不同——容器需要数据中心驱动,直通需要 vfio-pci,vGPU 需要 vGPU Manager。GPU Operator 通过一行节点标签来接收这个选择。而这个选择的结果,连节点上报的资源名称也会改变。名称不同,对调度器来说就是不同的资源,所以即使一边的队列很长,另一边的空位仍然空着。为什么按用途划分库存是难以撤销的决定,原因就在这里。
步骤
- 创建
/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中写三行——每行是<노드이름> <라벨값>(占位符依次为节点名称与标签值,按节点名称升序)。 - 在
/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 开关的名称。 - 在三个节点的
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中写三行——每行是<노드이름> <자원이름> <광고량>(占位符依次为节点名称、资源名称与上报量,按节点名称升序)。 - 在
/root/gpuwl/k8s/pod-container.yaml中写出 Podjob-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=<노드이름>(占位符为节点名称)。 - 在
/root/gpuwl/k8s/pod-passthrough.yaml中写出 Podvmi-passthrough——命名空间为gpu-vm,limits中为nvidia.com/GA102GL_A10: 1,没有 nodeSelector。其余与第 4 步相同。应用后,在/root/gpuwl/out/05-passthrough.txt中写两行——NODE=和RESOURCE=(原样写出所请求的资源名称)。 - 在
/root/gpuwl/k8s/pod-vgpu.yaml中写出 Podvmi-vgpu——命名空间为gpu-vm,limits中为nvidia.com/NVIDIA_A10-12Q: 1,没有 nodeSelector。应用后,在/root/gpuwl/out/06-placement.txt中写三行——到目前为止创建的三个 Pod 各自启动在哪里,按<파드이름> <노드이름>(占位符依次为 Pod 名称与节点名称)的形式,按 Pod 名称升序。 - 在
/root/gpuwl/k8s/pod-cross.yaml中写出 Podjob-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。
参考
- 这个集群里既没有 KubeVirt,也没有 vfio-pci,无法启动虚拟机。虚拟机请求改用要求同一个资源名称的 Pod 来搭建——因为在调度阶段发生的事实际上相同。
- 在真实的集群中,虚拟机要在
spec.domain.devices.gpus[].deviceName中写资源名称,并且在此之前,KubeVirt 自定义资源的 permittedHostDevices 中必须已经列有该资源。 - 节点 status 用
kubectl patch node <이름> --subresource=status --type=merge(占位符为节点名称)来修改。 - 用 jsonpath 读取资源时,要对名称中的点进行转义:
{.status.allocatable.nvidia\.com/gpu}。 - 常见错误:资源名称哪怕只错一个字符,该节点也会悄悄从候选中消失。既没有错误,也没有警告。
- 常见错误:把上报量为“0”的资源算作“有”,最后的检查器就会做出离谱的判定。
给三个节点写上用途
创建 /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" 的资源,必须视为不存在。评分器也会用故意弄得不一致的转储来试一试。