上一篇,我们已经把一台普通 Ubuntu Server 变成了:
Single-node Kubernetes │ ▼ kubelet │ CRI │ ▼ containerd │ ▼ runc并且确认:
kubectl get nodes -o wide看到的 Container Runtime 已经是:
containerd://...所以现在 Kubernetes 已经知道:
CPU Memory Pods这些资源。
而同一台服务器上:
nvidia-smi又明明可以看到:
NVIDIA GeForce RTX 3060上一篇 Docker 实验甚至已经证明:
docker run --gpus all ...Container 也可以真正访问 GPU。
于是一个很自然的问题出现了。
执行:
kubectl describe node查看:
Capacity Allocatable为什么里面没有:
nvidia.com/gpu: 1也就是说:
Linux 已经认识 GPU,NVIDIA Driver 认识 GPU,Container Runtime 也可以使用 GPU,但 Kubernetes 为什么还不知道这里有一张 GPU?
这一篇,就来解决这个问题。
一、Kubernetes 为什么不会自己识别 GPU?
CPU 和 Memory 对 Kubernetes 来说属于:
Native Resource所以一个 Node 加入 Cluster 后,很快就能看到:
capacity: cpu: 6 memory: ... pods: 110但是 GPU 不一样。
世界上不仅有:
NVIDIA GPU还有:
AMD GPU Intel GPU FPGA SmartNIC InfiniBand AI Accelerator ...Kubernetes Core 不可能内置所有厂商、所有硬件的识别逻辑。
所以 Kubernetes 采用了另一种方式:
Kubernetes 定义接口,硬件厂商自己告诉 Kubernetes:“我这里有哪些设备”。
这里就出现了两个重要概念:
Extended Resource + Device Plugin二、Extended Resource:让 Kubernetes 的资源世界可以扩展
Kubernetes 原生认识:
cpu memory ephemeral-storage pods但它允许我们继续增加:
vendor-domain/resource形式的资源。
例如:
example.com/fpga vendor.com/nic nvidia.com/gpu这种资源就叫:
Extended ResourceNVIDIA GPU 最常见的 Resource Name 就是:
nvidia.com/gpu所以当我们以后看到:
resources: limits: nvidia.com/gpu: 1它真正表达的是:
这个 Container 需要一个由 NVIDIA Device Plugin 提供的 GPU Resource。
Kubernetes 官方规定,Extended Resource 使用整数计数,而且不能 Overcommit;Device Plugin 注册的 NVIDIA GPU 正是其中一个典型例子。
三、nvidia.com/gpu: 1到底是什么意思?
这个地方特别容易误解。
它不是:
GPU 1也不是:
1GB VRAM更不是:
使用 GPU 的 1%而是:
请求一个 GPU Resource Unit假设服务器有:
GPU 0 GPU 1 GPU 2 GPU 3Device Plugin 可能向 Kubernetes 报告:
nvidia.com/gpu: 4那么:
nvidia.com/gpu: 1表示:
我要其中一张 GPU至于最后具体分到:
GPU 0 还是 GPU 2不是 Pod YAML 自己决定的。
四、那是谁创建了nvidia.com/gpu?
答案就是:
NVIDIA Device PluginKubernetes 提供了一套:
Device Plugin Framework厂商可以实现自己的 Plugin。
NVIDIA 官方的:
nvidia-device-plugin就是其中一个实现。
它通常以:
DaemonSet运行。
也就是说:
每个 GPU Node │ ▼ 运行一个 NVIDIA Device Plugin PodDevice Plugin 的主要工作包括:
发现 GPU ↓ 检查设备状态 ↓ 向 kubelet 注册 ↓ 报告 GPU 数量 ↓ Pod 申请 GPU 时完成设备分配NVIDIA 官方也把它定义为一个 DaemonSet,用于暴露各 Node 的 GPU 数量、跟踪设备健康状态,并支持 GPU Container。
五、Device Plugin 到底向谁报告 GPU?
不是直接找:
Scheduler也不是直接找:
API Server而是:
NVIDIA Device Plugin │ ▼ kubeletDevice Plugin 会通过 kubelet 提供的 Device Plugin API 注册自己。
大致可以理解成:
RTX 3060 │ ▼ NVIDIA Driver / NVML │ ▼ NVIDIA Device Plugin │ │ │ Register │ ListAndWatch ▼ kubelet │ ▼ Node Status │ ▼ API Server │ ▼ SchedulerDevice Plugin 注册时会告诉 kubelet:
ResourceName = nvidia.com/gpu随后继续报告:
有哪些设备 哪些设备 Healthy 哪些设备 Unhealthykubelet 再把这些资源写入:
Node Status所以最终我们才会看到:
Capacity: nvidia.com/gpu: 1 Allocatable: nvidia.com/gpu: 1这套注册和资源上报流程正是 Kubernetes Device Plugin Framework 的标准机制。
六、Scheduler 其实还是“不认识 RTX 3060”
这个地方很有意思。
当:
nvidia.com/gpu: 1出现在 Node 上以后,并不意味着 Scheduler 突然学会了:
CUDA RTX 3060 VRAM Tensor CoreScheduler 真正看到的更接近:
Node A cpu: 6 memory: 32Gi nvidia.com/gpu: 1Pod 请求:
nvidia.com/gpu: 1Scheduler 做的是:
Node A 有没有至少 1 个可分配的 nvidia.com/gpu?如果有:
可以调度如果没有:
Pending所以:
Scheduler 调度的仍然是 Resource,而不是直接操作 GPU Hardware。
这和:
CPU Request Memory Request的思路其实是一致的。
七、Device Plugin 和 NVIDIA Container Toolkit 是一回事吗?
不是。
这是这一篇最重要的区别之一。
上一篇 Docker GPU 已经讲过:
NVIDIA Container Toolkit解决的是:
怎样让 Container 真正使用宿主机 GPU。
例如:
Device Node Driver Library Runtime Configuration CDI等。
而:
NVIDIA Device Plugin主要解决的是:
怎样让 Kubernetes 知道 Node 有 GPU,并参与 Resource Scheduling。
可以画成两条路径。
第一条:
GPU ↓ NVIDIA Device Plugin ↓ kubelet ↓ nvidia.com/gpu ↓ Scheduler解决:
谁可以拿到 GPU?第二条:
Pod ↓ kubelet ↓ containerd ↓ NVIDIA Container Toolkit ↓ GPU解决:
拿到 GPU 以后 Container 怎么真正使用它?所以:
Device Plugin ≠ Container Toolkit一个偏:
Resource Discovery / Allocation一个偏:
Runtime Device Injection八、实验环境
继续使用前几篇的 AI Infra 实验机:
Ubuntu 24.04 Server Intel Core i5-10400F NVIDIA GeForce RTX 3060 12GB VRAM Kubernetes 1.37.1 containerd前一篇已经完成:
kubeadm kubelet kubectl containerd CRI Flannel Single-node Kubernetes这一篇不再介绍 Kubernetes 安装。
首先确认:
kubectl get nodesNode 是:
Ready然后:
nvidia-smi确认宿主机 RTX 3060 正常。
九、实验一:现在 Kubernetes 还看不到 GPU
先不要安装 Device Plugin。
执行:
kubectl describe node找到:
Capacity: Allocatable:也可以直接:
kubectl get node \ -o jsonpath='{.items[0].status.capacity}{"\n"}'这时候应该能看到:
cpu memory pods ephemeral-storage但是还没有:
nvidia.com/gpu这就是我们的:
Before状态。
宿主机:
RTX 3060 ✅Kubernetes:
nvidia.com/gpu ❌十、先让 containerd 具备 NVIDIA GPU Runtime 能力
上一篇 Docker GPU 实验中,我们配置的是:
sudo nvidia-ctk \ runtime configure \ --runtime=docker它只配置:
Docker而现在 Kubernetes 走的是:
kubelet ↓ CRI ↓ containerd所以还需要给 containerd 配置 NVIDIA Runtime。
为了让这次实验保持简单,我们直接让 NVIDIA Runtime 成为 containerd 的默认 Runtime:
sudo nvidia-ctk \ runtime configure \ --runtime=containerd \ --set-as-default然后:
sudo systemctl restart containerdNVIDIA 当前 Container Toolkit 会为 containerd 创建 NVIDIA Runtime 配置,并通过 containerd 配置的imports机制加载;官方当前文档默认使用:
/etc/containerd/conf.d/99-nvidia.toml这类 drop-in 配置。
可以检查:
sudo cat \ /etc/containerd/conf.d/99-nvidia.toml以及:
systemctl status containerd --no-pager如果生产环境不希望把 NVIDIA Runtime 设为默认,也可以使用:
RuntimeClass单独选择nvidiaRuntime;NVIDIA Device Plugin 官方同样支持这种方式。为了让个人实验路径更简单,这里先使用默认 Runtime。
十一、安装 NVIDIA Device Plugin
接下来终于安装本篇核心组件。
当前 NVIDIA Device Plugin 已进入:
v0.20.x系列,当前源码中的静态 DaemonSet 使用:
v0.20.1镜像。
个人实验直接使用官方 Static DaemonSet:
kubectl create -f \ https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.20.1/deployments/static/nvidia-device-plugin.yml然后:
kubectl get pods -n kube-system \ | grep nvidia正常应该看到:
nvidia-device-plugin-daemonset-xxxxx 1/1 Running为什么它是:
DaemonSet因为以后如果 Cluster 有:
GPU Node A GPU Node B GPU Node C每台机器都需要有人负责:
发现本机 GPU ↓ 告诉本机 kubeletDaemonSet 正好适合这种:
每个 Node 一个 Agent的模式。
十二、看看 Device Plugin 在说什么
找到 Plugin Pod:
kubectl get pods \ -n kube-system \ | grep nvidia然后:
kubectl logs \ -n kube-system \ <nvidia-device-plugin-pod>如果:
Driver NVML Runtime都正常,
Plugin 就会发现 RTX 3060,并向 kubelet 注册:
nvidia.com/gpuDevice Plugin 本身通过:
/var/lib/kubelet/device-plugins下面的 Unix Socket 与 kubelet Device Plugin Framework 通信;官方静态 DaemonSet 也正是把这个宿主机目录 Mount 到 Plugin Container 中。
十三、最关键的实验:nvidia.com/gpu出现了
现在再次执行:
kubectl describe node看:
Capacity: Allocatable:这一次应该出现:
Capacity: nvidia.com/gpu: 1 Allocatable: nvidia.com/gpu: 1也可以直接:
kubectl get node \ -o jsonpath='{.items[0].status.capacity.nvidia\.com/gpu}{"\n"}'期待:
1再看:
kubectl get node \ -o jsonpath='{.items[0].status.allocatable.nvidia\.com/gpu}{"\n"}'同样:
1这就是本篇最核心的变化。
之前:
RTX 3060 │ └── Linux 知道现在:
RTX 3060 │ ▼ NVIDIA Device Plugin │ ▼ kubelet │ ▼ nvidia.com/gpu: 1 │ ▼ Kubernetes物理 GPU 第一次真正进入了:
Kubernetes Resource Model十四、创建第一个 GPU Pod
现在可以真正请求 GPU 了。
创建:
cat <<'EOF' > gpu-pod.yaml apiVersion: v1 kind: Pod metadata: name: gpu-test spec: restartPolicy: Never containers: - name: cuda-test image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0 resources: limits: nvidia.com/gpu: 1 EOF执行:
kubectl apply -f gpu-pod.yaml观察:
kubectl get pod gpu-test -w正常应该从:
Pending经过:
ContainerCreating最终:
Completed然后:
kubectl logs gpu-test应该能看到 Vector Addition 的 CUDA Sample 最终:
Test PASSED这也是 NVIDIA Device Plugin 官方用于验证 GPU Pod 的 Sample Workload。
十五、这个 Pod 到底发生了什么?
现在把⑬和⑭串起来。
YAML 里面只有:
resources: limits: nvidia.com/gpu: 1但背后大致发生了:
GPU Pod │ │ request: │ nvidia.com/gpu: 1 ▼ Scheduler │ │ 找到有可用 GPU 的 Node ▼ kubelet │ ▼ NVIDIA Device Plugin │ │ Allocate ▼ 选择一个具体 GPU │ ▼ containerd │ ▼ NVIDIA Container Runtime / Toolkit │ ▼ GPU Device │ ▼ CUDA Program所以 Scheduler 并没有自己:
打开 /dev/nvidia0也没有自己:
调用 CUDA。
它只负责:
这个 Pod 需要 1 个 GPU ↓ 哪台 Node 满足条件?而真正到了 Node:
Device Plugin + kubelet + Container Runtime + NVIDIA Container Toolkit共同把这个决定落实。
十六、为什么只写limits?
对于普通 CPU,我们经常看到:
resources: requests: cpu: 1 limits: cpu: 2但是 GPU Extended Resource 通常写:
resources: limits: nvidia.com/gpu: 1因为 Device Plugin 管理的 Extended Resource:
只能使用整数 不能 Overcommit对于 Extended Resource,如果只写 Limit,Kubernetes 会把对应 Request 也视为相同值;如果同时填写 Request 和 Limit,两者必须相同。
因此:
nvidia.com/gpu: 0.5并不是默认 Kubernetes GPU Scheduling 的表达方式。
十七、一张 RTX 3060 能不能同时给两个 Pod?
默认情况下:
不能按照: 0.5 GPU + 0.5 GPU这样分。
为了直观看看 Kubernetes 的 Resource Accounting,可以先运行一个长期占用 GPU Resource 的 Pod:
cat <<'EOF' > gpu-holder.yaml apiVersion: v1 kind: Pod metadata: name: gpu-holder spec: containers: - name: holder image: busybox:1.36 command: - sh - -c - sleep infinity resources: limits: nvidia.com/gpu: 1 EOF执行:
kubectl apply -f gpu-holder.yaml等它:
Running后,再创建第二个同样请求:
nvidia.com/gpu: 1的 Pod。
因为这台服务器只有:
1 × RTX 3060第二个 Pod 会进入:
Pending查看:
kubectl describe pod <第二个Pod>通常会看到类似:
Insufficient nvidia.com/gpu这就非常直观地证明:
GPU 已经进入 Scheduler 的资源账本十八、为什么 GPU 被占用后Allocatable还是 1?
这里有一个容易误解的地方。
当一个 Pod 已经占用了 GPU 后:
kubectl describe node里面:
Allocatable: nvidia.com/gpu: 1通常不会变成:
0因为:
Capacity Allocatable描述的是:
这个 Node 的资源容量而不是:
此刻还剩多少未使用资源真正已经分配多少,可以在:
kubectl describe node下面的:
Allocated resources里观察。
所以:
Allocatable = 1和:
现在已经没有 GPU 可以再调度完全可以同时成立。
十九、nvidia.com/gpu和上一篇 CDI 的nvidia.com/gpu=0不是一回事
上一篇 Docker GPU 中,我们还看过 CDI:
nvidia.com/gpu=0这两个字符串长得很像:
Kubernetes: nvidia.com/gpu: 1 CDI: nvidia.com/gpu=0但意义完全不同。
Kubernetes:
nvidia.com/gpu: 1表示:
我要一个 GPU Resource而 CDI:
nvidia.com/gpu=0更接近:
具体选择 NVIDIA GPU 0 并描述怎样把这个设备注入 Container所以可以简单记:
Kubernetes Extended Resource ↓ 资源数量 / Scheduling CDI Device ↓ 具体设备 / Container Injection这两层不要混在一起。
二十、如果 Pod Pending,先看哪一层?
GPU Pod 出问题以后,最好先区分:
Scheduling和:
Runtime如果 Pod 一直:
Pending并出现:
Insufficient nvidia.com/gpu优先检查:
Device Plugin Node Resource Scheduler例如:
kubectl describe node里面有没有:
nvidia.com/gpu而如果 Pod:
已经 Scheduled 已经 Running但应用说:
CUDA unavailable则应该更多检查:
containerd NVIDIA Container Toolkit Runtime Device Injection CUDA Runtime这个区分非常重要:
Kubernetes 有没有分配 GPU和:
Container 能不能真正用 GPU是两个不同层级的问题。
二十一、现在整个 GPU Pod 链路终于完整了
从硬件往上:
RTX 3060 │ ▼ NVIDIA Driver │ ├───────────────┐ │ │ ▼ ▼ Device Plugin Container Toolkit │ │ ▼ ▼ kubelet containerd │ │ ▼ ▼ nvidia.com/gpu GPU Device │ ▲ ▼ │ Scheduler ───────► GPU Pod也可以从 Pod 的角度再看一次:
Pod YAML resources: limits: nvidia.com/gpu: 1 │ ▼ Scheduler │ ▼ GPU Node │ ▼ kubelet │ ▼ NVIDIA Device Plugin │ Allocate │ ▼ containerd │ ▼ NVIDIA Container Toolkit │ ▼ RTX 3060这就是:
Kubernetes 如何认识并最终使用一张 GPU。
二十二、这篇真正需要记住的几个概念
如果把整篇压缩,只需要记住:
Extended Resource让 Kubernetes 可以表示:
nvidia.com/gpu。
NVIDIA Device Plugin负责:
发现 GPU 向 kubelet 注册 上报 GPU 分配 GPUScheduler负责:
根据 nvidia.com/gpu 决定 Pod 去哪台 Node而:
NVIDIA Container Toolkit最终负责:
让 Container 真正访问 GPU所以一句话可以概括:
Device Plugin 让 Kubernetes “知道有 GPU”,Container Toolkit 让 Container “真正用到 GPU”。
二十三、下一篇为什么出现?
现在我们的单节点实验已经能够:
Kubernetes ↓ 识别 GPU ↓ nvidia.com/gpu ↓ 调度 GPU Pod ↓ 运行 CUDA看起来问题已经解决了。
但是注意一下这一系列操作。
我们手动做了:
安装 NVIDIA Driver ↓ 安装 NVIDIA Container Toolkit ↓ 配置 containerd ↓ 安装 NVIDIA Device Plugin ↓ 以后可能还要: GPU Feature Discovery MIG Monitoring 升级 版本管理一台实验服务器当然没问题。
但如果以后变成:
10 台 GPU Node 50 台 GPU Node 100 台 GPU Node难道还要:
SSH Node 1 安装 Driver SSH Node 2 安装 Driver SSH Node 3 配置 Toolkit SSH Node 4 更新 Device Plugin ...这时候新的问题已经不再是:
怎样让 Kubernetes 认识 GPU而是:
谁来管理整个 Kubernetes GPU Software Stack?
于是下一篇,我们第一次让:
RTX 3060变成:
nvidia.com/gpu: 1再看看:
当 GPU Node 越来越多, 这一整套东西 怎样从“手工安装” 走向“自动化运维”。