跳过正文
Kubernetes GPU 调度实战:AI 训练与推理基础设施

Kubernetes GPU 调度实战:AI 训练与推理基础设施

·1926 字·10 分钟·
目录

一张 A100 每月云上租用成本超过 2000 美元,但我们接手过的集群里,GPU 利用率常年不到 30%。Kubernetes 给了管理 GPU 的基础框架,但默认配置远远不够——驱动、device plugin、MIG、监控、调度策略每一层都有坑。这篇把我在这套栈上踩过的东西写下来。

K8s GPU 支持架构
#

整体技术栈
#

应用层:PyTorch / TensorFlow / Triton Inference Server
   ↕
K8s 调度层:Device Plugin + 资源请求
   ↕
运行时层:nvidia-container-toolkit(容器化 GPU 访问)
   ↕
驱动层:NVIDIA Driver + CUDA
   ↕
硬件层:GPU(A100/H100/V100/T4 等)

理解这个分层结构很重要。上层出问题,优先看资源请求和 Device Plugin;下层出问题,优先看驱动和容器运行时。

NVIDIA Device Plugin
#

Device Plugin 是 K8s 的扩展机制,允许第三方硬件厂商将设备资源(如 GPU)暴露给 K8s 调度器。NVIDIA Device Plugin 以 DaemonSet 形式运行在每个 GPU 节点上:

  • 定期向 kubelet 上报本节点 GPU 数量
  • 在 Pod 调度时,将 GPU 设备文件(/dev/nvidia*)挂载到容器
  • 管理 GPU 设备的分配和释放

安装 NVIDIA Device Plugin:

kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

或通过 Helm(推荐,可定制配置):

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update

helm install nvdp nvdp/nvidia-device-plugin \
  --namespace nvidia-device-plugin \
  --create-namespace \
  --version 0.14.5 \
  --set failOnInitError=false \
  --set compatWithCPUManager=true

安装成功后,节点会出现 nvidia.com/gpu 资源:

kubectl describe node gpu-node-1 | grep nvidia
# Capacity:
#   nvidia.com/gpu: 8
# Allocatable:
#   nvidia.com/gpu: 8

GPU Operator:一站式 GPU 管理
#

NVIDIA GPU Operator 是更完整的解决方案,自动管理 GPU 驱动、Device Plugin、容器运行时、DCGM 监控等所有组件的安装和升级:

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator \
  --create-namespace \
  --set driver.enabled=true \
  --set driver.version="550.54.15" \
  --set toolkit.enabled=true \
  --set devicePlugin.enabled=true \
  --set dcgm.enabled=true \
  --set dcgmExporter.enabled=true \
  --set mig.strategy=mixed

GPU Operator 的核心优势:节点不需要预装驱动,Operator 会自动探测 GPU 型号,下载并安装对应驱动。这对弹性伸缩场景(新节点自动加入)非常重要。

nvidia-container-toolkit
#

这是容器运行时层的关键组件,负责让容器内的进程能访问 GPU 硬件。它通过修改 OCI Runtime Spec,在容器启动时注入 NVIDIA 相关的设备文件和库路径。

安装后,containerd 的配置需要更新:

# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
  runtime_type = "io.containerd.runc.v2"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
    BinaryName = "/usr/bin/nvidia-container-runtime"

GPU 节点配置
#

驱动版本管理
#

NVIDIA 驱动有两个版本分支:

  • 生产分支(Production Branch):稳定性优先,适合生产环境。如 550.x。
  • 新功能分支(New Feature Branch):包含最新特性,变更频繁。

CUDA 与驱动的兼容性遵循最低版本要求,但向上兼容:驱动 550 可以运行 CUDA 12.x 编译的程序,也可以运行 CUDA 11.x 的程序。因此推荐使用最新稳定驱动,镜像内可以使用不同 CUDA 版本。

查看驱动与 CUDA 版本:

# 在 GPU 节点上
nvidia-smi

# 输出示例:
# Driver Version: 550.54.15   CUDA Version: 12.4
# GPU 0: NVIDIA A100 80GB PCIe  
# Memory-Usage: 0MiB / 81920MiB

节点标签与污点
#

GPU 节点应该打上标签,用于精确调度:

# 按 GPU 型号打标签
kubectl label node gpu-node-1 nvidia.com/gpu.product=A100-SXM4-80GB
kubectl label node gpu-node-2 nvidia.com/gpu.product=T4

# 按用途区分训练/推理节点
kubectl label node gpu-node-1 workload=training
kubectl label node gpu-node-2 workload=inference

# 添加污点,防止普通 Pod 占用 GPU 节点资源
kubectl taint node gpu-node-1 nvidia.com/gpu=present:NoSchedule

GPU Operator 会自动添加 nvidia.com/gpu.productnvidia.com/gpu.memory 等标签,非常方便。

MIG:多实例 GPU
#

MIG(Multi-Instance GPU)是 NVIDIA A100/H100 的特性,允许将一张 GPU 物理切分为多个独立实例,每个实例有独立的显存和计算资源,互不干扰。

A100 80GB 的 MIG 切分选项:

ProfileGPU 实例显存SM 切片
1g.10gb7 个10GB1/7
2g.20gb3 个20GB2/7
3g.40gb2 个40GB3/7
7g.80gb1 个80GB7/7(整卡)

启用 MIG 并配置切分:

# 在节点上启用 MIG 模式
nvidia-smi -mig 1

# 创建 GPU 实例(切分为 7 个 1g.10gb)
nvidia-smi mig -cgi 1g.10gb,1g.10gb,1g.10gb,1g.10gb,1g.10gb,1g.10gb,1g.10gb -C

在 K8s 中使用 MIG 实例,需要配置 GPU Operator 的 MIG Manager:

apiVersion: v1
kind: ConfigMap
metadata:
  name: default-mig-parted-config
  namespace: gpu-operator
data:
  config.yaml: |
    version: v1
    mig-configs:
      all-1g.10gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb": 7
      all-2g.20gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "2g.20gb": 3

然后为节点打标签触发 MIG 配置:

kubectl label node gpu-node-1 nvidia.com/mig.config=all-1g.10gb

资源调度
#

requests 与 limits 配置
#

GPU 资源比较特殊:requests 和 limits 必须相等,且必须是整数(整卡分配)。MIG 模式下可以请求分数 GPU。

apiVersion: v1
kind: Pod
metadata:
  name: gpu-training-job
spec:
  containers:
    - name: trainer
      image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
      resources:
        limits:
          nvidia.com/gpu: 4        # 请求 4 张整卡
          memory: "64Gi"
          cpu: "16"
        requests:
          nvidia.com/gpu: 4
          memory: "64Gi"
          cpu: "16"
      env:
        - name: NVIDIA_VISIBLE_DEVICES
          value: all
        - name: NVIDIA_DRIVER_CAPABILITIES
          value: compute,utility

MIG 实例请求:

resources:
  limits:
    nvidia.com/mig-1g.10gb: 1    # 请求 1 个 1g.10gb MIG 实例

节点亲和性与反亲和性
#

训练作业通常对 GPU 型号有要求,推理对延迟更敏感。通过 affinity 精确控制调度位置:

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              # 必须是 A100
              - key: nvidia.com/gpu.product
                operator: In
                values:
                  - A100-SXM4-80GB
                  - A100-PCIE-80GB
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              # 优先选择 NVLink 互联的节点
              - key: nvidia.com/gpu.count
                operator: Gt
                values: ["4"]
    # 多副本推理服务:分散到不同节点
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 50
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app: inference-server
            topologyKey: kubernetes.io/hostname

GPU 拓扑感知调度
#

对于多 GPU 分布式训练,GPU 之间的通信带宽至关重要。NVLink 连接的 GPU 之间带宽可达 600GB/s,而 PCIe 只有约 16GB/s。

Kubernetes 的 Topology Manager 可以确保 GPU 和 CPU 在同一 NUMA 节点上分配,减少跨 NUMA 访问延迟:

# kubelet 配置
--topology-manager-policy=best-effort
--topology-manager-scope=pod
--cpu-manager-policy=static

对于大规模训练(如 8 卡 A100),建议配置:

# Pod 请求整机所有 GPU
resources:
  limits:
    nvidia.com/gpu: 8
# 加注解强制绑定到同一物理节点
metadata:
  annotations:
    scheduler.alpha.kubernetes.io/tolerations: '[]'

GPU 共享方案对比
#

整卡独占(默认)
#

K8s 默认方案,一个容器独占一张完整 GPU。优点是隔离性强,性能可预期;缺点是资源浪费,一个小推理服务用不满整卡。

适用场景: 大模型训练、需要最大显存的推理服务。

MIG:物理切分
#

前面已介绍,A100/H100 专属特性。物理层面隔离,每个实例有独立的显存和 SM,完全隔离没有竞争。

适用场景: 多租户环境、需要硬隔离的 SaaS 场景。

限制: 只支持 A100/H100,切分规格固定,不够灵活。

时间片共享(NVIDIA Time-Slicing)
#

通过 GPU 时间分片让多个进程共享同一 GPU,类似 CPU 的分时复用:

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: false
        failRequestsGreaterThanOne: false
        resources:
          - name: nvidia.com/gpu
            replicas: 4  # 将 1 张 GPU 虚拟为 4 个资源

应用后,一张 GPU 会虚拟出 4 个 nvidia.com/gpu 资源,4 个 Pod 可以各自请求 1 个。但注意:没有显存隔离,任何一个进程都可以申请全部显存,可能导致 OOM。

适用场景: 开发测试环境、显存需求小的推理服务(如 embedding 模型)。

MPS:Multi-Process Service
#

MPS(CUDA Multi-Process Service)允许多个 CUDA 进程并发使用同一 GPU 的 SM,不同于时间片的轮转方式,MPS 是真正的空间并发:

# 在节点上启动 MPS Server
nvidia-cuda-mps-control -d
echo "set_default_active_thread_percentage 50" | nvidia-cuda-mps-control

MPS vs 时间片:

  • MPS 延迟更低(并发执行而非轮转)
  • MPS 适合多个小任务同时跑,吞吐更高
  • MPS 需要进程间信任(有内存隔离但不完全安全隔离)

适用场景: 同一租户的多个推理进程并发共享一张 GPU。


Karpenter:弹性 GPU 节点池
#

GPU 实例按需创建、用完即销毁,是降低 GPU 成本的关键。Karpenter 比 Cluster Autoscaler 更适合这个场景,因为它支持更细粒度的实例类型选择。

GPU NodePool 配置
#

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-training
spec:
  template:
    metadata:
      labels:
        workload: training
        nvidia.com/gpu: "true"
    spec:
      nodeClassRef:
        apiVersion: karpenter.k8s.aws/v1
        kind: EC2NodeClass
        name: gpu-nodeclass
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values:
            - p4d.24xlarge    # 8x A100 40GB
            - p3.2xlarge      # 1x V100
            - p3.8xlarge      # 4x V100
            - g5.xlarge       # 1x A10G
            - g5.12xlarge     # 4x A10G
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
      taints:
        - key: nvidia.com/gpu
          value: "present"
          effect: NoSchedule
  limits:
    nvidia.com/gpu: "64"   # 最多 64 张 GPU
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30m  # GPU 节点空闲 30 分钟后回收
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: gpu-nodeclass
spec:
  amiFamily: AL2
  role: KarpenterNodeRole-my-cluster
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  instanceStorePolicy: RAID0
  userData: |
    #!/bin/bash
    # 安装 NVIDIA 驱动和容器工具包
    yum install -y kernel-devel-$(uname -r)
    # GPU Operator 会自动处理驱动安装

Spot GPU 中断处理
#

使用 Spot GPU 实例可以节省 60-90% 成本,但需要处理实例中断:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: training-job
spec:
  template:
    spec:
      # 配置优雅终止时间
      terminationGracePeriodSeconds: 120
      containers:
        - name: trainer
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - |
                    # 保存 checkpoint 后退出
                    kill -SIGTERM $(pgrep -f train.py)
                    sleep 100

结合 AWS Node Termination Handler,提前收到 Spot 中断通知(2 分钟预告),触发优雅 checkpoint 保存:

helm install aws-node-termination-handler \
  eks/aws-node-termination-handler \
  --namespace kube-system \
  --set enableSpotInterruptionDraining=true \
  --set enableRebalanceMonitoring=true \
  --set webhookURL=${SLACK_WEBHOOK_URL}

监控体系:DCGM Exporter
#

核心指标
#

DCGM(Data Center GPU Manager)提供 GPU 的全面监控指标:

指标说明告警阈值
DCGM_FI_DEV_GPU_UTILGPU SM 利用率(%)< 20%(浪费)> 95%(过载)
DCGM_FI_DEV_MEM_COPY_UTIL显存带宽利用率(%)> 90%
DCGM_FI_DEV_FB_USED已用显存(MiB)> 95% 容量
DCGM_FI_DEV_GPU_TEMPGPU 温度(℃)> 80℃ 警告,> 90℃ 严重
DCGM_FI_DEV_POWER_USAGE功耗(W)> TDP 的 95%
DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTALNVLink 带宽分布式训练监控
DCGM_FI_DEV_ECC_SBE_VOL_TOTAL单比特 ECC 错误> 0 关注,积累增加告警
DCGM_FI_DEV_ECC_DBE_VOL_TOTAL双比特 ECC 错误(不可修复)> 0 立即告警

Prometheus + Grafana 集成
#

GPU Operator 部署时如果启用了 dcgmExporter.enabled=true,会自动创建 DCGM Exporter DaemonSet 和对应的 ServiceMonitor。

Grafana Dashboard 关键面板:

{
  "panels": [
    {
      "title": "GPU 利用率",
      "targets": [{
        "expr": "avg by (gpu, node) (DCGM_FI_DEV_GPU_UTIL{namespace='gpu-operator'})"
      }]
    },
    {
      "title": "显存使用率",
      "targets": [{
        "expr": "DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE * 100"
      }]
    },
    {
      "title": "GPU 温度",
      "targets": [{
        "expr": "DCGM_FI_DEV_GPU_TEMP"
      }],
      "thresholds": [{"value": 80, "color": "yellow"}, {"value": 90, "color": "red"}]
    }
  ]
}

Prometheus 告警规则
#

groups:
  - name: gpu-alerts
    rules:
      - alert: GPUHighTemperature
        expr: DCGM_FI_DEV_GPU_TEMP > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "GPU {{ $labels.gpu }} 温度过高"
          description: "节点 {{ $labels.node }} GPU {{ $labels.gpu }} 温度 {{ $value }}℃,已超过 85℃ 阈值"

      - alert: GPUMemoryAlmostFull
        expr: DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) > 0.95
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "GPU 显存接近用满"
          description: "节点 {{ $labels.node }} GPU {{ $labels.gpu }} 显存使用率 {{ $value | humanizePercentage }}"

      - alert: GPULowUtilization
        expr: |
          avg_over_time(DCGM_FI_DEV_GPU_UTIL[30m]) < 10
          and on(node) kube_node_labels{label_workload="training"} == 1
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "训练节点 GPU 利用率持续偏低"
          description: "节点 {{ $labels.node }} GPU 30 分钟平均利用率仅 {{ $value }}%,疑似空转"

      - alert: GPUUncorrectableError
        expr: DCGM_FI_DEV_ECC_DBE_VOL_TOTAL > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "GPU 发生不可修复 ECC 错误"
          description: "节点 {{ $labels.node }} GPU {{ $labels.gpu }} 检测到双比特 ECC 错误,硬件可能损坏"

训练作业调度
#

分布式训练架构
#

大模型训练通常需要多机多卡,K8s 上主要使用 Kubeflow 的 Training Operator 管理分布式训练作业:

apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: llm-pretrain
  namespace: training
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      restartPolicy: OnFailure
      template:
        spec:
          tolerations:
            - key: nvidia.com/gpu
              operator: Exists
              effect: NoSchedule
          containers:
            - name: pytorch
              image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel
              resources:
                limits:
                  nvidia.com/gpu: 8
                  memory: "640Gi"
                  cpu: "96"
              command:
                - torchrun
                - --nproc_per_node=8
                - --nnodes=4
                - --node_rank=$(RANK)
                - --master_addr=$(MASTER_ADDR)
                - --master_port=23456
                - train.py
                - --model_size=7B
              volumeMounts:
                - name: training-data
                  mountPath: /data
                - name: checkpoint
                  mountPath: /checkpoint
          volumes:
            - name: training-data
              persistentVolumeClaim:
                claimName: training-dataset-pvc
            - name: checkpoint
              persistentVolumeClaim:
                claimName: checkpoint-pvc
    Worker:
      replicas: 3
      restartPolicy: OnFailure
      template:
        spec:
          tolerations:
            - key: nvidia.com/gpu
              operator: Exists
              effect: NoSchedule
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                  - matchExpressions:
                      - key: nvidia.com/gpu.product
                        operator: In
                        values: ["A100-SXM4-80GB"]
          containers:
            - name: pytorch
              image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel
              resources:
                limits:
                  nvidia.com/gpu: 8

节点间高速互联
#

P4d.24xlarge 实例有 8x A100 通过 NVSwitch 全互联,多机之间通过 EFA(Elastic Fabric Adapter)高速互联,带宽可达 400Gbps。

配置 EFA 支持:

containers:
  - name: pytorch
    resources:
      limits:
        hugepages-2Mi: "5120Mi"      # EFA 需要大页内存
        vpc.amazonaws.com/efa: "4"   # 请求 EFA 设备
    env:
      - name: NCCL_SOCKET_IFNAME
        value: "^lo"
      - name: NCCL_DEBUG
        value: "INFO"
      - name: FI_EFA_USE_DEVICE_RDMA
        value: "1"
      - name: FI_PROVIDER
        value: "efa"

推理部署优化
#

Triton Inference Server
#

NVIDIA Triton 是专为 GPU 推理优化的服务框架,支持 TensorRT、ONNX、PyTorch、TensorFlow 等多种模型格式,内置动态 batching 和并发推理。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: triton-server
  namespace: inference
spec:
  replicas: 2
  template:
    spec:
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: triton
          image: nvcr.io/nvidia/tritonserver:24.01-py3
          command:
            - tritonserver
            - --model-repository=s3://my-models/triton
            - --strict-model-config=false
            - --grpc-port=8001
            - --http-port=8000
            - --metrics-port=8002
          resources:
            limits:
              nvidia.com/gpu: 1
              memory: "32Gi"
              cpu: "8"
          ports:
            - containerPort: 8000
              name: http
            - containerPort: 8001
              name: grpc
            - containerPort: 8002
              name: metrics
          readinessProbe:
            httpGet:
              path: /v2/health/ready
              port: 8000
            initialDelaySeconds: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /v2/health/live
              port: 8000
            initialDelaySeconds: 60

动态 batching 配置(config.pbtxt):

name: "my_model"
backend: "tensorrt"
max_batch_size: 32

input [
  {
    name: "input_ids"
    data_type: TYPE_INT32
    dims: [512]
  }
]

output [
  {
    name: "logits"
    data_type: TYPE_FP32
    dims: [32000]
  }
]

dynamic_batching {
  preferred_batch_size: [8, 16, 32]
  max_queue_delay_microseconds: 5000  # 等待最多 5ms  batch
}

instance_group [
  {
    count: 2
    kind: KIND_GPU
    gpus: [0]
  }
]

vLLM:LLM 推理的事实标准
#

对于 LLM 推理,vLLM 凭借 PagedAttention 显存管理和 Continuous Batching,已成为最主流的选择:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-server
spec:
  template:
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.4.3
          command:
            - python
            - -m
            - vllm.entrypoints.openai.api_server
            - --model
            - /models/Llama-3-8B-Instruct
            - --tensor-parallel-size
            - "1"
            - --gpu-memory-utilization
            - "0.90"
            - --max-num-seqs
            - "256"
            - --port
            - "8000"
          resources:
            limits:
              nvidia.com/gpu: 1
              memory: "40Gi"
          volumeMounts:
            - name: model-storage
              mountPath: /models
      volumes:
        - name: model-storage
          persistentVolumeClaim:
            claimName: model-pvc

常见故障排查
#

OOMKilled:显存不足
#

最常见的 GPU 问题。排查步骤:

# 查看 Pod 状态
kubectl describe pod <pod-name> | grep -A5 "Last State"

# 查看显存使用情况
kubectl exec -it <pod-name> -- nvidia-smi

# 检查 DCGM 指标
kubectl exec -it dcgm-exporter-xxx -- nvidia-smi dmon -s u

解决方案:

  1. 增大 nvidia.com/gpu 请求(使用更多 GPU 分散显存)
  2. 减小 batch size
  3. 使用混合精度(FP16/BF16)减少显存占用
  4. 开启梯度检查点(gradient checkpointing)

Pod 调度到无 GPU 节点
#

症状:Pod pending,Event 显示 Insufficient nvidia.com/gpu,但实际有 GPU 节点空闲。

排查:

# 查看节点 GPU 资源
kubectl get nodes -o custom-columns=\
"NAME:.metadata.name,GPU:.status.capacity.nvidia\.com/gpu"

# 查看是否有污点未容忍
kubectl describe node gpu-node-1 | grep Taint

# 查看 Pod 是否配置了 toleration
kubectl get pod <pod-name> -o yaml | grep -A10 tolerations

常见原因:

  • 忘记配置 tolerations 匹配 GPU 节点污点
  • nodeSelector 条件与实际节点标签不匹配
  • Device Plugin 未正常运行,节点 GPU 资源为 0
# 检查 Device Plugin 状态
kubectl get pod -n nvidia-device-plugin
kubectl logs -n nvidia-device-plugin nvidia-device-plugin-xxx

驱动版本不兼容
#

症状:Pod 启动失败,日志显示 CUDA driver version is insufficient

# 查看节点驱动版本
kubectl exec -it <pod-name> -- nvidia-smi

# 检查 CUDA Toolkit 版本要求
# pytorch 镜像标签如 pytorch:2.1.0-cuda12.1,需要驱动 >= 525

解决方案:升级节点 NVIDIA 驱动,或使用与驱动版本兼容的镜像。

NVLink 未启用导致训练慢#

症状:多 GPU 训练比预期慢,NCCL all-reduce 通信成为瓶颈。

# 检查 NVLink 状态
nvidia-smi nvlink --status -i 0

# 检查 NVLink 带宽
nvidia-smi nvlink --getbandwidth -i 0

成本优化
#

Spot GPU 实例策略
#

结合 Karpenter,训练作业使用 Spot 实例可以节省 60-70%:

# Karpenter NodePool:优先 Spot,容量不足时自动切换 On-Demand
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["p3.8xlarge", "p3.16xlarge", "p4d.24xlarge"]
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 10m

训练代码配合 checkpoint 机制,Spot 中断时自动从最近 checkpoint 恢复。

GPU 利用率监控与优化
#

低 GPU 利用率是最大的浪费。设置 Grafana 告警:利用率持续 30 分钟低于 20% 则触发通知。

常见低利用率原因:

  • 数据加载成为瓶颈(CPU 喂不饱 GPU):增大 num_workers,使用 DALI 预处理
  • batch size 太小:适当增大 batch size 提高 GPU 并行度
  • 频繁同步操作:减少梯度同步频率(gradient accumulation)

节点自动缩容
#

训练完成后及时释放 GPU 节点,避免空转计费:

# Karpenter 配置:节点空闲 10 分钟即回收
disruption:
  consolidationPolicy: WhenEmpty
  consolidateAfter: 10m
  # 预算控制:每次最多回收 50% 节点
  budgets:
    - nodes: "50%"

GPU 这块最容易浪费钱,节点弹性(Karpenter)、资源隔离(MIG/时间片)、监控告警(DCGM)这三件事不做扎实,后面所有调优都是瞎猜。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

CoreDNS 深度排障:K8s DNS 问题完全指南

·1064 字·5 分钟
DNS 问题是 K8s 中最难定位的问题之一,因为它的失败往往是间歇性的、有延迟的,看起来像网络问题,实际上是 DNS 超时。本文记录了我在生产环境排查过的多类 DNS 故障,附详细的抓包分析和调优配置。

混沌工程实战:Chaos Mesh 在 K8s 中注入故障

·809 字·4 分钟
混沌工程不是破坏系统,而是在可控环境中提前暴露脆弱点。本文记录了我用 Chaos Mesh 在生产级 K8s 集群中设计并执行混沌演练的完整过程,包括安装、实验配置、Workflow 编排和游戏日流程设计。

OPA/Kyverno:K8s 准入控制策略实战

·895 字·5 分钟
没有准入控制的 K8s 集群就像一个没有门卫的机房——任何人都能随意进出。本文记录了我在多个生产集群部署 Kyverno 策略的实战经验,涵盖资源限制强制、镜像来源白名单、标签规范、以及与 OPA Gatekeeper 的对比选型思路。