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. 节点重新接受任务调度
相关推荐
AOwhisky4 小时前
Kubernetes调度与服务暴露:从“定时任务”到“服务发现”的完全指南
linux·运维·云原生·容器·kubernetes·服务发现
Cyber4K4 小时前
【Kubernetes专项】温故而知新,重温技术原理(6)
云原生·容器·kubernetes
运维老郭6 小时前
K8s故障排查:一条分层排查路径解决99%线上问题
运维·云原生·kubernetes
Trival_dream7 小时前
应用与实例的关系
java·docker·kubernetes
Nice_Fold19 小时前
Kubernetes DaemonSet、StatefulSet与Service(自用笔记)
笔记·容器·kubernetes
Java后端的Ai之路1 天前
Kubernetes是什么?(小白入门版)
云原生·容器·kubernetes·教程
EAIReport1 天前
Docker与K8s核心解析:共同性、差异性及实战适配指南
docker·容器·kubernetes
长安链开源社区1 天前
动手开发 | 如何通过k8s部署长安链
云原生·容器·kubernetes·区块链
亚空间仓鼠1 天前
Kubernetes技术入门与实践(五):DaemonSet与StatefulSet
容器·贪心算法·kubernetes