☰
从零搭建一个 AI Infra 实验室⑭:Kubernetes 如何认识一张 GPU——Extended Resource、NVIDIA Device Plugin 与 nvidia.com/gpu
2026/10/10 2:47:47 网站建设 项目流程

上一篇,我们已经把一台普通 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 Resource

NVIDIA 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 3

Device Plugin 可能向 Kubernetes 报告:

nvidia.com/gpu: 4

那么:

nvidia.com/gpu: 1

表示:

我要其中一张 GPU

至于最后具体分到:

GPU 0 还是 GPU 2

不是 Pod YAML 自己决定的。


四、那是谁创建了nvidia.com/gpu?

答案就是:

NVIDIA Device Plugin

Kubernetes 提供了一套:

Device Plugin Framework

厂商可以实现自己的 Plugin。

NVIDIA 官方的:

nvidia-device-plugin

就是其中一个实现。

它通常以:

DaemonSet

运行。

也就是说:

每个 GPU Node │ ▼ 运行一个 NVIDIA Device Plugin Pod

Device Plugin 的主要工作包括:

发现 GPU ↓ 检查设备状态 ↓ 向 kubelet 注册 ↓ 报告 GPU 数量 ↓ Pod 申请 GPU 时完成设备分配

NVIDIA 官方也把它定义为一个 DaemonSet,用于暴露各 Node 的 GPU 数量、跟踪设备健康状态,并支持 GPU Container。


五、Device Plugin 到底向谁报告 GPU?

不是直接找:

Scheduler

也不是直接找:

API Server

而是:

NVIDIA Device Plugin │ ▼ kubelet

Device Plugin 会通过 kubelet 提供的 Device Plugin API 注册自己。

大致可以理解成:

RTX 3060 │ ▼ NVIDIA Driver / NVML │ ▼ NVIDIA Device Plugin │ │ │ Register │ ListAndWatch ▼ kubelet │ ▼ Node Status │ ▼ API Server │ ▼ Scheduler

Device Plugin 注册时会告诉 kubelet:

ResourceName = nvidia.com/gpu

随后继续报告:

有哪些设备 哪些设备 Healthy 哪些设备 Unhealthy

kubelet 再把这些资源写入:

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 Core

Scheduler 真正看到的更接近:

Node A cpu: 6 memory: 32Gi nvidia.com/gpu: 1

Pod 请求:

nvidia.com/gpu: 1

Scheduler 做的是:

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 nodes

Node 是:

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 containerd

NVIDIA 当前 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 ↓ 告诉本机 kubelet

DaemonSet 正好适合这种:

每个 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/gpu

Device 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 分配 GPU
Scheduler

负责:

根据 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 越来越多, 这一整套东西 怎样从“手工安装” 走向“自动化运维”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询