K8s到底需不需要GPU节点?集群资源分配的底层逻辑

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资源金贵,管不好白烧钱。管好了,一块卡能顶两块用。


有问题评论区聊。

相关推荐
极客先躯1 小时前
高级java每日一道面试题-2026年02月12日-实战篇[Docker]-什么是容器的 Seccomp 配置?如何自定义?
java·运维·分布式·docker·容器·自动化·文件
master3362 小时前
GitLab (Docker) 常用命令及解决方案清单
docker·容器·gitlab
卧室小白2 小时前
K8S基础-控制器&deploy&pod回滚更新&service
docker·容器·kubernetes
许彰午3 小时前
零基础无文档啃读纯实操摸索学会Docker全过程
运维·docker·容器
江湖有缘3 小时前
零门槛搭建个人微社区:Docker部署 Paopao-ce 完整教程
运维·docker·容器
OceanBase数据库官方博客3 小时前
OceanBase × Flink 数据集成系列——旁路导入连接器的批量写入能力
架构·kubernetes·oceanbase
Moshow郑锴3 小时前
Ubuntu26.04之Docker配置国内镜像加速器
云原生·eureka
Jooolin12 小时前
从 DeepSeek、Qwen 到 GPT:一次企业级 AI 知识库项目的模型选型复盘
人工智能·云原生·ai编程
皮皮蟹虾饺16 小时前
DNS协议指南:从报文格式到安全加密与 K8s 实战
安全·容器·kubernetes