K8s到底需不需要GPU节点?集群资源分配的底层逻辑
摘要:很多团队上K8s后第一件事就是往集群里加GPU节点,然后发现利用率很低、调度有问题、GPU被低负载Pod占满。本文从K8s资源分配的底层机制讲起,搞清楚K8s怎么管理GPU、什么时候该加、以及常见的调度问题和解法。
关键词:Kubernetes、GPU调度、资源分配、nvidia-device-plugin
分类:K8s / AI / 运维
先说结论
大部分K8s集群不需要GPU节点。
你可能觉得AI时代了GPU不是标配吗?但想想你的K8s集群里跑的是什么------Web服务、API网关、消息队列、数据库代理------这些东西CPU跑得好好的,加GPU没意义。
GPU节点的硬件成本是普通节点的5-10倍,运维也复杂不少。没想清楚就加,大概率的结果是GPU节点空转烧钱。
真正需要GPU节点的场景:AI推理、模型训练、图像处理、视频转码这类计算密集型任务。
K8s资源分配的基本逻辑
搞清楚K8s怎么管CPU和内存,才能理解GPU是怎么插进来的。
CPU和内存的本质区别
CPU是可压缩资源。 多个Pod争抢CPU的时候,K8s可以限流------给你少分点CPU时间片,Pod变慢但不会被杀。
内存是不可压缩资源。 内存用完就是用完了,没法"限流"。Pod超了limit直接被OOMKill,进程被内核杀掉,没有商量余地。
yaml
resources:
requests:
cpu: "500m" # 调度依据:至少要0.5核
memory: "256Mi" # 调度依据:至少要256Mi
limits:
cpu: "1000m" # 运行时上限:最多用1核
memory: "512Mi" # 运行时上限:超了就杀
这个区别直接影响策略。CPU可以超分------8核节点可以跑20个request=500m的Pod,大部分时间用不满,超分是安全的。内存不能超分------request了多少就要预留多少,8G内存的节点跑不了太多大内存Pod。
bash
kubectl describe node 节点名 | grep -A 10 "Allocated resources"
Allocated resources:
Resource Requests Limits
-------- -------- ------
cpu 3500m (43%) 6000m (75%)
memory 12Gi (75%) 18Gi (112%)
requests已经75%了,再加Pod可能调度不进去------即使实际内存使用率才50%。这是K8s资源管理里最常见的"明明有空余但调度不进去"的原因。
requests和limits别搞混
requests:调度依据。K8s根据它决定Pod放哪个节点。
节点可分配 = 总资源 - 所有Pod的requests之和
limits:运行时上限。Pod实际能用的天花板。
CPU超了→限流
内存超了→直接杀
设太高:节点有空余但调度器认为满了,新Pod进不来。设太低:Pod实际需要的资源超了,多个Pod同时OOM。
一个典型的Java服务配置:
yaml
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "2Gi"
requests是保底------至少能拿到0.5核和1G内存。limits是天花板------最多用到2核和2G。
GPU在K8s里怎么工作的
GPU跟CPU、内存都不一样。它是不可压缩的扩展资源。
不可压缩:跟内存一样,用满了不能限流,要么给整卡要么不给。
扩展资源:K8s核心调度器本身不认识GPU,需要通过Device Plugin机制"注册"进去。
nvidia-device-plugin
NVIDIA提供了一个Device Plugin,部署之后K8s就能识别GPU。
bash
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml
部署成功后有GPU的节点会多出可分配资源:
bash
kubectl describe node gpu-node-1 | grep -A 5 "Capacity"
Capacity:
cpu: 32
memory: 128Gi
nvidia.com/gpu: 8
Pod请求GPU:
yaml
resources:
limits:
nvidia.com/gpu: 1 # 要1块GPU
注意这里只有limits没有requests。GPU的特殊之处:requests和limits必须相等,不能只要"0.5块"。 GPU不支持分片(NVIDIA的MIG模式除外,后面会提到)。
调度逻辑
K8s调度GPU的逻辑很朴素:一块GPU只能分给一个Pod。
节点8块GPU
Pod A要1块 → 分配GPU:0
Pod B要1块 → 分配GPU:1
Pod C要4块 → 分配GPU:2,3,4,5
剩2块
Pod D要3块 → 调度失败,只剩2块了
GPU不能超分。8块卡最多同时服务8个Pod各1块。用完就没了。
这就是GPU利用率低的根源:Pod只用了一点GPU算力(比如10%),但这块卡的剩余90%也分不出去了。
bash
nvidia-smi
+-----------------------------------------------------------------------------+
| GPU Name | GPU-Util | Memory-Usage |
| 0 A100-SXM-80G | 8% | 512Mi / 81920Mi |
| 1 A100-SXM-80G | 0% | 0Mi / 81920Mi |
+-----------------------------------------------------------------------------+
GPU 0用了8%,GPU 1用了0%。两块卡都被分配出去了但基本没干活。K8s认为它们"已占用",其他Pod用不了。
什么时候该加GPU节点
不需要的
纯Web/API服务 → CPU足够
数据库/缓存/消息队列 → CPU+内存+磁盘IO
微服务网关/负载均衡 → CPU足够
CI/CD构建 → 偶发GPU需求用云GPU临时开
这些场景瓶颈在网络IO或磁盘IO,加GPU不会有任何提升。
需要的
AI推理服务 → 需要GPU,K8s调度有价值(多推理服务共享GPU)
模型训练 → 需要GPU,通常独占节点
图像/视频处理 → 看具体任务
决策思路
要跑AI任务吗?
├── 不跑 → 不需要GPU节点
└── 跑
├── 训练还是推理?
│ ├── 训练 → 独占GPU节点,长时间任务
│ └── 推理 → GPU节点 + 共享调度,按需分配
└── 推理QPS大吗?
├── 不大(<10) → 1-2个GPU节点够了
└── 大(>100) → GPU节点池 + 自动扩缩
一个容易忽略的点:训练任务用K8s调度的优势其实不大。训练通常是长时间独占GPU,跑完释放。用K8s调度反而多了不少开销。很多团队的训练任务直接跑在裸机或者Slurm集群上,K8s只管推理服务。
几个常见的GPU调度问题
问题1:GPU碎片
8卡节点,3个Pod各要2块,用掉6块。剩2块。一个要3块的Pod进不来------明明有剩余但用不了。
解法:
用卡多的节点。8卡比4卡碎片率低,4卡比2卡低。
Pod的GPU请求量尽量统一。都请求1块的话碎片基本不存在。
按任务类型分节点池。大任务(要4块以上)和小任务(要1块)放在不同的节点池里,互不影响。
yaml
# 大任务节点池
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values: ["a100-80g"]
问题2:GPU被低负载Pod占满
最常见的浪费。一个推理服务GPU利用率10%,但独占了一块卡。8块卡全被这种Pod占了,真正需要GPU的任务反而排不上。
解法一:GPU共享。
K8s原生的nvidia-device-plugin把GPU当作不可分割的设备,一块卡只能给一个Pod。要共享需要额外工具。
HAMI(原k8s-vGPU-scheduler)支持按显存和算力百分比分配GPU:
yaml
resources:
limits:
nvidia.com/gpumem: 2000 # 要2GB显存
nvidia.com/gpucores: 30 # 要30%算力
一块80GB的A100可以同时服务4个各需要20GB显存的推理Pod。
NVIDIA的GPU Operator也支持时间片模式------多个Pod轮转使用同一块GPU。适合推理这种短时请求的场景。
解法二:缩副本。
如果每个Pod的GPU利用率都很低,减少Pod数量。8个Pod各占1块卡但利用率10%,不如缩到2个Pod各占1块卡,利用率40%。剩下的6块卡空出来给其他任务。
问题3:训练和推理抢资源
同一个集群里既有训练又有推理。训练任务跑几个小时把GPU全占了,推理服务排不上。
解法:节点池物理隔离。
yaml
# 推理节点池
nodeSelector:
node-role: inference
# 训练节点池
nodeSelector:
node-role: training
物理隔离最干净。GPU资源不够做不了物理隔离的话,用ResourceQuota做逻辑隔离:
yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: training-quota
namespace: training
spec:
hard:
limits.nvidia.com/gpu: "4"
训练命名空间最多用4块GPU,不会把推理的资源抢光。
但逻辑隔离有局限:训练Pod运行期间GPU被占住,推理Pod还是要等训练Pod结束或者被驱逐才能拿到资源。物理隔离没有这个问题。
问题4:GPU碎片导致大Pod调度失败
Kueue可以帮忙。Kueue是K8s的批处理调度组件,GPU资源不足时Pod进入队列等待而不是直接调度失败:
yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: inference-queue
spec:
clusterQueue: gpu-cluster-queue
配合优先级设置,高优先级任务可以抢占低优先级任务的资源。
GPU节点的运维
驱动和CUDA版本
GPU节点运维比普通节点麻烦。驱动版本、CUDA版本、容器运行时,三者要匹配。
bash
nvidia-smi | head -3
# Driver Version: 535.129.03
# CUDA Version: 12.2
驱动版本不匹配是最常见的GPU故障。升级驱动后CUDA版本变了,依赖特定CUDA版本的应用可能起不来。
建议用NVIDIA GPU Operator统一管理。它自动处理驱动、CUDA、容器运行时的版本匹配,省掉手动维护的麻烦。
GPU健康检查
GPU卡故障率比CPU高。ECC错误、显存坏块、掉卡------长时间高负载下不罕见。
bash
# ECC错误检查
nvidia-smi -q | grep -A 3 "ECC Errors"
# Uncorrectable Errors: 0 ← 非0就有问题
# 掉卡检查
nvidia-smi -L
# 列出来的GPU数量应该跟物理安装数一致
用dcgm-exporter把GPU指标暴露到Prometheus,配上告警:
yaml
- alert: GPUECCError
expr: DCGM_FI_DEV_ECC_CURRENT > 0
for: 5m
annotations:
summary: "GPU ECC错误,可能需要换卡"
不监控GPU健康状态的话,卡出了问题你可能根本不知道------Pod还在跑但结果已经不对了。
节点维护
GPU节点需要维护的时候(驱动升级、硬件更换),要优雅驱逐Pod:
bash
kubectl cordon gpu-node-1 # 标记不可调度
kubectl drain gpu-node-1 --ignore-daemonsets --delete-emptydir-data # 驱逐Pod
# ...维护...
kubectl uncordon gpu-node-1 # 恢复
注意:如果训练任务在跑,drain会中断。确保任务支持checkpoint恢复,或者等训练完了再维护。
一个完整的GPU集群参考
一个做AI推理的团队的实际架构:
控制面(3节点):
etcd + apiserver + scheduler + controller-manager
CPU节点池(Web/API/中间件):
4台 8核32G
跑:Web服务、API网关、Redis、MySQL代理、Prometheus
GPU推理节点池:
2台 8×A100 80GB
跑:模型推理服务
GPU Operator + HAMI(GPU共享调度)
扩缩容:
CPU节点:HPA + Cluster Autoscaler
GPU节点:基于GPU利用率手动扩缩
GPU节点不建议全自动扩缩。GPU实例的启动比CPU节点慢得多------操作系统启动、驱动加载、模型加载,加起来可能要几分钟到十几分钟。等新节点ready了流量高峰可能已经过了。
yaml
# GPU推理服务的HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: inference-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: inference-service
minReplicas: 2
maxReplicas: 16
metrics:
- type: Pods
pods:
metric:
name: gpu_utilization
target:
type: AverageValue
averageValue: "70"
总结
| 场景 | 需要GPU节点吗 | 建议 |
|---|---|---|
| 纯Web/API | 不需要 | CPU节点就够了 |
| AI推理 | 需要 | GPU共享调度,提高利用率 |
| 模型训练 | 需要 | 独占节点或节点池隔离 |
| 图像/视频 | 看具体任务 | 轻量的CPU也能做 |
| CI/CD | 不需要 | 偶发需求用云GPU |
几条建议:
没有GPU任务就别加GPU节点。成本和复杂度都上去了,没有任何收益。
推理一定要做资源共享。10%利用率独占一块卡是最常见的浪费。
训练和推理物理隔离。逻辑隔离(ResourceQuota)能防住大部分场景,但训练Pod运行期间GPU就是被占住了,推理Pod还是要等。
GPU健康监控不能少。ECC错误、掉卡------不监控出了问题你都不知道。
驱动和CUDA版本管好。一次不匹配的升级可能让整个推理服务起不来。
GPU资源金贵,管不好白烧钱。管好了,一块卡能顶两块用。
有问题评论区聊。