首页 > 人工智能 > 人工智能 > K8s云原生-GPU 虚拟化切分与算力调度实战
如果一张 GPU 只能给一个 Pod 用,而那个 Pod 只跑到 5% 的利用率,剩下 95% 的算力就那么空转着。这篇记录一次完整的云原生 GPU 资源池化实践。 环境:
GPU 和 CPU 有一个本质区别—— 它不能被分时复用,至少默认情况下不能。
K8s 的 device plugin 把 GPU 注册成扩展资源
nvidia.com/gpu,而扩展资源有两条死规矩:
结果就是: 一张卡 = 一个 Pod 。第二个申请 GPU 的 Pod 直接 Pending,而第一个 Pod 可能只跑了 5% 的利用率。这就是为什么行业里 GPU 平均利用率长期在 20%–30%。切分的价值不在"省钱"这么简单,而在于把一张物理卡变成可申请、可计量、可调度的资源单位**。
它有一个必须在动手前确认的事实:
T4 不支持 MIG **。
nvidia-smi 里那列
MIG M. 显示
N/A 就是证据。MIG(Multi-Instance GPU)是 A100 / A30 / H100 / L40 这一代才有的硬件切分能力,能把一张卡切成最多 7 个
硬件隔离的实例——有独立的 SM、L2、显存通道,互不干扰。T4 没有这个能力,所以硬件切分这条路直接堵死。剩下的选项:
| 方案 | 隔离级别 | 成本 | 结论 |
|---|---|---|---|
| MIG | 硬件级 | 免费 | ❌ T4 不支持 |
| NVIDIA vGPU | 硬件辅助(需 vGPU Manager) | 需付费 License + 授权服务器 | ❌ 个人/小团队不现实 |
| Time-Slicing | 无隔离,纯时间片轮转 | 免费 | ⚠️ 只是让多个容器"能跑",谁超额谁拖垮全场 |
| MPS | 算力有隔离,显存无隔离 | 免费 | ⚠️ 一个进程 OOM 全炸 |
| HAMi | 显存 硬限制 + 算力 **软限速 | 免费开源( CNCF * Incubating)* | ✅ * 本次选型* |
为什么选 HAMi:它是开源方案里唯一同时管住 显存和 算力两个维度的,而且是真正挂在 K8s 调度器上的——不是"让容器能跑起来",而是"能按额度申请、能被调度、超额会被拒"。
一个判断 :如果你的目标是"算力调度"这四个字,K8s 就不是可选项,而是必需品。因为调度这件事的载体就是调度器,HAMi 本质上是 kube-scheduler 的一个扩展。如果你只想看"一张卡怎么被两个进程分显存",docker + NVIDIA Container Toolkit 二十分钟就够,不用上 K8s。
这一节的目标只有两件事: 宿主机 上有可用的 NVIDIA 驱动,集群里有可用的 K8s。下面按实际顺序记录每一步;如果你已经有现成的环境,可以直接跳到第四章。
① 基础环境:系统镜像与安装包

② 主机配置:网络、存储与节点角色

③ 部署 Kubernetes 集群(KubeKey)

④ 安装 NVIDIA 驱动与 CUDA Toolkit

⑤ 搭建 Harbor 私有镜像仓库(离线/内网环境必备)

⑥
nvidia-smi:确认驱动 580.126.20 与 CUDA 13.0

⑦ 节点资源概览:CPU 与内存

⑧ K8s 集群就绪


上面两张是集群部署完成的确认:节点 Ready、系统组件 Running。 至此环境就绪,后续所有操作都在 master * 上执行,不再需要登录 *GPU ** 节点手动装东西——这正是 GPU Operator 存在的意义。
| 项 | 值 |
|---|---|
| 节点 |
master(control-plane,2 核)+
node(GPU 节点,节点名就叫
node) |
| K8s | v1.35.6(Kt 部署,集群管理 KubeSphere) |
| 容器运行时 | containerd 2.2.6 |
| OS | Ubuntu 22.04.5 LTS,内核 5.15.0-181 |
| GPU | Tesla T4, 15360 MiB** |
| 驱动 | 580.126.20( 宿主机 已装),CUDA 13.0 |
| CNI | Calico |
GPU Operator v26.7.0 (driver.enabled=false, devicePlugin.enabled=false) + HAMi v2.10.0 (scheduler.kubeScheduler.image.tag=v1.35.6, devicePlugin.runtimeClassName=nvidia-legacy) + 节点标签 gpu=on + containerd 镜像加速
在 GPU 节点上:
nvidia-ctk --version grep -in nvidia /etc/containerd/config.toml kubectl get runtimeclass
我这台三条结果分别是:
command not found、无输出、
No resources found。
说明 toolkit 没装、containerd 没配 nvidia runtime —— GPU Operator 不能省,它就是来补这一层的。
反过来也成立:如果你的宿主机已经装好了驱动 + toolkit,且 containerd 默认 runtime 已经是 nvidia, 可以完全跳过 GPU ** Operator,直接打标签装 HAMi。
kubectl label nodes node gpu=on

这个标签是给 HAMi 的 device plugin DaemonSet 用的——它靠
gpu=on 选择要部署到哪些节点。不打标签,device plugin 会一直停在 0/0 的调度状态。
这是整个流程里最容易搞错的地方。这套环境的驱动是宿主机自带的 580.126.20,所以:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia helm repo update # 先看有哪些版本可选 helm search repo nvidia/gpu-operator --versions | head -10

正式安装(
--version 建议显式指定,避免不同时间拉到不同版本):
helm install gpu-operator -n gpu-operator --create-namespace \ nvidia/gpu-operator \--version=v26.7.0 \ --set driver.enabled=false \ --set devicePlugin.enabled=false
两个
false 各管一件事,缺一个都出事:
| 参数 | 为什么必须关 |
|---|---|
| driver.enabled=false | 别让 Operator 去装驱动,会和你云主机的 580.126.20 打架。你驱动已就绪,它不需要装 |
| devicePlugin.enabled=false | HAMi 自带 device plugin,两者都报 nvidia.com/gpu 会直接冲突,这是官方硬约束 |

helm repo add hami-charts https://project-hami.github.io/HAMi/
helm repo update
# 先确认 scheduler 参数的真实键名(2.9.0 是嵌套的 image.tag,不是 imageTag)
helm search repo hami-charts/hami --versions | head -10
helm show values hami-charts/hami --version 2.10.0 | grep -n -A8 kubeScheduler
# 渲染出全部镜像,看有哪些拉不到
helm template hami hami-charts/hami --version 2.10.0 \
--set scheduler.kubeScheduler.image.tag=v1.35.6 \
| grep -oE 'image: *[^ ]+' | awk '{print $2}' | tr -d '"' | sort -u
正式安装。
注意
*
**kubeScheduler.image.tag**
必须对齐你的 K8s 版本*,否则 scheduler 起不来:
helm install hami hami-charts/hami --version 2.10.0 \ -n hami-system --create-namespace \ --set scheduler.kubeScheduler.image.tag=v1.35.6


装完先看 device plugin 是否 Ready。多数情况下它会直接报错
Incompatible strategy detected auto(详见 6.6),这时必须显式指定 RuntimeClass:
helm upgrade hami hami-charts/hami -n hami-system --reuse-values \ --set devicePlugin.runtimeClassName=nvidia-legacy kubectl rollout restart daemonset/hami-device-plugin -n hami-system

kubectl get node node -o jsonpath='{.status.allocatable.nvidia\.com/gpu}{"\n"}'
kubectl describe nodes node

这是第一个「肉眼可见」的成功标志:
一张物理卡开始对外报 10 个可调度单位。
deviceSplitCount 默认 10,HAMi 的 device plugin 会把每张卡虚拟成 10 份上报给调度器。
如果这里
还是 1,说明 HAMi device plugin 没起来,或者它在和 GPU Operator 自带的 device plugin 抢着上报资源——回头检查 4.3 那两个
false。
# HAMi vGPU 切分验证:两个 Pod 共享同一张物理 T4 --- apiVersion: v1 kind: Pod metadata: name: gpu-slice-a spec: schedulerName: hami-scheduler restartPolicy: Never containers: - name: app image: nvidia/cuda:12.4.1-base-ubuntu22.04 command: ["sleep", "infinity"] resources: limits: nvidia.com/gpu: 1 # 1 个 vGPU 实例 nvidia.com/gpumem: 4000 # 显存配额 4000 MiB nvidia.com/gpucores: 30 # 算力配额 30% --- apiVersion: v1 kind: Pod metadata: name: gpu-slice-b spec: schedulerName: hami-scheduler restartPolicy: Never containers: - name: app image: nvidia/cuda:12.4.1-base-ubuntu22.04 command: ["sleep", "infinity"] resources: limits: nvidia.com/gpu: 1 nvidia.com/gpumem: 4000 nvidia.com/gpucores: 30
kubectl apply -f hami-vgpu-test.yaml

用同一份 YAML 起两个 Pod,各申请 1 个 vGPU、4000MiB 显存、30% 算力。起好之后按下面四层顺序验证——前三层只能证明「看起来隔离了」,第四层才真正证明「确实拦得住」。

两个 Pod 拿到的 GPU UUID 完全一致——它们确实挤在同一张物理 T4 上。注意注解里还带着各自的资源额度。

容器内的显存上限显示为 4096 MiB,而不是物理卡的 15360MiB——从这一刻起,它以为自己独占一张 4G 的卡。

HAMi 注入的两个关键环境变量:
CUDA_DEVICE_MEMORY_LIMIT_0=4000m(显存上限)与
CUDA_DEVICE_SM_LIMIT=30(算力上限),HAMi Core 就是照着这两个值做拦截的。
前三层证明的是「看起来隔离了」,这一层要证明的是「程序真的申请不到多余的显存」。
不需要 nvcc 和编译器,
base 镜像里自带
libcudart.so.12,装个 python3 用 ctypes 直接调
cudaMalloc,做一次逐档扫描:
kubectl exec gpu-slice-a -- bash -c 'apt-get update -qq && apt-get install -y -qq python3'
kubectl exec gpu-slice-a -- python3 -c '
import ctypes
c = ctypes.CDLL("libcudart.so.12")
for g in (2, 3, 3.8, 4.0, 4.5, 6):
p = ctypes.c_void_p()
err = c.cudaMalloc(ctypes.byref(p), ctypes.c_size_t(int(g*1024*1024*1024)))
print("cudaMalloc %.1f GiB -> errcode=%d %s" % (g, err, "OK" if err==0 else "OOM"))
if err == 0:
c.cudaFree(p)
'
实测输出:
[HAMI-core ERROR (pid:479 allocator.c:52)]: Device 0 OOM 4401922048 / 4194304000 [HAMI-core ERROR (pid:479 allocator.c:52)]: Device 0 OOM 4938792960 / 4194304000 cudaMalloc 2.0 GiB -> errcode=0 OK cudaMalloc 3.0 GiB -> errcode=0 OK cudaMalloc 3.8 GiB -> errcode=0 OK cudaMalloc 4.0 GiB -> errcode=2 OOM cudaMalloc 4.5 GiB -> errcode=2 OOM cudaMalloc 6.0 GiB -> errcode=2 OOM
分界线干净得不像话: 3.8GiB 通过,4.0GiB 拒绝。
更有意思的是把 HAMi 日志里的数字拆开算。日志格式是
Device 0 OOM <已用 + 本次请求> / <配额字节>:
| 申请量 | 请求字节 | 固定开销 | HAMi 报出的数 |
|---|---|---|---|
| 4.0 GiB | 4294967296 | +106954752 | 4401922048 ✅ 吻合 |
| 4.5 GiB | 4831838208 | +106954752 | 4938792960 ✅ 吻合 |
| 6.0 GiB | 6442450944 | +106954752 | 6549405696 ✅ 吻合 |
那个固定开销
106954752 字节 =
102 MiB
*,正是 *CUDA
** context 自身的常驻占用。它也被 HAMi 计进了「已用」——因为拦截点在
cuMemAlloc 这一层,创建 context 时的分配同样走这条路径。
于是边界可以被精确算出来,判定式是
102 MiB + 本次请求 > 4000 MiB:
| 申请 | 折算 MiB | 加 102MiB 后 | 余量 | 结果 |
|---|---|---|---|---|
| 3.8 GiB | 3891.2 | 3993.2 | +6.8 MiB** | OK(惊险通过) |
| 4.0 GiB | 4096 | 4198 | −198 MiB | OOM |
一个反直觉但很实用的结论 :申请 4000MiB 的配额,用户实际能 malloc 到的大约是 3898 MiB(≈3.807 GiB) ,不是名义的 4000MiB。给别人配配额时记得留这 102MiB 的余量。
顺带解答一个疑点:OOM 日志打印在结果
之前,不是时序错乱——HAMi-core 写的是 stderr(无缓冲),
print 写的是 stdout(有缓冲,进程退出时才 flush)。
决定性的一击:6GiB = 6144MiB,而
物理 T4 有 15360MiB。没有 HAMi 的话闭着眼睛都能分到,这里却返回
errcode=2。此刻物理卡几乎全空,却分配失败——这一条就证明限制来自 HAMi,而不是硬件。
部署本身只有几条命令,真正耗时间的是下面这些。每一条都按「现象 → 根因 → 解法」记,顺序基本就是实际踩中的顺序。
现象:GPU Operator 主控 Pod 已经 Running,但
node-feature-discovery 全家
ImagePullBackOff。
为什么它是
死结:GPU Operator 的部署链路是「NFD 检测到 NVIDIA 硬件 → 打上
nvidia.com/gpu.present 标签 → 各组件 DaemonSet 靠这个标签选节点部署」。NFD 挂掉,标签就不存在,
nvidia-container-toolkit-daemonset
根本不会被创建,containerd 里永远没有 nvidia runtime。
迷惑点在于:
kubectl get pods -n gpu-operator 里
完全看不到 toolkit daemonset —— 不是它还在启动,是压根没被创建。
根因:
registry.k8s.io 不可达,报错打到
europe-west3-docker.pkg.dev 超时。
containerd 的镜像加速机制是
/etc/containerd/certs.d/<registry>/hosts.toml:
server = "https://registry.k8s.io" [host."https://k8s.tencentcloudcr.com"] capabilities = ["pull", "resolve"]
这个文件是热加载的,写完即生效,连 containerd 都不用
重启——它就是 containerd 版的
daemon.json。多出来的只有一步:要在
config.toml 里用
config_path 告诉 containerd「去这个目录找 hosts.toml」。
问题就出在这一步。
第一次配置:文件写对了、语法合法、containerd 正常启动,但 mirror 就是不用,报错和配之前一字不差。根因是版本键不匹配:
| containerd 版本 | 配置键 |
|---|---|
1.x(
version = 2) |
[plugins."io.containerd.grpc.v1.cri".registry] |
2.x(
version = 3) |
[plugins."io.containerd.cri.v1.images".registry] |
我的 config.toml 是 1.x 格式,加的却是 2.x 的键。containerd 官方迁移文档说得很难听:硬编码旧插件路径的配置文件「 will not apply the settings you think it does」,而最先崩的「 就是配了自定义 registry mirror 的集群」。
判断生效与否,唯一可靠的手段是
**<font style="background-color:rgb(240,251,239);">containerd config dump</font>**
——它输出的是合并后的
生效值
,不是文件里写了什么。
<font style="background-color:rgb(240,251,239);">containerd config dump | grep -nE 'config_path|sandbox'</font>
同类小坑还有三个:
config_path 写成
冒号分隔的多路径会被静默忽略(
containerd config default 生成的恰恰就是
/etc/containerd/certs.d:/etc/docker/certs.d),必须是单路径。/etc/containerd/conf.d/99-nvidia.toml 写覆盖配置并通过
imports 架空主配置——
装完 GPU Operator 必须复查一次。现象:第二次跑配置脚本后 containerd 启动失败,节点直接掉线。
根因:TOML 不允许同一张表定义两次,而脚本因为正则在复制粘贴过程中失真,每次都判定「段不存在」并追加一遍,于是文件里出现了两个一模一样的
[plugins...registry] 表头。
两条教训:
**\[**
**\]**
**\.**
这类反斜杠转义*——聊天窗口 → 剪贴板 → 终端这条链路会吞掉反斜杠。需要匹配特殊字符就用
line.strip() == "..." 这种纯字符串比较。恢复只要一条:用脚本自动备份的
config.toml.bak.<时间戳> 覆盖回去,再重启 containerd。
现象:toolkit 配好 nvidia runtime 之后,Pod 卡在
failed to get sandbox image "registry.k8s.io/pause:3.10.1"。
镜像拉取其实分三层, helm 只能管到第一层:
| 层 | 谁能控制 | 举例 |
|---|---|---|
| A. chart manifest 里的容器镜像 | helm values /
--post-renderer |
nvcr.io/nvidia/k8s/... |
| B. pause / sandbox_image | 只有 containerd config.toml 能改,helm 完全管不到 |
registry.k8s.io/pause:3.10.1 |
| C. 后续未预见的镜像 | 只有 containerd 层 mirror 能兜 | 后装的 HAMi、Prometheus 等 |
所以
只改 helm 永远解决不了 B 层。要么把
sandbox_image 指向可达地址,要么老实把 containerd 的 mirror 配好。
Incompatible strategy detected auto
现象:
hami-device-plugin 0/2 Error,
hami-scheduler 正常。
factory.go:135] Incompatible strategy detected auto factory.go:136] If this is a GPU node, did you configure the NVIDIA Container Toolkit? main.go:201] error starting plugins: ... invalid device discovery strategy
那句「did you configure the NVIDIA Container Toolkit?」是 误导性的——toolkit 明明是 Running 的。
真正根因(HAMi 官方排障手册原文):从 GPU Operator 25.10.0 起 CDI 默认开启,Operator
不再把
*
**nvidia**
设为默认 runtime
;通过
NVIDIA_VISIBLE_DEVICES 访问 GPU 的管理容器
必须显式使用
runtimeClassName: nvidia,而 HAMi Device Plugin 就属于此类管理容器*。
我装的是 v26.7.0,正是 CDI 模式。
runc 仍是 containerd 默认 runtime → HAMi device plugin 没走 NVIDIA runtime hook → 容器里没有驱动库、没有
/dev/nvidia* → NVML 初始化失败。
helm upgrade hami hami-charts/hami -n hami-system --reuse-values \ --set devicePlugin.runtimeClassName=nvidia kubectl rollout restart daemonset/hami-device-plugin -n hami-system
unresolvable CDI devices上一节的
nvidia RuntimeClass 救了 device plugin,却
把问题转移给了业务 Pod**:
failed to inject CDI devices: unresolvable CDI devices management.nvidia.com/gpu=GPU-fb0031a8-bcd7-e7b6-1fd9-5c81d4d627f9
原因写在 HAMi 官方手册里:
「HAMi chart 会把 _
_devicePlugin.runtimeClassName_ 同时应用到 Device Plugin
*和被 HAMi scheduler 改写的业务负载*」_。也就是说业务 Pod 也被注入了同一个 RuntimeClass。
而这个
nvidia RuntimeClass 在 GPU Operator 26.7 下走 CDI 注入,去找
management.nvidia.com/gpu=<UUID> 这个 CDI 设备类。上节点一查:
ls -l /etc/cdi/ /var/run/cdi/ # 两个目录都不存在
节点上压根没生成任何 CDI spec**,注入必然失败。
修法:换成
nvidia-legacy(这也正是 4.4 里那次 upgrade 做的事)。它走老的 nvidia-container-runtime hook +
NVIDIA_VISIBLE_DEVICES 环境变量注入,
完全不依赖 CDI,而 HAMi 默认的
deviceListStrategy 恰好就是
envvar,天生匹配。
备选方案是关掉 GPU Operator 的 CDI(
kubectl patch clusterpolicies.nvidia.com/cluster-policy ... /spec/cdi/enabled=false),但会触发 Operator 重新配置 containerd,影响面大得多。
这是本次
最意外的发现。起了 8 个
gpucores: 5 的 Pod,只有 4 个 Running,其余全 Pending,报
1 1/1 CardInsufficientCore。算笔账:
| Pod | 算力 | 状态 |
|---|---|---|
| gpu-slice-a | 30% | Running |
| gpu-slice-b | 30% | Running |
| gpu-oom-test | 20% | ImagePullBackOff,但配额已占 |
| gpu-fill-3 ~ 6 | 5% × 4 = 20% | Running |
| 小计 | 100% | 卡满 |
100%,一分不差。 那个 ImagePullBackOff 的 Pod 容器根本没起来,但 HAMi 在 Bind 阶段就已经把配额扣给它了。
运维要点 :HAMi 环境下遇到「明明没几个 Pod 在跑,为什么新 Pod 调度不进去」,第一个要看的就是 有没有卡住的 Pod 。Pending / ImagePullBackOff / CrashLoop 状态的 Pod 会一直占着 GPU 配额不释放,删掉即释放。
| 组件 | 位置 | 职责 |
|---|---|---|
| Mutating Webhook | Master | 拦截 Pod 创建,识别 GPU 请求,把
schedulerName 改成
hami-scheduler,补全资源声明 |
| HAMi Scheduler | Master | 挂成 kube-scheduler 的 extender。 Filter 判断有没有满足显存 / 算力的卡; Score 决定 binpack 还是 spread; Bind 把「用哪张卡、多少显存、多少算力」写回 Pod 注解 |
| Device Plugin | GPU 节点 DaemonSet | 把 1 张物理卡虚拟成 N 个可调度单位,并把每张卡的明细写进节点注解 |
| HAMi Core | 业务容器内 | 真正执行隔离。
*它不是进程,是挂进容器的 *
**libvgpu.so** |
关键在于: 调度阶段和运行阶段是两件事。Scheduler 决定「分配多少」,HAMi Core 负责「真的只能用到这么多」。前者不管执行,后者不参与决策——这种解耦也是它能兼容原生 K8s 的原因。
libvgpu.so 通过 Linux 的
/etc/ld.so.preload 加载——动态链接器在任何进程启动时都会优先加载它,效果等同
LD_PRELOAD,但对容器内
所有进程透明生效,既不用改环境变量,也不用改业务代码。
加载之后只做一件事:
重写
*
**dlsym**
,劫持所有以
**cu** 和
**nvml** 开头的函数符号*。所有 CUDA 调用都得先过它这一关。
| 动作 | 拦截目标 | 作用 |
|---|---|---|
| 改上报值 |
nvmlDeviceGetMemoryInfo /
_v2 |
让
nvidia-smi 只看到配额——
骗眼睛 |
| 拦分配 |
cuMemAlloc_v2 /
cuMemAllocManaged |
分配前算「已用 + 本次 > 配额?」,超了返回
CUDA_ERROR_OUT_OF_MEMORY——
真拦路 |
只做第一条,程序照样能把显存分配爆;只做第二条,
nvidia-smi 显示的数字会误导业务代码自己算余量。
两条合起来才有效,这也是为什么 L2(看得见)和 L4(拦得住)要分开验证。
g_cur_cuda_cores,初值 = 配额(30% → 30 个 token)cuLaun 提交时,按本次 kernel 的
grid
* 数量*扣 tokennanosleep),这就是「限速」真正发生的时刻utilization watcher 线程采样真实 GPU 利用率,用
delta() 动态把 token 补回来这套方案好用,但不能吹过头。上线前必须知道它的边界在哪:
算力 隔离是「软限速」,不是硬隔离。
<font style="background-color:rgb(255,245,235);">/dev/nvidia*</font>
、或设置
<font style="background-color:rgb(255,245,235);">CUDA_DISABLE_CONTROL=true</font>
。显存 隔离也不是完美的:如实测所见,CUDA context 的 102MiB 会被计入配额,实际可用略小于名义值,配配额时要留余量。
另外 HAMi 管不了 PCIe * 带宽、 NVLink* 带宽以及 **显存 带宽的争抢——同一张卡上的邻居如果疯狂刷显存带宽,你的实际性能仍然会掉。这是所有软件切分方案的共同短板,MIG 之所以强,就在于它连这些也一起隔了。
# 1. GPU Operator:关掉 driver 和 devicePlugin,只借它的 toolkit helm install gpu-operator -n gpu-operator --create-namespace nvidia/gpu-operator \ --version=v26.7.0 \ --set driver.enabled=false \ --set devicePlugin.enabled=false # 2. 节点标签 kubectl label nodes <gpu-node> gpu=on # 3. HAMi:scheduler 镜像 tag 必须对齐 K8s 版本 helm install hami hami-charts/hami --version 2.10.0 \ -n hami-system --create-namespace \ --set scheduler.kubeScheduler.image.tag=<你的K8s版本> \ --set devicePlugin.runtimeClassName=nvidia-legacy
| 对比项 | 原生 K8s | 加 HAMi 之后 |
|---|---|---|
| 一张 T4 能给几个 Pod | 1 | 9(实测) |
| 调度依据 | 整数槽位 | 显存 + 算力 双维度连续量 |
| 超额请求 | 起来之后 OOM | 调度阶段直接拒绝 |
| 容器可见显存 | 全量 15360MiB | 各自配额(4000MiB) |
| 邻居 OOM 的影响 | — | 互不影响(实测) |
nvidia-ctk --version 和
kubectl get runtimeclass 两条命令,决定了 GPU Operator 是必需品还是多余的负担。containerd config dump 这一条原则,能省掉大量「明明配了却没用」的困惑。# 节点上 GPU 的真实用量明细(调度器 Filter 读的就是这份)
kubectl get node <node> -o jsonpath='{.metadata.annotations.hami\.io/node-nvidia-register}'
# 可调度槽位数(应为 物理卡数 × deviceSplitCount)
kubectl get node <node> -o jsonpath='{.status.allocatable.nvidia\.com/gpu}'
# 某个 Pod 被分到了哪张卡、多少额度
kubectl get pod <pod> -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}'
# 容器内配额视角
kubectl exec <pod> -- nvidia-smi
kubectl exec <pod> -- env | grep CUDA_DEVICE
# containerd 生效配置(不是文件内容)
containerd config dump | grep -nE 'config_path|sandbox'
# RuntimeClass 现状
kubectl get runtimeclass
# 手动预拉镜像(绕开 kubelet 的 pull deadline)
ctr -n k8s.io images pull --hosts-dir=/etc/containerd/certs.d <image>
如果这篇对你有帮助,欢迎转发给同样在折腾 GPU 利用率的朋友。有问题可以在评论区交流,踩到新的坑我也会继续补充进来。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050990/viewspace-3182599/ ,如需转载,请注明出处,否则将追究法律责任。