GPU Operator 事先已经把这张 H20 登记成资源 nvidia.com/gpu,数量是 1。这次没有改 Operator,也没有改 vLLM。只提交了两个 Pod,用来看调度器怎么记账、设备插件怎么把卡交给容器、名额满了以后下一个任务停在哪里。
整条链路
- 1YAML 里写上 nvidia.com/gpu: "1"这是向调度器要一个名额。没有这行,调度器不参与分卡。
- 2调度器看节点还剩几个名额prod102 当时剩 1。第一个任务被写上节点名 prod102。
- 3kubelet 创建容器,设备插件填入可见的卡容器里出现 NVIDIA_VISIBLE_DEVICES=这张 H20 的 UUID。
- 4节点账本变成 1 / 1第二个同样申请 1 的 Pod 没有节点可去,停在 Pending。
- 5第一个 Pod 删除,名额回到节点调度器立刻把第二个任务分到 prod102,容器执行 nvidia-smi 后结束。
演示前
节点上报的可分配数量是 1。集群里没有别的 Pod 在申请这个资源。vLLM 健康检查是 200,演示过程中没有重启它。
allocatable=1 vLLM /health 200
名额从哪来:Node Feature Discovery 给节点打过 nvidia.com/gpu.present=true,nvidia-device-plugin 向 kubelet 注册了 1 张卡。这两个程序在演示开始前就已经是 Running,这次没有动它们。
第一个任务:占住名额
Pod 名叫 gpu-demo-hold,命名空间 gpu-operator。镜像用节点上已经有的 vLLM 镜像,只为了里面带 nvidia-smi。命令是列出显卡、打印环境变量,然后睡眠,让名额在观察期间一直被占着。容器一退出,名额就会还回去,所以要用睡眠把过程留住。
apiVersion: v1
kind: Pod
metadata:
name: gpu-demo-hold
namespace: gpu-operator
spec:
restartPolicy: Never
containers:
- name: smi
image: docker.m.daocloud.io/vllm/vllm-openai:latest
imagePullPolicy: IfNotPresent
command: ["bash","-lc","nvidia-smi -L; env | grep NVIDIA; sleep 180"]
resources:
limits:
nvidia.com/gpu: "1"
提交后大约 3 秒进入 Running。事件按顺序是:
| 谁 | 发生了什么 |
|---|---|
| default-scheduler | Successfully assigned gpu-operator/gpu-demo-hold to prod102 |
| kubelet | 镜像本机已经有,容器已创建、已启动 |
容器里 nvidia-smi -L 的输出:
GPU 0: NVIDIA H20 (UUID: GPU-0a9cfb9a-ef39-5b22-54ec-22474c4cfd76)
同一份日志里,和这次分配直接相关的环境变量是:
NVIDIA_VISIBLE_DEVICES=GPU-0a9cfb9a-ef39-5b22-54ec-22474c4cfd76 NVIDIA_DRIVER_CAPABILITIES=compute,utility
NVIDIA_VISIBLE_DEVICES 不是写在 YAML 里的。设备插件在分配这张卡时写进容器。值就是上面 nvidia-smi 看到的那张 H20。容器运行时按这个变量把设备放进容器。这时节点账本
kubectl describe node prod102 的已分配资源里,显卡这一行变成:
Resource Requests Limits nvidia.com/gpu 1 1
CPU、内存那几行是节点上其他 Pod 的合计,和这次演示无关。显卡这一行的 1,就是 gpu-demo-hold 占着的那个名额。可分配总量仍然是 1,所以剩余是 0。
第二个任务:排队
第一个还在跑的时候,提交 gpu-demo-queue。它也申请 nvidia.com/gpu: "1",命令只有 nvidia-smi -L,跑完就结束。
并排看两个 Pod:
NAME READY STATUS IP NODE gpu-demo-hold 1/1 Running 10.244.0.62 prod102 gpu-demo-queue 0/1 Pending <none> <none>
第二个没有 IP,也没有节点。调度器的原话:
FailedScheduling 0/1 nodes are available: 1 Insufficient nvidia.com/gpu. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
第一句是名额不够:唯一的节点上,nvidia.com/gpu 已经是 0 个剩余。第二句是抢占没有发生。调度器找了一圈,没有可以踢掉的、优先级更低的 Pod。vLLM 没有申请这个资源,所以它不在这本账上,也不会被这次排队踢掉。
删掉第一个,第二个放行
执行 kubectl -n gpu-operator delete pod gpu-demo-hold --wait=true。名额回到节点后,第二个任务的事件多了四行:
| 结果 | 事件 |
|---|---|
| 先排队 | FailedScheduling,Insufficient nvidia.com/gpu |
| 随后分到 | Successfully assigned gpu-operator/gpu-demo-queue to prod102 |
| 跑完 | 镜像已在本机,容器创建并启动,阶段变成 Succeeded |
它的输出和第一个任务看到的是同一张卡:
GPU 0: NVIDIA H20 (UUID: GPU-0a9cfb9a-ef39-5b22-54ec-22474c4cfd76)
从删除第一个 Pod 到第二个变成 Succeeded,中间只隔了一次约 3 秒的观察。排队结束得很快,因为镜像已经在本机,命令本身只是列出显卡。
收场
随后删除 gpu-demo-queue。现场回到演示前:
仍在申请 nvidia.com/gpu 的 Pod:无 allocatable=1 vLLM /health 200
gpu-operator 命名空间里剩下的,仍是 Operator 自己的程序:device plugin、feature discovery、validator,以及已经 Completed 的 cuda-validator。没有演示 Pod 留下。
k8s-探针与密钥/scripts/09-模拟GPU调度.sh。它会自己删除这两个 Pod。再跑一次会再次占用大约一分钟的调度名额。这次记录已经够看流程,不必为了复习再跑。这次演示没有把卡从现有程序手里拿走
演示期间 vLLM 的健康检查一直是 200。宿主机上的 Python 进程也没有被停掉。原因用这次的账本就能对上:
| 程序 | 在不在这本名额里 | 演示期间 |
|---|---|---|
| gpu-demo-hold / gpu-demo-queue | 申请了 nvidia.com/gpu: 1 | 同一时间只有一个能跑 |
| vLLM | 没有申请 | /health 保持 200 |
| Xinference、宿主机 Python | 没有申请 | 不经过这本账,继续使用同一张 H20 的显存 |
所以这次看到的「独占」,范围是调度器的名额:写了 nvidia.com/gpu: 1 的 Pod,同时只能有一个。显存仍然是原本那些进程一起用。两个演示容器只调用了 nvidia-smi,没有加载模型。