技术博客

GPU 节点故障自愈:Node Problem Detector 与自动隔离实战

详解如何在 Kubernetes GPU 集群中通过 Node Problem Detector(NPD)检测 GPU 故障、上报 NodeCondition、结合 Node Auto Remediation 实现节点自动隔离与修复,涵盖 NVIDIA XID 错误、ECC 故障、驱动崩溃等常见场景。

GPUKubernetesNPD故障自愈NVIDIA监控告警

GPU 节点故障是大规模 AI 集群最常见的运维挑战之一:显卡 ECC 错误累积导致静默数据损坏、驱动崩溃导致 Pod 挂起、GPU 温度过高触发自动降频。如果没有自动化的故障检测和隔离机制,这类问题往往要等到用户投诉才被发现,造成训练任务长时间卡死或推理结果异常。

本文介绍以 Node Problem Detector(NPD)+ Node Auto Remediation 为核心的 GPU 故障自愈方案。

GPU 故障分类

故障类型 表现 影响
XID 错误 nvidia-smi 报 Xid 79/94/95 等 GPU 计算挂起,Pod 无响应
ECC 不可纠正错误(UE) 显存数据损坏 静默计算错误,结果不可信
ECC 可纠正错误(CE)高频 单比特翻转 预示硬件劣化,需关注
驱动崩溃 /dev/nvidia* 消失 所有 GPU Pod 失效
GPU 温度过高 结温 > 83°C(A100 阈值) 降频,吞吐下降 50%+
NVLink/PCIe 链路故障 nv_link_errors 计数增长 多 GPU 训练通信失效

架构设计

GPU 节点
  ├── nvidia-smi / nvml 库
  ├── NPD DaemonSet(监控节点日志和系统状态)
  │     ├── 检测 /var/log/nvidia/ XID 错误
  │     ├── 检测 ECC 错误计数
  │     └── 上报 NodeCondition → Kubernetes API
  └── Node Auto Remediation(NAR)
        ├── 监听 NodeCondition 变化
        ├── 触发 cordon + drain + 重启
        └── 修复后自动 uncordon

安装 Node Problem Detector

helm repo add deliveryhero https://charts.deliveryhero.io/
helm repo update

helm upgrade --install node-problem-detector deliveryhero/node-problem-detector \
  --namespace kube-system \
  --set image.tag=v0.8.19 \
  --set settings.log_monitors[0]=/config/plugin/config/kernel.json \
  --set settings.custom_plugin_monitors[0]=/config/plugin/config/nvidia-gpu-monitor.json

配置 NVIDIA GPU 自定义监控插件

NPD 支持自定义监控脚本,通过检测 GPU 状态上报 NodeCondition:

// /config/plugin/config/nvidia-gpu-monitor.json
{
  "plugin": "custom",
  "pluginConfig": {
    "invoke_interval": "30s",
    "timeout": "20s",
    "max_output_length": 4096,
    "concurrency": 1
  },
  "source": "nvidia-gpu-monitor",
  "conditions": [
    {
      "type": "NvidiaGpuHealthy",
      "reason": "NvidiaGpuHasNoError",
      "message": "GPU is functioning normally"
    },
    {
      "type": "NvidiaEccUncorrectableError",
      "reason": "NvidiaGpuHasNoEccUncorrectableError",
      "message": "GPU has no ECC uncorrectable errors"
    },
    {
      "type": "NvidiaDriverNotLoaded",
      "reason": "NvidiaDriverIsLoaded",
      "message": "Nvidia driver is loaded"
    }
  ],
  "rules": [
    {
      "type": "permanent",
      "condition": "NvidiaDriverNotLoaded",
      "reason": "NvidiaDriverNotLoaded",
      "path": "/config/plugin/config/gpu-check.sh",
      "args": ["driver_loaded"],
      "timeout": "10s"
    },
    {
      "type": "permanent",
      "condition": "NvidiaEccUncorrectableError",
      "reason": "NvidiaEccUncorrectableError",
      "path": "/config/plugin/config/gpu-check.sh",
      "args": ["ecc_uncorrectable"],
      "timeout": "10s"
    },
    {
      "type": "permanent",
      "condition": "NvidiaGpuHealthy",
      "reason": "NvidiaGpuHasXidError",
      "path": "/config/plugin/config/gpu-check.sh",
      "args": ["xid_error"],
      "timeout": "10s"
    }
  ]
}

GPU 检测脚本

#!/bin/bash
# /config/plugin/config/gpu-check.sh

CHECK_TYPE=$1

case $CHECK_TYPE in
  driver_loaded)
    # 检查驱动是否加载
    if ! ls /dev/nvidia0 &>/dev/null 2>&1; then
      echo "Nvidia driver not loaded: /dev/nvidia0 not found"
      exit 1
    fi
    # 检查 nvidia-smi 是否可用
    if ! nvidia-smi > /dev/null 2>&1; then
      echo "nvidia-smi command failed - driver may be crashed"
      exit 1
    fi
    exit 0
    ;;

  ecc_uncorrectable)
    # 检查所有 GPU 的 ECC 不可纠正错误
    ECC_ERRORS=$(nvidia-smi --query-gpu=ecc.errors.uncorrected.aggregate.total \
      --format=csv,noheader,nounits 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
    if [ "$ECC_ERRORS" -gt 0 ]; then
      echo "Found $ECC_ERRORS ECC uncorrectable errors"
      exit 1
    fi
    exit 0
    ;;

  xid_error)
    # 检查 dmesg 中的 XID 严重错误(Xid 79、94、95 为严重错误)
    CRITICAL_XIDS=$(dmesg --since "10 minutes ago" 2>/dev/null | \
      grep -E "NVRM: Xid (79|94|95)" | wc -l)
    if [ "$CRITICAL_XIDS" -gt 0 ]; then
      echo "Found $CRITICAL_XIDS critical Xid errors in last 10 minutes"
      exit 1
    fi
    exit 0
    ;;
esac

给脚本打入 ConfigMap 并挂载到 NPD Pod 中(通过 Helm values 或手动配置)。

部署 Node Auto Remediation(NAR)

NAR 是 NVIDIA GPU Operator 和 AMD GPU Operator 均支持的自动修复组件,也可使用社区项目 medik8s/node-healthcheck-operator

使用 medik8s Node Healthcheck Operator

# 安装 Node Healthcheck Operator
kubectl apply -f https://raw.githubusercontent.com/medik8s/node-healthcheck-operator/main/config/default/kustomization.yaml

配置 NodeHealthCheck:

apiVersion: remediation.medik8s.io/v1alpha1
kind: NodeHealthCheck
metadata:
  name: gpu-node-healthcheck
spec:
  selector:
    matchLabels:
      nvidia.com/gpu.present: "true"

  # 触发修复的条件
  unhealthyConditions:
  - type: NvidiaEccUncorrectableError
    status: "True"
    duration: 30s    # 持续 30 秒才触发,避免抖动

  - type: NvidiaDriverNotLoaded
    status: "True"
    duration: 60s

  - type: NvidiaGpuHealthy
    status: "False"
    duration: 120s

  # 修复动作:使用 SelfNodeRemediation(重启节点)
  remediationTemplate:
    apiVersion: self-node-remediation.medik8s.io/v1alpha1
    kind: SelfNodeRemediationTemplate
    name: automatic-reboot-template
    namespace: medik8s
---
apiVersion: self-node-remediation.medik8s.io/v1alpha1
kind: SelfNodeRemediationTemplate
metadata:
  name: automatic-reboot-template
  namespace: medik8s
spec:
  template:
    spec:
      remediationStrategy: Automatic

自定义修复逻辑(轻量方案)

如果不想引入额外 Operator,可以用简单的 Kubernetes Operator 逻辑实现:

# 监控脚本:检测 NodeCondition 并自动隔离(运行在独立 Pod 或 CronJob 中)
#!/bin/bash
while true; do
  # 查找所有 GPU 节点中 Condition=True 的故障节点
  UNHEALTHY_NODES=$(kubectl get nodes -l nvidia.com/gpu.present=true \
    -o json | jq -r '.items[] |
    select(
      .status.conditions[] |
      select(.type == "NvidiaEccUncorrectableError" and .status == "True")
    ) | .metadata.name')

  for NODE in $UNHEALTHY_NODES; do
    echo "[$(date)] 检测到故障节点: $NODE,开始隔离..."

    # 标记不可调度
    kubectl cordon $NODE

    # 驱逐 Pod(保留 DaemonSet)
    kubectl drain $NODE \
      --ignore-daemonsets \
      --delete-emptydir-data \
      --grace-period=60 \
      --timeout=300s

    # 打上故障标签
    kubectl label node $NODE gpu.health=unhealthy \
      gpu.isolated-at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" --overwrite

    # 发送告警(Webhook 或邮件)
    curl -X POST $ALERT_WEBHOOK \
      -H "Content-Type: application/json" \
      -d "{\"text\": \"GPU 节点 $NODE 已自动隔离,原因: ECC 不可纠正错误\"}"

    echo "[$(date)] 节点 $NODE 已隔离完成"
  done

  sleep 60
done

关键 XID 错误码速查

运维人员需要了解常见 XID 错误的含义与处理方式:

XID 含义 处理建议
13 显存 ECC 错误 检查 ECC 计数,关注是否增长
31 GPU 挂起(hang) 立即重启节点
43 GPU 驱动状态不一致 重启节点,若持续联系 NVIDIA 支持
61 内部微控制器中断故障 需硬件检测
63 行重映射记录故障(Row Remapping) 显存物理损坏,需更换硬件
74 显存 NVLink 错误 检查 NVLink 连接
79 GPU 挂起,需重置 立即隔离并重启
94 容器卸载时 GPU 未响应 驱逐 Pod,重启节点
95 非预期的 SRAM ECC 错误 可能是硬件损坏,需评估更换
# 查看节点 XID 错误历史
ssh <node-ip> "dmesg | grep -E 'NVRM: Xid' | tail -50"

# 实时监控 XID 错误
ssh <node-ip> "dmesg -w | grep 'NVRM: Xid'"

Prometheus 告警规则

groups:
- name: gpu-health
  interval: 30s
  rules:
  # ECC 不可纠正错误(严重,立即告警)
  - alert: GpuEccUncorrectableError
    expr: |
      nvidia_ecc_errors_uncorrected_aggregate_total > 0
    for: 0m
    labels:
      severity: critical
    annotations:
      summary: "节点 {{ $labels.instance }} GPU {{ $labels.gpu }} ECC 不可纠正错误"
      description: "ECC 不可纠正错误数 {{ $value }},显存数据可能已损坏,需立即隔离节点"

  # ECC 可纠正错误高频(预警,不立即隔离)
  - alert: GpuEccCorrectableErrorHigh
    expr: |
      increase(nvidia_ecc_errors_corrected_aggregate_total[1h]) > 100
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "节点 {{ $labels.instance }} GPU {{ $labels.gpu }} ECC 可纠正错误频繁"
      description: "过去 1 小时 ECC 可纠正错误 {{ $value }} 次,建议安排硬件检测"

  # GPU 温度过高
  - alert: GpuTemperatureCritical
    expr: |
      nvidia_temperature_gpu > 83
    for: 3m
    labels:
      severity: warning
    annotations:
      summary: "节点 {{ $labels.instance }} GPU {{ $labels.gpu }} 温度过高"
      description: "GPU 结温 {{ $value }}°C,超过 83°C 阈值,可能触发降频"

  # 节点 GPU 资源全部不可用(驱动崩溃)
  - alert: GpuNodeAllDevicesUnavailable
    expr: |
      kube_node_status_allocatable{resource="nvidia.com/gpu"} == 0
      and on(node) kube_node_labels{label_nvidia_com_gpu_present="true"}
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "GPU 节点 {{ $labels.node }} 所有 GPU 资源不可分配"
      description: "该节点 GPU 驱动可能已崩溃,请检查 NPD 状态并考虑重启节点"

完整运维流程

日常巡检

# 每日检查 GPU 节点健康状态
kubectl get nodes -l nvidia.com/gpu.present=true \
  -o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[-1].type,READY:.status.conditions[-1].status

# 查看所有 GPU 节点的 ECC 错误
for node in $(kubectl get nodes -l nvidia.com/gpu.present=true -o name | cut -d/ -f2); do
  echo "=== $node ==="
  kubectl debug node/$node -it --image=nvidia/cuda:12.1.0-base-ubuntu22.04 -- \
    nvidia-smi --query-gpu=index,ecc.errors.uncorrected.aggregate.total --format=csv
done

# 检查 NPD 上报的节点状态
kubectl get nodes -o json | jq -r '.items[] |
  select(.metadata.labels["nvidia.com/gpu.present"] == "true") |
  "\(.metadata.name): \(.status.conditions[] | select(.type | startswith("Nvidia")) | "\(.type)=\(.status)")"'

故障恢复确认

# 确认驱动已重新加载
ssh <node-ip> "nvidia-smi && echo '驱动正常'"

# 确认 ECC 错误已清除(或计数不再增长)
ssh <node-ip> "nvidia-smi --query-gpu=ecc.errors.uncorrected.aggregate.total --format=csv,noheader"

# 解除隔离
kubectl uncordon <node-ip>
kubectl label node <node-ip> gpu.health=healthy --overwrite

小结

GPU 故障自愈的核心闭环:NPD 检测 → NodeCondition 上报 → NAR 自动 drain+重启 → Prometheus 告警通知运维。关键原则:ECC 不可纠正错误要立即隔离(数据损坏影响业务),ECC 可纠正错误记录趋势(预测硬件寿命),XID 79/94/95 类重置级错误立即重启节点。自动化修复配合人工确认才是成熟运维,自动隔离后一定要有人工复核再解除。