Monitoring 2026-04-30

bash 复制代码
1. NVLink降宽就是比如 A100 原来是nv12,现在变成nv6. 降速用p2p就能测试出来

2. Level 3 包含了 Level 1 和 Level 2 的所有检查项,并在此基础上增加了更严苛、更深层的硬件压力测试。
反正都是要停机,我直接用 -r 3(~20-40分钟),mlxlink 测的是网卡到主板的带宽
bash 复制代码
场景1:训练 loss 突然 spike / NaN,但所有监控指标正常

  这是最经典的 "静默数据损坏"(Silent Data Corruption, SDC)

  运维操作:
    1. 把出问题的节点 drain
    2. 跑 dcgmi diag -r 3
    3. 输出:

    +---------------------------+--------------------------------------------+
    | Diagnostic (compute)    | FAIL                                         |
    |                          | GPU 6: Matrix multiply result mismatch     |
    |                          | Expected: 1.23456789                       |
    |                          | Got:      1.23456287                       |
    |                          | Bit errors detected in SM computation      |
    +---------------------------+--------------------------------------------+

    4. 确认 GPU 6 有静默计算错误
    5. 这张卡算出来的结果是错的,但不会报任何 error
    6. 只有 r3 的矩阵乘法验证能抓到

场景2:GPU 间歇性挂死,偶尔 Xid 79 (GPU 从总线掉线)
【对比 XID 48:Double Bit ECC Error (双位 ECC 错误)】
  运维操作:
    1. 跑 r1、r2 都 Pass(因为短时间内没触发)
    2. 跑 r3(持续压力 30 分钟)
    3. 输出:

    +---------------------------+--------------------------------------------+
    | Memory Stress            | FAIL                                        |
    |                          | GPU 2: ECC DBE detected during stress      |
    |                          | at address 0x7F3A_2000                     |
    |                          | GPU requires replacement                   |
    +---------------------------+--------------------------------------------+

    4. 长时间压力下终于复现了 DBE(Double Bit Error)
    5. 这张卡必须换

drain

bash 复制代码
# 1. 选定要巡检的节点,从调度器摘出来
# A100 集群
kubectl cordon node-a100-{01..64}
kubectl drain node-a100-{01..64} --ignore-daemonsets --delete-emptydata

1. 每个节点上都有日志采集,监控和 nvidia 插件这些daemonset,你 drain走,它又生成,陷入死循环。
2. 不加 --delete-emptydata,kubectl 会为了保护你的数据不丢失,拒绝驱逐该 pod。emptyDir 是 pod 删除或调度到别的节点,
 数据也没了。当 Pod 分配到节点时,K8s 会在节点上创建一个空目录。只要 Pod 还在这个节点上运行,这个目录就一直存在。
3. drain 的节点要注意有没有coredns和 Metrics Server 这些不在下面四大static pod 的基础组件,它们是deployment/pod 形式的,
如果没有副本会发生脑裂现象,造成短暂的服务停止。
Metrics Server 是 Kubernetes 内置的"性能监控聚合器".从每个节点上的 kubelet 收集基础的资源使用数据(CPU、内存),并提供给
 Kubernetes 的其他组件使用。没有它就会
  1)kubectl top 命令:你将无法通过 kubectl top nodes 或 kubectl top pods 查看实时资源占用。
  2)HPA (Horizontal Pod Autoscaler):这是最致命的。如果你设置了"当 CPU 超过 80% 时自动扩容",HPA 必须通过 Metrics 
  Server 才知道现在的 CPU 是多少。
  
4. drain 会自动忽略 静态pod,也就是yaml文件在 /etc/kubernetes/manifests 里的 kube-apiserver、kube-controller-manager、
etcd 、Scheduler 等。

# 2. 确认 GPU 上没有进程
ansible ib_a100 -m shell -a "nvidia-smi --query-compute-apps=pid --format=csv,noheader | wc -l"
# 所有节点应该返回 0

二、Monitoring

bash 复制代码
cgroup 数据源头,负责限制和记录容器使用资源情况,比如 cpu 内存和 IO
cAdvisor 内置在 kubelet 里。它直接读取 cgroup 的原始数据, 并通过 http 暴露给 Prometheus 和 metrics server.
metrics server 定期去 kubelet (cAdvisor)那拉取 cpu 和内存使用情况,缓存在内存,为了 给 kubectl top 使用。集群中一个pod
node-exporter 主机层指标

kube--state-metrics 直接监听 API Server 的元数据,也就是k8s资源的逻辑状态,不看 cgroup,它也是集群中一个pod。
它告诉你 资源是什么状态,它们应该是什么样。

监控防抖

同组告警

bash 复制代码
group_by: ['node', 'alertname']

# 同一时刻 node01 上触发了3条 GPUTemperatureHigh 告警(8卡里3张过热)
# 不会发3条消息,合并成1条:
# "node01: GPUTemperatureHigh - GPU0/GPU3/GPU5 温度超过83°C"
bash 复制代码
节点指标(GPU温度突然飙到85°C)
  → Prometheus 每15s采集一次
  → 告警规则里写持续时间:
      alert: GPUTemperatureHigh
      expr: nvidia_gpu_temperature > 83
      for: 5m          # 关键:持续5分钟才触发,这就是防抖
  → 5分钟内恢复了 → 不告警
  → 5分钟还没恢复 → 触发告警 → Alertmanager
  → Alertmanager 再做 group_wait/group_interval:
      group_wait: 30s        # 同组告警等30s聚合
      group_interval: 5m     # 同组告警最少间隔5m才发下一条
      repeat_interval: 4h    # 同一条告警4小时才重复通知
  → 最终发到飞书/钉钉

三层防抖:Prometheus for 过滤瞬时抖动 → Alertmanager group_wait 聚合 → repeat_interval 防刷屏。


过了实时监控(防抖)和日常巡检这两关,只能说明表面上硬件没有问题。

巡检只是那一瞬,深度是压测,时间长,能看出一段时间内有没有问题。

如果上层应用同事反馈性能不达标【nccl-test】,或者loss有尖刺,监控偶尔发现xid 79掉卡问题【-r 3查】

过两关后 nccl-test查出来的问题

  1. GDR问题
bash 复制代码
事件:运维升级了内核 / OFED 驱动 / CUDA 版本

原因:
  新内核没加载 nvidia-peermem 模块
  GDR 路径断了,数据走 GPU → CPU内存 → 网卡,多了一次拷贝
  硬件完全正常,纯软件问题

日常巡检能发现吗?不能
  ibstat 看端口状态,看不到 GDR
  mlxlink 看物理层,看不到 GDR
  它们的视角只到网卡这一层,分不出来源头是 GPU 还是 CPU
  而nccl属于 upper layer,它可以查出来。

怎么确认:
  $ lsmod | grep nvidia_peermem
  (空)  ← 模块没加载

  但你不跑 NCCL test,根本不知道要去查这个
  1. 环境变量中没选网卡
bash 复制代码
事件:有人改了集群调度模板 / 容器镜像 / 启动脚本

之前配置:
  NCCL_IB_HCA=mlx5_0,mlx5_1(用两张网卡)
  NCCL_ALGO=Ring
  NCCL_CROSS_NIC=0

被改成(或者变量丢了):
  NCCL_IB_HCA 没设置 → NCCL 只自动选了一张网卡
  
结果:
  所有硬件指标全绿
  ib_write_bw 两张网卡分别测都是 196 Gbps ✓
  但 NCCL busbw 掉了一半,因为只用了一张网卡
  1. NVLink 部分链路降速但未断开
bash 复制代码
事件:NVLink 信号劣化,链路自动降速

nvidia-smi nvlink -s → 全 Active ✓
nvidia-smi nvlink -e → 错误计数 = 0 ✓(降速后不报错了)
nvidia-smi topo -m → 全 NV18 ✓(链路还在)

但 NVLink 从 25 GB/s/link 降到了 20 GB/s/link   单向
日常巡检读寄存器看不到速率变化(NVLink 没有像 IB 那样直观显示速率的命令)

只有 p2pBandwidthTest 或 NCCL test 灌流量才能发现带宽掉了

1. NCCL test 什么时候用 WARN 而不是 INFO

bash 复制代码
用于训练的镜像中的nccl,在训练过程中如果NCCL_DEBUG=INFO,那么在pod的日志里只会打印拓扑和选路信息,不会打印 busbw
busbw 是 nccl-test 自己算的,不是 NCCL 库的功能

NCCL 的日志级别通过环境变量控制:

bash 复制代码
export NCCL_DEBUG=INFO    # 默认,输出通信路径、选路信息
export NCCL_DEBUG=WARN    # 只输出警告和错误

例子 1:大规模生产训练

bash 复制代码
# 512 卡训练,每张卡都打 INFO 日志
# INFO 会输出每张卡的:选了哪张网卡、走的什么算法、Ring 拓扑、Tree 拓扑...
# 512 张卡 × 每张几十行 = 上万行日志,淹没真正有用的信息

# 生产环境用 WARN
export NCCL_DEBUG=WARN

# 只有出问题时才会看到输出,比如:
# WARN: Timeout waiting for ring 0 on rank 47
Rank 47(第 48 个 GPU 进程)在执行通信环路同步时超时了
# WARN: NET/IB: Got completion(完工) with error 12 on mlx5_1
在名为 mlx5_1 的网卡上,RDMA 传输任务以"错误代码 12"结束。

例子 2:调试完成后的日常 NCCL test 巡检

NCCL test 的目的是模拟真实训练场景

bash 复制代码
# 你已经确认过集群配置没问题,现在只是每周例行跑 NCCL test 验证性能
# 不需要看选路细节,只关心有没有异常

export NCCL_DEBUG=WARN
mpirun -np 512 ... all_reduce_perf -b 1G -e 1G -g 1 -n 50

# 正常时:干干净净只有 busbw 结果
# 异常时:WARN 会告诉你哪个 rank 出了什么错
复制代码
NCCL_DEBUG=WARN(默认模式)只能告诉你"结果:病倒了",而 NCCL_DEBUG=INFO 才能告诉你"过程:是怎么病的"。
比如: 
1)运维升级了内核,但忘了加载 nvidia-peermem。

WARN 模式表现:完全静默。因为从网络协议看,通讯是通的,只是变慢了。程序能跑完,但带宽从 200Gbps 掉到了 10Gbps。
你只会看到业务慢,但没有任何报错。

NFO 模式威力:
它会打印出每一条链路的初始化细节。你会看到:
NET/IB : Using [0]mlx5_1:1/RoCE ... (GDRv3) ← 正常
或者
NET/IB : Using [0]mlx5_1:1/RoCE ... (后面没有 GDR 字样) ← 抓到真凶


2)在 8 卡 SXM 架构中,如果 NVSwitch 固件或驱动配置出错,NCCL 可能会放弃 NVLink,转而走 PCIe。

WARN 模式表现:同样无报错。程序能跑,但 nccl-tests 测出来的带宽只有 20GB/s。

INFO 模式威力:
它会输出拓扑探测的结果(Topology Search)。你会看到:
NCCL INFO Channel 00 : 0 1 2 3 4 5 6 7 [NVL] ← 正常,NVL 代表 NVLink
或者
NCCL INFO Channel 00 : 0 1 2 3 4 5 6 7 [PBM] ← 异常,PBM 代表经过了 PCIe Bridge

2. dcgmi -r 3 怎么测 Tensor Core,输出什么信息

dcgmi diag -r 3 的 Diagnostic (compute) 测试项就是在测 Tensor Core。

/ˌdaɪəɡˈnɒstɪk/ 诊断


Monitoring

只需要记住 实时监控和两种巡检 巡检的内容 和 两种报告方式:ansible(收集和 fetch) + Prometheus

还有最下面的自动 cordon + drain

Prometheus(情报员):负责盯着数据。如果发现某个指标(比如 GPU 温度)超过了你设定的阈值,它就会产生一个告警。

Prom 连 Alertmanager

yaml 复制代码
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - 'localhost:9093' # Alertmanager 的地址

在 alertmanager.yml 里,你需要定义一个 receiver(接收者)。因为 Alertmanager 原生不支持飞书,所以我们使用 webhook 模式。

yaml 复制代码
receivers:
- name: 'feishu-robot'
  webhook_configs:
  - url: 'http://feishu-adapter:8080/webhook' # 翻译官的地址
    send_resolved: true # 问题解决了也要通知一声
bash 复制代码
实时监控(紧急硬错误)   日常巡检(是否正常)  深度体检(隐疾, 要在没有训练任务的时候,因为要独占)

下面这两条都说明在深度体检的时候会影响到正在训练的任务,所以巡检脚本中要有检查是否在训练,训练就不能深度体检

bash 复制代码
dcgmi diag -r 3:
  → 独占 GPU 跑 30 分钟烤机压测
  → 和训练任务抢显存、抢算力
  → 两者同时跑 = 训练挂掉或体检结果不准

nccl-tests:是一套专门用来压测 GPU 间通信效率的工具集
  → 要占满所有 8 张 GPU 跑 AllReduce
  → 训练任务在跑的话显存不够,直接 OOM

实时监控(7×24 自动,抓紧急硬错误)

目标:已经坏了的东西,必须立刻知道

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  ① ECC DBE(双比特不可纠正错误)                                │
│      XID 48 = DBE 双比特错误                                     │
│     处置:立即 cordon + drain,换卡                              │
│       XID 79 = GPU 掉卡(从 PCIe 总线消失)                      │
│       XID 79 检查主板/PCIe 插槽                                  │
│                                                                 │
│  ② IB/RoCE 端口 Down                                           │
│                                                                 │
│       IB 节点:ibstat 的 State 字段(通过自定义 exporter)       │
│       RoCE 节点:ethtool 的 Link detected 字段                  │
│     阈值:State ≠ Active 立即告警                                │
│     处置:检查线缆/光模块,换端口                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

日常巡检(每天凌晨 4 点,确认是否正常)

不影响正在运行的任务

就是(读寄存器/计数器)

bash 复制代码
dcgmi diag -r 2                         ⚠️ 唯一有影响的项
  → 会跑 GPU 压测 2-3 分钟
  → 会占 GPU 算力和显存
复制代码
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  ① ECC SBE 增长趋势(单比特可纠正错误)                        │
│                                                                 │
│     检查方式:                                                   │
│       今天的 SBE 累计值 vs 昨天的值                              │
│       increase(DCGM_FI_DEV_ECC_SBE_AGG_TOTAL[24h])             │
│     阈值:24小时增长 > 10 → warning                             │
│                                                                 │
│  ② PCIe 宽度 + 纠正错误                                        │
│                                                                 │
│     什么问题:                                                   │
│       PCIe x16 降到 x8 → 带宽减半,训练慢但不报错               │
│       PCIe Receiver Error 增长 → 金手指氧化/线缆松动            │
│     检查方式:                                                   │
│       mlxlink -d <dev> --port_type PCIE → 看 Width              │
│       mlxlink -d <dev> --port_type PCIE -ce → 看错误计数        │
│     阈值:Width ≠ x16 → warning,Receiver Error > 0 → warning  │
│                                                                 │
│  ③ GDR 模块 + NVLink 错误计数                                   │
│                                                                 │
│     什么问题:                                                   │
│       nvidia-peermem 模块掉了 → GDR 失效 → 性能降 30-50%        │
│       NVLink 错误计数 > 0 → 链路信号在恶化                      │
│     检查方式:                                                   │
│       lsmod | grep nvidia_peermem                                │
│       nvidia-smi nvlink -e                                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

深度体检(每周日 / 新机验收 / 维修后)

目标:表面正常但实际有隐疾,只有压测才能暴露

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  ① dcgmi diag -r 3 静默计算错误                                │
│                                                                 │
│     什么问题:                                                   │
│       GPU Tensor Core 偶发计算错误                               │
│     现实案例:                                                   │
│       训练 loss 偶尔出尖刺,换节点就好                           │
│       → 跑 -r 3 发现 GPU5 Targeted Stress Test FAIL             │
│       → Tensor Core 物理损伤,但不触发 ECC(ECC 只管显存)      │
│                                                                 │
│  ② nccl-tests 节点内 AllReduce busbw                            │
│                                                                 │
│     什么问题:                                                   │
│       NVLink 链路没断(状态 Active,错误计数 0)                 │
│       但实际传输带宽衰退了 30%                                   │
│     检查方式:                                                   │
│       all_reduce_perf -b 1G -e 1G -g 8                          │
│       A100 期望 busbw > 350 GB/s                                │
│       某台只有 260 GB/s → 进一步用 p2pBandwidthLatencyTest       │
│       定位到 GPU4↔GPU5 之间 NVLink 带宽只有正常的一半           │
│                                                                 │
│  ③ ib_write_bw 跨节点裸带宽                                     │
│                                                                 │
│     什么问题:                                                   │
│       IB 端口 Active,速率显示 200Gb/s(ibstat 正常)           │
│       但实际跑数据只有 15 GB/s(期望 24 GB/s)                  │
│       原因可能是:光模块衰减、交换机端口背板问题、线缆弯折      │
│     检查方式:                                                   │
│       Server: ib_write_bw -d mlx5_0 --report_gbits              │
│       Client: ib_write_bw -d mlx5_0 <server_ip> --report_gbits  │
│       期望 ≥ 190 Gbps(~24 GB/s)                               │
│       低于 150 Gbps → 换线缆/光模块                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

怎么深度检查两台机器之间的带宽

bash 复制代码
策略1:相邻节点配对(最常用)
  node-0 ↔ node-1
  node-2 ↔ node-3
  node-4 ↔ node-5
  ...
  → 56 组测试,覆盖每台机器至少一次
  → 20 分钟跑完

ansible实现链条

bash 复制代码
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  第一步:生成配对清单                                        │
│                                                              │
│  管理节点上的 Python 脚本读取 inventory                      │
│  → 输出配对列表:                                            │
│    pair_1: server=node-0  client=node-1                      │
│    pair_2: server=node-2  client=node-3                      │
│    ...                                                       │
│                                                              │
│  第二步:Ansible 并行启动所有 server 端                      │
│                                                              │
│  对所有 server 节点并行执行:                                │
│    ib_write_bw -d mlx5_0 --report_gbits &                   │
│  server 进程后台等待 client 连入                             │
│                                                              │
│  第三步:Ansible 并行启动所有 client 端                      │
│                                                              │
│  等 server 全部就绪(sleep 5)                               │
│  对所有 client 节点并行执行:                                │
│    ib_write_bw -d mlx5_0 <对应server的IP> --report_gbits    │
│  client 跑完自动退出,server 也跟着退出                      │
│                                                              │
│  第四步:收集结果                                            │
│                                                              │
│  Ansible fetch 回所有 client 端的输出                        │
│  解析 BW 数值,和阈值比较                                    │
│  低于 190 Gbps 的标记异常                                    │
│                                                              │
│  第五步:汇总报告                                            │
│                                                              │
│  正常:pair node-0↔node-1: 196 Gbps ✓                       │
│  异常:pair node-4↔node-5: 87 Gbps ✗ ← 线缆/光模块问题     │
│  → 发飞书                                                    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

二、完整链路(每种巡检举两个指标示例)

当我通过PromQL自己算出来一些指标来放到grafana上展示的时候,这个实现可以grafana取的时候自己算一下,然后取出来,也可以Prometheus 存储的时候算一下,给这个指标一个新名字,然后grafana直接拿

grafana 实时算 随复杂度增加而变慢(临时调试) Prometheus预先算的话始终极快

日常巡检 vs 深度体检:带宽检查的本质区别

bash 复制代码
日常巡检做的事:

    mlxlink -d /dev/mst/mt4125_pciconf0 --port_type PCIE

    它只是 读一个寄存器的值,就像你看一眼仪表盘


┌──────────────────────────────────────────────────────────────────┐
│                     日常巡检:读状态寄存器                        │
│                                                                  │
│  做了什么:  问 PCIe 控制器 "你现在协商的宽度是多少?"            │
│  类比:      看汽车仪表盘上的转速表                               │
│  耗时:      < 0.1 秒                                            │
│  对业务影响:零(只读操作)                                       │
│  能发现:    宽度从 x16 降到了 x8(硬件层面已经降了)             │
│                                                                  │
│  它读到的是一个 "既成事实"                                       │
│  不是它去测的,是 PCIe 链路在启动时就协商好了                     │
│  或者运行中因为硬件故障自动降级了                                 │
└──────────────────────────────────────────────────────────────────┘

只有深度体检才能抓到的问题

bash 复制代码
┌────────────────────────┬──────────────────────────┬────────────────────────┐
│ 问题                    │ 日常巡检读寄存器看到的    │ 深度体检实际跑出来的    │
├────────────────────────┼──────────────────────────┼────────────────────────┤
│ NVLink 性能衰退         │ Link Active ✓ 看着正常   │ P2P 带宽只有150/250GB  │
│ GPU 静默计算错误        │ ECC计数=0 ✓ 看着正常     │ dcgmi -r 3 算出错结果  │
│ HBM 显存带宽劣化        │ 显存容量正常 ✓           │ bandwidthTest 实测偏低  │
│ PCIe 信号劣化(未降宽)   │ Width=x16 ✓ 还没降      │ 眼图测试 眼高不足       │
│ NCCL 通信路径异常       │ 所有端口都Active ✓       │ AllReduce busbw 偏低   │
│ GPU 高负载降频          │ 当前空闲频率正常 ✓       │ 压力测试时频率掉了     │
└────────────────────────┴──────────────────────────┴────────────────────────┘

2.1 实时监控层(7×24 自动)

IB/RoCE 端口状态实时监控链条

bash 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  第一步:自定义 exporter 采集端口状态                            │
│                                                                 │
│  写一个 Python 脚本,每 30 秒执行 ibstat / ethtool              │
│  把结果转成 Prometheus 格式,暴露 HTTP 端口 :9401               │
│                                                                 │
│  第二步:Prometheus 抓取                                        │
│                                                                 │
│  prometheus.yml 添加 scrape target :9401                        │
│  每 30 秒 pull 一次                                             │
│                                                                 │
│  第三步:告警规则                                               │
│                                                                 │
│  ib_port_state != 1 → AlertManager → 飞书                      │
│                                                                 │
│  第四步:Grafana 展示                                           │
│                                                                 │
│  状态表格:每个节点每个端口一行,红绿标记                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
复制代码
指标1: GPU 温度
指标2: ECC SBE 增长率

链路:
GPU 硬件 → DCGM API → dcgm-exporter(:9400) → Prometheus(每30s抓)
                                                      │
                                            ┌─────────┴──────────┐
                                            ↓                    ↓
                                        Grafana 面板          AlertManager
                                        (运维看大屏)         (飞书推送)

dcgm-exporter 部署(K8s DaemonSet,每个 GPU 节点跑一个):

yaml 复制代码
# 精髓部分
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: dcgm-exporter
spec:
  selector:
    matchLabels:
      app: dcgm-exporter
  template:
    spec:
      containers:
      - name: dcgm-exporter
        image: nvcr.io/nvidia/k8s/dcgm-exporter:3.2.5
        ports:
        - containerPort: 9400
        securityContext:
          privileged: true        # 必须,要访问 GPU 设备
        volumeMounts:
        - name: dev
          mountPath: /dev
      volumes:
      - name: dev
        hostPath:
          path: /dev

Prometheus 告警规则:

DCGM_FI_DEV_GPU_TEMP 才是指标(Metric),它是由 NVIDIA DCGM Exporter 采集并直接暴露给 Prometheus 的。

你写的这段代码是告警规则,它的逻辑是:

expr (表达式): 这是关键。它告诉 Prometheus:"去查一下所有 GPU 的温度,看看有没有大于 85 的。"

for: 5m: 这是一个过滤器,表示温度必须持续 5 分钟超过 85 度才会触发告警(防止尖峰瞬时误报)。

yaml 复制代码
# 温度
- alert: GPU_Temp_High
  expr: DCGM_FI_DEV_GPU_TEMP > 85
  for: 5m
  labels:
    severity: warning

# ECC 增长
- alert: GPU_ECC_SBE_Growing
  expr: increase(DCGM_FI_DEV_ECC_SBE_AGG_TOTAL[1h]) > 5
  for: 10m
  labels:
    severity: warning

Grafana 面板:

复制代码
Panel 1 PromQL:  DCGM_FI_DEV_GPU_TEMP
  → 折线图,按节点+GPU编号分组,一眼看谁温度高

Panel 2 PromQL:  increase(DCGM_FI_DEV_ECC_SBE_AGG_TOTAL[1h])
  → 折线图,正常是0平线,异常的卡会爬升

2.2 日常巡检层(每天凌晨 4 点)

复制代码
指标1: GDR 模块 + NVLink 错误计数 
指标2: PCIe 宽度(是否 x16)

链路:
cron(管理节点,4:00) → Ansible SSH → 每台GPU节点执行脚本
                                          │
                                   脚本输出结果
                                          │
                              Ansible fetch 回管理节点
                                          │
                              Python 汇总脚本解析
                                          │
                                   ┌──────┴──────┐
                                   ↓             ↓
                              飞书群消息     HTML 报告
                             (异常才发)    (存档可查)

Ansible playbook 精髓:

copy 模块的 content 参数把前两步 register 捕获的 stdout 拼到一起,写入目标主机上的 /tmp/check_{{ inventory_hostname }}.txt。

yaml 复制代码
# daily_check.yml
- hosts: ib_nodes          # A100/H20 节点组
  become: yes
  tasks:
    - name: 检查 IB 端口状态
      shell: |
        for dev in mlx5_0 mlx5_1; do
          STATE=$(ibstat $dev | grep "State:" | awk '{print $2}')
          RATE=$(ibstat $dev | grep "Rate:" | awk '{print $2}')
          echo "${dev}: State=${STATE} Rate=${RATE}"
          [ "$STATE" != "Active" ] && echo "[CRITICAL] ${dev} is DOWN"
        done
      register: ib_result

    - name: 检查 PCIe 宽度
      shell: |
        for dev in /dev/mst/mt*_pciconf0; do
          WIDTH=$(mlxlink -d $dev --port_type PCIE 2>/dev/null | grep "Width" | awk '{print $NF}')
          echo "${dev}: Width=${WIDTH}"
          [ "$WIDTH" != "x16" ] && echo "[WARNING] PCIe degraded to ${WIDTH}"
        done
      register: pcie_result

    - name: 保存结果
      copy:
        content: |
          === {{ inventory_hostname }} ===
          {{ ib_result.stdout }}
          {{ pcie_result.stdout }}
        dest: "/tmp/check_{{ inventory_hostname }}.txt"

    - name: 回收
      fetch:
        src: "/tmp/check_{{ inventory_hostname }}.txt"
        dest: "/data/daily_reports/"
        flat: yes

- hosts: management
  tasks:
    - name: 汇总发飞书
      shell: |
        cd /data/daily_reports/
        # 提取异常
        ISSUES=$(grep -h "\[CRITICAL\]\|\[WARNING\]" *.txt)
        if [ -n "$ISSUES" ]; then
          python3 /opt/scripts/send_feishu.py "巡检异常:\n${ISSUES}"
        else
          python3 /opt/scripts/send_feishu.py "巡检通过 ✓ 共检查 $(ls *.txt|wc -l) 台"
        fi

cron 配置:

bash 复制代码
# 管理节点 crontab
0 4 * * * ansible-playbook /opt/playbooks/daily_check.yml >> /var/log/daily_check.log 2>&1

飞书推送脚本精髓:

python 复制代码
# send_feishu.py
import sys, requests, json

WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxx"

msg = sys.argv[1]
requests.post(WEBHOOK, json={
    "msg_type": "text",
    "content": {"text": f"[GPU集群巡检] {msg}"}
})

运维看到的飞书消息:

复制代码
正常时:
  [GPU集群巡检] 巡检通过 ✓ 共检查 112 台

异常时:
  [GPU集群巡检] 巡检异常:
  [CRITICAL] node-15 mlx5_1: State=Down
  [WARNING] node-32 PCIe degraded to x8

2.3 深度体检层(每周日 / 验收 / 维修后)

跑带宽测试,不如nccl 和 dcgmi -r 3

Ansible 深度体检 Playbook
yaml 复制代码
# deep_check.yml
# 用法:ansible-playbook deep_check.yml --limit node-15,node-16
# 前提:目标节点已 cordon + drain,无训练任务

- hosts: all
  become: yes
  vars:
    report_dir: "/tmp/deep_check"
    nccl_test_bin: "/opt/nccl-tests/build/all_reduce_perf"
    p2p_test_bin: "/opt/cuda-samples/bin/p2pBandwidthLatencyTest"
    busbw_threshold_ib: 350      # 节点内 NVLink busbw 期望最低值
    busbw_threshold_roce: 100    # V100/4090 节点内期望最低值

  tasks:

    # ════════════════════════════════════════
    # 0. 准备
    # ════════════════════════════════════════
    - name: 创建报告目录
      file:
        path: "{{ report_dir }}"
        state: directory

    - name: 确认没有 GPU 进程在跑
      shell: nvidia-smi --query-compute-apps=pid --format=csv,noheader | wc -l
      register: gpu_procs
      failed_when: gpu_procs.stdout | int > 0
      # 如果有进程在跑,直接 fail,防止影响训练

    # ════════════════════════════════════════
    # 1. dcgmi diag -r 3(核心:抓静默计算错误)
    # ════════════════════════════════════════
    - name: 深度 GPU 诊断 (dcgmi diag -r 3, 约30分钟)
      shell: |
        dcgmi diag -r 3 2>&1 | tee {{ report_dir }}/dcgmi_r3.log
      register: dcgmi_result
      timeout: 2400    # 40分钟超时

    - name: 检查 dcgmi 结果
      shell: |
        if grep -q "FAIL" {{ report_dir }}/dcgmi_r3.log; then
          FAILED_GPUS=$(grep "FAIL" {{ report_dir }}/dcgmi_r3.log)
          echo "[CRITICAL] dcgmi diag FAILED: ${FAILED_GPUS}"
          exit 1
        else
          echo "dcgmi diag -r 3: ALL PASSED ✓"
        fi
      register: dcgmi_check

    # ════════════════════════════════════════
    # 2. NVLink P2P 带宽(核心:抓链路衰退)
    # ════════════════════════════════════════
    - name: P2P 带宽测试
      shell: |
        {{ p2p_test_bin }} 2>&1 | tee {{ report_dir }}/p2p.log
      register: p2p_result
      when: "'4090' not in ansible_facts['hostname']"
      # 4090 没有 NVLink,跳过

    - name: 检查 P2P 带宽
      shell: |
        # 提取单向带宽矩阵中非对角线的最小值
        MIN_BW=$(grep -A 100 "Unidirectional" {{ report_dir }}/p2p.log \
          | grep "^[[:space:]]*[0-7]" \
          | awk '{for(i=2;i<=NF;i++){if($i+0>0 && $i+0<min+0 || min==""){min=$i}}}END{print min}')
        
        echo "P2P 最低带宽: ${MIN_BW} GB/s"
        
        # A100 NVLink 期望 >200 GB/s
        if (( $(echo "$MIN_BW < 200" | bc -l) )); then
          echo "[WARNING] P2P 带宽偏低,可能有 NVLink 衰退"
          exit 1
        else
          echo "P2P bandwidth: OK ✓"
        fi
      register: p2p_check
      when: "'4090' not in ansible_facts['hostname']"
      ignore_errors: yes

    # ════════════════════════════════════════
    # 3. 节点内 NCCL AllReduce(综合验证)
    # ════════════════════════════════════════
    - name: NCCL 节点内 AllReduce 测试
      shell: |
        {{ nccl_test_bin }} \
          -b 1G -e 1G -g 8 -n 50 -w 10 \
          2>&1 | tee {{ report_dir }}/nccl_intra.log
      register: nccl_result

    - name: 检查 NCCL busbw
      shell: |
        BUSBW=$(grep "1073741824" {{ report_dir }}/nccl_intra.log \
          | awk '{print $7}' | head -1)
        
        echo "节点内 AllReduce busbw: ${BUSBW} GB/s"
        
        if (( $(echo "$BUSBW < {{ busbw_threshold_ib }}" | bc -l) )); then
          echo "[WARNING] busbw ${BUSBW} 低于阈值 {{ busbw_threshold_ib }}"
          exit 1
        else
          echo "NCCL busbw: OK ✓"
        fi
      register: nccl_check
      ignore_errors: yes

    # ════════════════════════════════════════
    # 4. 显存带宽测试(检测 HBM 衰退)
    # ════════════════════════════════════════
    - name: 显存带宽测试
      shell: |
        for i in $(seq 0 7); do
          /opt/cuda-samples/bin/bandwidthTest --device=$i --dtod \
            2>&1 | grep "Device to Device" | tail -1
        done | tee {{ report_dir }}/membw.log
      register: membw_result

    # ════════════════════════════════════════
    # 5. IB 网卡眼图(PCIe 信号质量)
    # ════════════════════════════════════════
    - name: PCIe 眼图测试
      shell: |
        for dev in /dev/mst/mt*_pciconf0; do
          echo "=== $dev ==="
          mlxlink -d $dev --port_type PCIE -e 2>&1
        done | tee {{ report_dir }}/eye.log
      register: eye_result
      when: "'mlxlink' in ansible_facts.packages | default([])"
      ignore_errors: yes

    # ════════════════════════════════════════
    # 6. 生成汇总报告
    # ════════════════════════════════════════
    - name: 生成节点报告
      shell: |
        cat > {{ report_dir }}/summary.txt << 'SUMMARY'
        ╔══════════════════════════════════════════╗
        ║  深度体检报告: {{ inventory_hostname }}
        ║  时间: $(date)
        ╠══════════════════════════════════════════╣
        ║
        ║  1. dcgmi diag -r 3:  {{ dcgmi_check.stdout_lines[-1] }}
        ║  2. P2P 带宽:         {{ p2p_check.stdout_lines[-1] | default('SKIPPED') }}
        ║  3. NCCL busbw:       {{ nccl_check.stdout_lines[-1] }}
        ║  4. 显存带宽:         见 membw.log
        ║  5. PCIe 眼图:        见 eye.log
        ║
        ╠══════════════════════════════════════════╣
        ║  总结:
        SUMMARY

        FAIL_COUNT=0
        [ "{{ dcgmi_check.rc }}" != "0" ] && FAIL_COUNT=$((FAIL_COUNT+1))
        [ "{{ p2p_check.rc | default(0) }}" != "0" ] && FAIL_COUNT=$((FAIL_COUNT+1))
        [ "{{ nccl_check.rc }}" != "0" ] && FAIL_COUNT=$((FAIL_COUNT+1))

        if [ $FAIL_COUNT -eq 0 ]; then
          echo "║  ✅ ALL PASSED" >> {{ report_dir }}/summary.txt
        else
          echo "║  ❌ $FAIL_COUNT 项异常" >> {{ report_dir }}/summary.txt
        fi
        echo "╚══════════════════════════════════════════╝" >> {{ report_dir }}/summary.txt

        cat {{ report_dir }}/summary.txt

    # ════════════════════════════════════════
    # 7. 回收报告
    # ════════════════════════════════════════
    - name: 回收报告到管理节点
      fetch:
        src: "{{ report_dir }}/{{ item }}"
        dest: "/data/deep_reports/{{ inventory_hostname }}/"
        flat: yes
      loop:
        - summary.txt
        - dcgmi_r3.log
        - nccl_intra.log
        - p2p.log
        - membw.log
        - eye.log

调用上面的 yaml
bash 复制代码
#!/bin/bash
# run_deep_check.sh
# 滚动深度体检入口脚本

BATCH_SIZE=2
NODES=$(kubectl get nodes -l nvidia.com/gpu=true -o jsonpath='{.items[*].metadata.name}')

echo "开始滚动深度体检,共 $(echo $NODES | wc -w) 台节点,每批 $BATCH_SIZE 台"

for BATCH in $(echo $NODES | xargs -n $BATCH_SIZE); do
  echo ""
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "当前批次: $BATCH"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

  # 1. cordon(禁止新任务调度)
  for N in $BATCH; do
    kubectl cordon $N
    echo "  cordon $N ✓"
  done

  # 2. drain(驱逐现有 Pod)
  for N in $BATCH; do
    kubectl drain $N \
      --ignore-daemonsets \
      --delete-emptydir-data \
      --timeout=600s \
      --grace-period=120
    echo "  drain $N ✓"
  done

  # 3. Ansible 跑深度体检
  LIMIT=$(echo $BATCH | tr ' ' ',')
  ansible-playbook deep_check.yml --limit "$LIMIT"

  # 4. 根据结果决定是否放回
  for N in $BATCH; do
    REPORT="/data/deep_reports/${N}/summary.txt"
    if grep -q "ALL PASSED" "$REPORT"; then
      kubectl uncordon $N
      echo "  ✅ $N 通过,已 uncordon"
    else
      echo "  ❌ $N 未通过,保持 cordon,通知运维"
      python3 /opt/scripts/send_feishu.py \
        "⚠️ 深度体检未通过: $N\n$(cat $REPORT)"
    fi
  done

  # 5. 间隔等集群稳定
  sleep 30
done

echo ""
echo "全部检完,汇总报告:"
python3 /opt/scripts/generate_html_report.py /data/deep_reports/
复制代码
执行时间线:

02:00  脚本启动
02:01  cordon node-0, node-1
02:05  drain 完成(Pod 迁走)
02:06  Ansible 开始体检
02:40  dcgmi diag -r 3 完成(30分钟)
02:45  nccl-tests + p2p 完成(5分钟)
02:46  结果判断,uncordon 或保持
02:47  开始下一批 node-2, node-3
...
周日下午  112 台全部检完

自动 cordon + drain

复制代码
实时监控:  ✅ 需要自动 cordon+drain
日常巡检:  ❌ 不需要自动,人工判断后手动操作

因为日常巡检的任务还能用
→ 贸然自动 drain 可能打断一个跑了 3 天的训练任务
→ 损失比"慢一点"大得多

自动 Cordon+Drain 完整链路

bash 复制代码
GPU 硬件故障
     │
     ▼
DCGM API 检测到异常
     │
     ▼
dcgm-exporter 暴露指标(:9400/metrics)
     │
     ▼
Prometheus 抓取(每 30 秒)
     │
     ▼
Alerting Rule 触发(PromQL 判断)
     │
     ▼
AlertManager 收到告警
     │
     ├──→ 飞书通知运维(通知人知道)
     │
     └──→ Webhook 调用自动修复服务(自动止损)
              │
              ▼
         auto-remediation 服务
              │
              ├── kubectl cordon <node>(禁止新任务调度)
              ├── kubectl drain <node>(驱逐现有 Pod)
              └── 打标签记录原因和时间

Auto-Remediation 服务:参数传递全链条


完整链条

复制代码
Step 1: GPU 硬件故障发生
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  node-23 的 GPU5 发生 ECC DBE 错误
  
  DCGM API 读到:
    GPU index=5, DBE count 从 0 变成 1

        │
        │ dcgm-exporter 每 30 秒采集一次
        ▼

Step 2: dcgm-exporter 暴露指标
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  curl node-23:9400/metrics 返回:
  
  DCGM_FI_DEV_ECC_DBE_VOL_TOTAL{
    gpu="5",                          ← 哪张卡
    UUID="GPU-abc123",
    Hostname="node-23",               ← 哪台机器(★ 后面一路传下去)
    pod="llama3-worker-3",
    namespace="team-nlp"
  } 1

        │
        │ Prometheus 每 30 秒来 Pull 一次
        ▼

Step 3: Prometheus 抓取并执行告警规则
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  告警规则:
    expr: increase(DCGM_FI_DEV_ECC_DBE_VOL_TOTAL[2m]) > 0
  
  Prometheus 算了一下:
    node-23 GPU5 的 DBE 在 2 分钟内从 0 增长到 1
    increase = 1 > 0 → 命中!

  生成一条 Alert 对象,里面的字段来自规则定义:
    labels:
      alertname: "GPU_ECC_DBE_AutoCordon"
      severity: "critical"
      auto_action: "cordon_drain"      ← 规则里写死的标签
      gpu: "5"                         ← 从指标 label 继承
      Hostname: "node-23"              ← 从指标 label 继承

    annotations:
      node: "node-23"                  ← 规则里用模板 {{ $labels.Hostname }} 填充
      reason: "ECC DBE detected on GPU5"  ← 规则里用模板拼的字符串

        │
        │ Prometheus 把 Alert 推给 AlertManager
        ▼

Step 4: AlertManager 路由分发
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  AlertManager 收到 Alert,看 labels 做路由:
    auto_action == "cordon_drain"  → 匹配到 receiver: auto-remediation
    severity == "critical"         → 同时匹配到 receiver: urgent-feishu
                                     (因为 continue: true,两个都触发)

  分发动作 1:POST 到自动修复服务
    URL: http://auto-remediation-svc:8080/alert
    Body(AlertManager 标准格式):
    {
      "alerts": [
        {
          "status": "firing",
          "labels": {
            "alertname": "GPU_ECC_DBE_AutoCordon",
            "auto_action": "cordon_drain",
            "Hostname": "node-23",
            "gpu": "5"
          },
          "annotations": {
            "node": "node-23",              ← ★ 节点名在这里
            "reason": "ECC DBE detected on GPU5"  ← ★ 原因在这里
          }
        }
      ]
    }

  分发动作 2:POST 到飞书 webhook
    → 运维群收到消息

        │
        │ HTTP POST
        ▼

Step 5: auto-remediation 服务处理
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Flask 服务收到 POST 请求

  解析参数:
    alert['status']                → "firing"(正在发生,不是 resolved)
    alert['labels']['auto_action'] → "cordon_drain"(确认需要自动处置)
    alert['annotations']['node']   → "node-23"(★ 拿到节点名)
    alert['annotations']['reason'] → "ECC DBE detected on GPU5"(★ 拿到原因)

  限流检查:
    过去 1 小时已 cordon 几台?
    < 3 台 → 继续
    ≥ 3 台 → 暂停,发紧急飞书通知人工介入

        │
        │ 通过限流检查
        ▼

Step 6: 执行 Cordon
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  调用 K8s API:
    v1.patch_node("node-23", {
      "spec": {"unschedulable": true},     ← 禁止新 Pod 调度到这台
      "metadata": {
        "labels": {
          "gpu-health": "failed",                        ← ★ 审计标签1
          "cordon-reason": "ECC DBE detected on GPU5",   ← ★ 审计标签2
          "cordon-time": "20250110-143052"                ← ★ 审计标签3
        }
      }
    })

  效果:
    node-23 被标记为 SchedulingDisabled
    新的训练任务不会再被调度到这台机器
    已在跑的 Pod 暂时还在(下一步驱逐)

        │
        ▼

Step 7: 执行 Drain(驱逐 Pod)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  列出 node-23 上所有 Pod:
    v1.list_namespaced_pod(field_selector="spec.nodeName=node-23")

  逐个判断:
    pod=dcgm-exporter    owner=DaemonSet  → 跳过(监控不能停)
    pod=fluentd          owner=DaemonSet  → 跳过(日志采集不能停)
    pod=kube-proxy       namespace=kube-system → 跳过(系统组件)
    pod=llama3-worker-3  owner=Job        → 驱逐!

  驱逐:
    v1.create_namespaced_pod_eviction("llama3-worker-3", "team-nlp", {
      grace_period_seconds: 120           ← 给 2 分钟保存 checkpoint
    })

  效果:
    llama3-worker-3 收到 SIGTERM
    训练框架捕获信号 → 保存紧急 checkpoint → 退出
    K8s 调度器把这个 Pod 重新调度到其他健康节点
    训练从 checkpoint 恢复继续跑

        │
        ▼

Step 8: 完成,等待人工修复
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  node-23 当前状态:
    Status: Ready,SchedulingDisabled
    Labels:
      gpu-health=failed
      cordon-reason=ECC DBE detected on GPU5
      cordon-time=20250110-143052
  
  没有任何训练 Pod 在跑
  只剩 DaemonSet 的监控/日志 Pod
  等运维来修

审计日志:存在哪里,怎么看

复制代码
存储位置有两层:

第一层:Node Label(最直观)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  查看所有被自动下线的节点:
    kubectl get nodes -l gpu-health=failed
    
    NAME      STATUS                    LABELS
    node-23   Ready,SchedulingDisabled  gpu-health=failed
                                        cordon-reason=ECC DBE detected on GPU5
                                        cordon-time=20250110-143052
    node-47   Ready,SchedulingDisabled  gpu-health=failed
                                        cordon-reason=XID 79 GPU fell off bus
                                        cordon-time=20250108-092011

  → 一条命令看到:哪些节点坏了、什么原因、什么时候下线的


第二层:服务日志(详细过程)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  auto-remediation 服务的标准输出(被 K8s 采集):
    kubectl logs deployment/auto-remediation -n monitoring

    2025-01-10 14:30:52 INFO  收到自动处置告警: node=node-23 reason=ECC DBE detected on GPU5
    2025-01-10 14:30:52 INFO  限流检查: 过去1小时已cordon 0 台,允许继续
    2025-01-10 14:30:53 INFO  cordon node-23 ✓
    2025-01-10 14:30:53 INFO  evicted team-nlp/llama3-worker-3
    2025-01-10 14:30:53 INFO  drain node-23 完成

  这些日志通过 fluentd → Elasticsearch → Kibana 可长期查询


第三层:飞书消息(人人可见)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  运维群收到:
    ⚠️ [自动处置] node-23 已自动 cordon+drain
    原因:ECC DBE detected on GPU5
    时间:2025-01-10 14:30:52
    影响 Pod:team-nlp/llama3-worker-3
    请安排维修后手动 uncordon

修复后放回流程

复制代码
运维修完后:

  1. 确认修复
     dcgmi diag -r 3          → PASS
     nccl-tests busbw         → 正常

  2. 清除标签 + uncordon
     kubectl label node node-23 gpu-health- cordon-reason- cordon-time-
     kubectl uncordon node-23

  3. 节点重新接受任务调度
相关推荐
gs8014010 分钟前
记一次多 Agent 架构在单节点 K8s 触发的 PID 耗尽与 Pod 驱逐(Evicted)大摸排
容器·架构·kubernetes
bukeyiwanshui1 小时前
20260701 k8s Metric Server
容器·贪心算法·kubernetes
Zhu7585 小时前
在k8s集群部署ApacheHadoop单节点单数据副本
云原生·容器·kubernetes
摇滚侠5 小时前
云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 51-60
java·云原生·kubernetes
bukeyiwanshui5 小时前
20260703 K8s ECShop商城项目
云原生·容器·kubernetes
糖果店的幽灵5 小时前
混沌测试从入门到精通(容器与K8s)
云原生·容器·kubernetes
蜀道山老天师18 小时前
K8s Secret 与 ConfigMap 配置管理及动态更新实战详解
云原生·容器·kubernetes
bukeyiwanshui21 小时前
20260624 k8s pod命令
云原生·容器·kubernetes
爱好曙光1 天前
云原生混沌工程实战:基于Litmus的故障注入与弹性测试
云原生·kubernetes·devops·混沌工程·故障注入·litmus·弹性测试