[NVSentinel] metadata-collector模块调研

NVSentinel metadata-collector模块调研

基于 NVSentinel 文档与 metadata-collector/ 代码的实现分析。文档引用指向 NVIDIA/NVSentinel main 分支。

官方文档:


1. 模块定位

一句话:metadata-collectorDaemonSet 形式跑在每个 GPU 节点上,采集本机 GPU(及 NVSwitch / NIC 等相关拓扑)信息,写入节点本地共享存储,并补充 Pod↔GPU 映射,供其他模块只读消费。

metadata-collector 是 NVSentinel 的 节点级硬件清单 + Pod↔GPU 映射发布器。它不消费 HealthEvent,也不做隔离/修复决策;它为下游监视器与 node-drainer 提供统一的硬件语义与设备归属信息。

text 复制代码
NVML / nvidia-smi / kubelet
            │
            ▼
   metadata-collector (DaemonSet, 每 GPU 节点一份)
            │
            ├─► /var/lib/nvsentinel/gpu_metadata.json
            │         ├─ gpu-health-monitor(UUID / PCI / 热限 / NVLink 误报抑制)
            │         ├─ syslog-health-monitor(XID PCI→GPU、SXID NVSwitch 映射)
            │         └─ nic-health-monitor(GPU↔NIC topo、NUMA)
            │
            └─► Pod 注解 dgxc.nvidia.com/devices
                      └─ node-drainer(COMPONENT_RESET 部分驱逐)
角色 职责
metadata-collector 采集 GPU/NVSwitch/NIC 拓扑并写本地 JSON;周期性标注 Pod 占用的 GPU UUID
health monitors 读 JSON,把底层错误码关联到具体 GPU / 链路
node-drainer 读 Pod 注解,只驱逐占用故障 GPU 的 Pod(部分 drain)
fault-remediation 不直接读 metadata;间接依赖「事件里是否有 GPU_UUID」以及 node-drainer 是否能部分 drain

为什么需要它(ADR-010):各 monitor 语言/运行时不同,若各自查 NVML,标识不一致、重复特权、难测。统一落盘 JSON + K8s 注解后,跨组件用同一契约。


2. 代码结构

text 复制代码
metadata-collector/
├── main.go                         # 两阶段生命周期:Collect 一次 → Mapper 循环
├── Dockerfile                      # CUDA base + CGO 链接 NVML
├── Makefile                        # CGO_ENABLED=1、lint-test
├── go.mod
└── pkg/
    ├── collector/collector.go      # 编排 NVML + NIC topo → model.GPUMetadata
    ├── writer/writer.go            # 原子写 JSON(.tmp → rename,chmod 0644)
    ├── nvml/
    │   ├── wrapper.go              # NVML 封装:Init/GetGPUInfo/ChassisSerial
    │   ├── topology.go             # CollectNVLinkTopology(NVML + 解析回退)
    │   └── nvlink_parser.go        # 解析 `nvidia-smi nvlink -R`
    ├── nic/topo_parser.go          # 解析 `nvidia-smi topo -m`
    └── mapper/
        ├── mapper.go               # Pod→GPU 注解增删改
        ├── httpsclient.go          # Kubelet HTTPS GET /pods
        └── grpcclient.go           # Kubelet PodResourcesLister gRPC

共享契约(下游都依赖):

路径 内容
data-models/pkg/model/gpu_metadata.go GPUMetadata / GPUInfo / NVLink JSON schema
data-models/pkg/model/pod_device_annotation.go dgxc.nvidia.com/devices 注解名与结构

Helm Chart:distros/kubernetes/nvsentinel/charts/metadata-collector/

依赖:github.com/NVIDIA/go-nvml、Kubernetes client-go、commons logger;构建需 CGO


3. 运行时架构:两阶段生命周期

入口在 main.go。进程模型很清晰:先一次性采硬件,再永久跑 mapper 。任一步失败则 os.Exit(1),由 DaemonSet 重启。

text 复制代码
main
 ├─ runCollector(ctx)     # 阻塞到成功写完 JSON(或失败退出)
 │    ├─ NVMLWrapper.Init()
 │    ├─ collector.Collect()
 │    ├─ writer.Write(gpu_metadata.json)
 │    └─ NVMLWrapper.Shutdown()
 └─ runMapper(ctx)        # 每 30s 一轮,直到 SIGTERM
      └─ PodDeviceMapper.UpdatePodDevicesAnnotations()

3.1 常量与 flag

符号 含义
defaultOutputPath /var/lib/nvsentinel/gpu_metadata.json 默认落盘路径
defaultPodDeviceMonitorPeriod 30s mapper 周期
--output-path 覆盖默认路径 Chart 注入为 global.metadataPath

3.2 关键设计点:元数据不是周期刷新

runCollector 只在 Pod 启动时执行一次 。GPU 热插拔、驱动升级、fabric-manager 晚于采集完成链路训练等情况,不会自动更新 JSON,除非 DaemonSet Pod 重启。

下游读者需自行处理「文件缺失 / 字段为 nil / active link 为 0」------见 §8、§11。

3.3 产物一览

产物 目标 更新频率
GPU 元数据 JSON hostPath 文件 Pod 启动一次
Pod 设备注解 dgxc.nvidia.com/devices 约每 30s(有变化才 patch)

不写:Node label / Node annotation / ConfigMap(ConfigMap 仅出现在 e2e 注入辅助脚本里)。


4. 采集流水线(collector)

4.1 编排入口

pkg/collector/collector.goCollect

  1. GetDeviceCount / Hostname / GetDriverVersion(失败则 整次 Collect 失败
  2. prepareTopologyData:建 PCI→Device map,解析 nvidia-smi nvlink -R(失败则 Warn,remote_link_id 退化为 -1
  3. collectGPUData:逐 GPU GetGPUInfo + CollectNVLinkTopology;GPU0 在驱动 ≥ R560 时取 chassis serial
  4. 所有 GPU 的 NUMANode 先置 -1,再 populateNICTopologynvidia-smi topo -m
go 复制代码
// Collect 主序(简化)
count := nvml.GetDeviceCount()
metadata := &GPUMetadata{Version: "1.0", Timestamp: RFC3339, NodeName, DriverVersion, GPUs: ...}
prepareTopologyData(ctx)          // deviceMap + parsed NVLink topology
collectGPUData(...)               // 逐卡;NVLink 失败只 Warn,卡仍入库
for i := range GPUs { NUMANode = -1 }
populateNICTopology(ctx, metadata) // NIC topo 失败不拖垮整次 Collect

4.2 致命 vs 降级

失败点 行为 结果
NVML Init / device count / GetGPUInfo 返回 error 无 JSON,Pod CrashLoop
nvidia-smi nvlink -R 解析失败 Warn,空 parsedTopology JSON 仍写出;remote_link_id=-1
单卡 CollectNVLinkTopology 失败 Warn 该卡仍进 gpus[],链路字段可能不全
nvidia-smi topo -m 失败 Warn,直接 return nic_topologynuma_node 保持 -1
topo 列 GPU 数 ≠ NVML count Warn,丢弃 nic/numa 避免错位矩阵误导 nic-health-monitor
无 IB NIC 列 Info nic_topology 为空
驱动 major < 560 跳过 chassis serial chassis_serial 为 null

supportsChassisSerial 解析 driverVersion 的 major(. 前第一段),阈值常量 minSupportedMajorVersion = 560

4.3 NVML 封装(pkg/nvml/wrapper.go

NVMLWrappergo-nvml 的薄封装,关键能力:

方法 作用
Init / Shutdown nvml.Init / Shutdown
GetGPUInfo(index) UUID、规范化 PCI、序列号、设备名、slowdown_tlimit_cFI_DEV_TEMPERATURE_SLOWDOWN_TLIMIT
GetChassisSerial(index) GetPlatformInfo(通常只对 GPU0)
BuildDeviceMap PCI → Device,供 NVLink 反向查 remote link

PCI 地址会做 normalize(小写等),保证与 syslog 里 PCI 字符串可匹配。

CollectNVLinkTopology 核心逻辑:

  1. 遍历 nvml.NVLINK_MAX_LINKS
  2. GetNvLinkState 成功 → 计入 nvlink_link_count不论是否 ENABLED
  3. FEATURE_ENABLED → 计入 nvlink_active_link_count,并查 remote PCI / device type
  4. 只把 remote 为 NVLINK_DEVICE_TYPE_SWITCH 的链路写入 gpuInfo.NVLinks ,并收集 NVSwitch PCI 集合到顶层 nvswitches[]
  5. remote_link_id:优先用 nvidia-smi nvlink -R 解析结果;否则 NVML 反向查找;再否则 -1

代码注释明确区分两类硬件语义(对理解 gpu-health-monitor 误报抑制很重要):

  • 无 NVLink 硅片 (如部分 L40/A40):GetNvLinkState 常返回 NOT_SUPPORTED → count 为 0
  • 可插 NVLink bridge 的 PCIe 卡 (A100/H100 PCIe):无桥时也可能 SUCCESS 且 active=0 → 不能 单凭 nvlink_link_count>0 断定「正在用 NVLink」
  • SXM/HGX:启动时 active=0 常表示 fabric-manager 尚未训完链路 → 消费者应视为 UNKNOWN,而非「无 NVLink」

4.5 GPU↔NIC 拓扑(pkg/nic/topo_parser.go

RunTopoCommand 执行 nvidia-smi topo -m(超时约 30s),ParseTopoMatrix

  • 识别 GPU 列(GPU\d+)与 NIC 列(mlx5_* / NIC\d+ / ib* / roce* 等)
  • 处理 NIC LegendNIC0 → mlx5_0
  • 解析每卡 NUMA Affinity(Grace/GB200 上可能是逗号/区间;取可用整数值)
  • 单元格合法值:XPIXPXBPHBNODESYSNV<n>

metadata-collector 只原样发布矩阵 ,不做 management/compute NIC 分类;分类在 nic-health-monitor/pkg/topology


5. JSON 契约(gpu_metadata.json

定义见 data-models/pkg/model/gpu_metadata.goversion 写死 "1.0"timestamp 为 UTC RFC3339。

5.1 顶层 GPUMetadata

JSON 字段 类型 说明
version string "1.0"
timestamp string 采集时刻
node_name string os.Hostname()(注意:Chart 虽注入 NODE_NAME,当前 Go 代码用 hostname)
driver_version string NVML
chassis_serial string | null 驱动 ≥ R560
gpus array 见下
nvswitches string\[\] NVSwitch PCI 去重列表
nic_topology mapstring\[\]string 可选;nic_topology[nic][i] 对齐 gpus[i]

5.2 gpus[] / GPUInfo

字段 说明
gpu_id NVML index
uuid / pci_address / serial_number / device_name 标识
nvlinks[] 仅到 NVSwitch 的链路:link_idremote_pci_addressremote_link_id
nvlink_link_count 可查询状态的链路数;缺省(nil)=未知,0=确认无 NVLink 硅片路径
nvlink_active_link_count 采集时 ACTIVE 数;语义见 §4.4
numa_node 来自 topo -m;不可用时 -1
slowdown_tlimit_c 慢降频温度偏移 °C;不支持则省略

5.3 原子写入(writer.go

text 复制代码
MarshalIndent → 写 output.tmp (先 0600) → chmod 0644 → rename 到正式路径

chmod 0644 是刻意设计:collector 常以 root 运行,而 syslog / nic monitor 可能非 root 读同一 hostPath;若保持 0600,下游会启动失败。rename 保证读者不会读到半截 JSON。


6. Pod 设备映射(mapper)

6.1 目标注解

常量:model.PodDeviceAnnotationName = "dgxc.nvidia.com/devices"

示例:

json 复制代码
{
  "devices": {
    "nvidia.com/gpu": ["GPU-455d8f70-2051-db6c-0430-ffc457bff834"],
    "nvidia.com/pgpu": ["GPU-..."]
  }
}

资源名白名单来自 EntityTypeToResourceNames["GPU_UUID"]nvidia.com/gpunvidia.com/pgpu

6.2 每轮步骤(UpdatePodDevicesAnnotations

代码大段注释写得很清楚,摘要如下:

  1. Kubelet HTTPS /pods :列出本节点 Pod(等价于 API fieldSelector=spec.nodeName=...,但走本地 kubelet,减轻 apiserver 压力)
  2. Kubelet PodResourcesLister gRPC (Unix socket,挂载 /var/lib/kubelet/pod-resources):拿到每个 Pod 实际分到的 GPU UUID(device plugin 或 DRA)
  3. 对比现有注解 → MergePatch 增改 / JSONPatch 删除;无变化不写

jsonPatchPath 使用 JSON Pointer 转义:/metadata/annotations/dgxc.nvidia.com~1devices/~1)。

6.3 已知语义限制(上游注释原文意图)

问题 影响
只在「注解内容变化」时 patch node-drainer 无法靠心跳发现陈旧注解;只能靠「请求了 GPU 却没有注解」间接发现
PodResourcesLister 可能仍报告终态 Pod 占用设备(K8s <1.34 行为) node-drainer 忽略终态 Pod,一般无害;设备迁到新 Pod 后会从旧注解移除
上游规划 K8s ≥1.35 ResourceHealthStatus 成熟后,可考虑用 container status 替代本 mapper

mapper 错误(kubelet 不可达、patch 失败且非 NotFound)会 终止进程,与 collector 同样走 CrashLoop。


7. Kubernetes 部署形态

7.1 工作负载

  • Kind :DaemonSet(charts/metadata-collector/templates/daemonset.yaml
  • 开关 :父 Chart global.metadataCollector.enabled(默认 true)
  • 滚动maxUnavailable: 5%

7.2 调度条件(硬绑定)

yaml 复制代码
nodeSelector:
  nvidia.com/gpu.present: "true"
  nvsentinel.dgxc.nvidia.com/driver.installed: "true"

即:依赖 GPU Operator / labeler 打上「有 GPU + 驱动已装」标签后才会调度。无这些标签的节点不会跑 collector,下游也就没有本机 JSON。

7.3 Pod 安全与网络

设置 原因
hostNetwork true 访问本机 kubelet HTTPS
hostPID true 与节点进程视图一致
privileged + runAsUser: 0 true NVML / 设备节点;覆盖镜像默认非 root 用户
runtimeClassName 默认 "nvidia" GPU 运行时;CRI-O 环境可在 values 里省略

7.4 Volume

Volume hostPath 用途
output /var/lib/nvsentinel gpu_metadata.json,与 monitors 共享
sys /sys(ro) 拓扑/设备相关
pod-gpu-resources /var/lib/kubelet/pod-resources PodResourcesLister socket

7.5 RBAC

资源 动词 用途
pods patch 写/删设备注解
nodes/proxy get 经 SA 访问 kubelet /pods

7.6 Helm 旋钮

父 Chart:

Key 默认 作用
global.metadataCollector.enabled true 是否部署
global.metadataPath /var/lib/nvsentinel/gpu_metadata.json --output-path,且与 monitors 路径一致

子 Chart values.yaml

Key 默认 作用
image.repository ghcr.io/nvidia/nvsentinel/metadata-collector
runtimeClassName "nvidia"
kubeletHost Downward API status.hostIP mapper 访问 kubelet 的主机;可改为 {} 回退 localhost
resources 100m/128Mi → 500m/256Mi

应用本身几乎只有一个业务 flag:--output-path


8. 下游消费者(谁读、怎么读)

8.1 gpu-health-monitor

  • 路径:health-monitors/gpu-health-monitor/gpu_health_monitor/metadata/reader.py
  • 读同一 hostPath(--metadata-path / global.metadataPath
  • 典型用途:
    • get_gpu_uuid(gpu_id):给 DCGM 事件补 UUID(影响 COMPONENT_RESET / 部分 drain)
    • PCI、chassis serial
    • slowdown_tlimit_c:热余量阈值(设计 042)
    • classify_nvlink_down:结合 nvlink_*_count 抑制 PCIe 卡上的 NVLink Down 误报
  • 行为:懒加载;文件暂不存在时可重试,不永久缓存「空」

8.2 syslog-health-monitor

  • 路径:health-monitors/syslog-health-monitor/pkg/metadata/reader.go
  • 用途:
    • GetGPUByPCI:XID 日志里的 PCI → GPU UUID/序列号
    • GetGPUByNVSwitchLink:SXID ↔ NVSwitch PCI + link
    • GetDriverVersion:选择 XID 解码表
  • 无元数据时,错误关联粒度会退化到「节点级」而非「某张卡」

8.3 nic-health-monitor

  • 路径:health-monitors/nic-health-monitor/pkg/topology/topology.go
  • 硬依赖 nic_topology + 有效 GPU NUMA(除非配置 override 绕过)
  • 启动时若文件缺失 / gpus 空 / nic_topology 空,进程可直接退出
  • 用矩阵区分 management / compute / storage NIC

8.4 node-drainer

  • 路径:node-drainer/pkg/informers/informers.go
  • 读的是 Pod 注解,不是 JSON 文件
  • filterPodsUsingEntityCOMPONENT_RESET + 实体类型 GPU_UUID 时只驱逐占用该 UUID 的 Pod
  • 若启用相关开关,请求了 GPU 却没有 dgxc.nvidia.com/devices 可能导致 drain 失败
  • Chart 侧:partialDrainEnableddrainGPUPods

8.5 fault-remediation

  • 无直接依赖 metadata-collector 代码或 JSON
  • 间接关系:HealthEvent 若缺 GPU UUID,部分部署会把 COMPONENT_RESET 降级 为整节点动作(如 RESTART_VM)------见 fault-remediation values 注释
  • 部分 drain 开启时,依赖 node-drainer ↔ 注解链路畅通

8.6 测试辅助

  • tests/helpers/metadata.go:e2e 用 ConfigMap + 拷贝 Pod 注入假元数据
  • tilt/inject-nic-metadata-stub.sh:本地 stub

9. 可观测性

能力 现状
Prometheus /metrics
readiness / liveness DaemonSet 未配置
日志 commons 结构化 slog;agent 名 metadata-collector
健康模型 Collect 成功后 Pod 保持 Running;mapper 每 30s 打 podCount;任一步 fatal → exit 1

排障时优先看:NVML init 失败、nvidia-smi 不在 PATH、kubelet socket/HTTPS、nodeSelector 标签是否齐全、hostPath 权限是否 0644。


10. 测试

包内 *_test.go 覆盖:

重点
collector chassis serial 门控、NIC topo 填充、GPU 数量不一致丢弃
nic topo 解析(换行表头、Legend、ANSI、NUMA)
nvml nvlink -R 输出解析
writer 原子写与权限
mapper patch 决策、HTTPS/gRPC client(mock)

make lint-testCGO_ENABLED=1


11. 局限与 NVIDIA 耦合

11.1 行为局限

  1. 元数据一次性采集:驱动/链路晚于启动变化需重启 Pod
  2. SXM 启动窗口nvlink_active_link_count=0 可能是 fabric 未就绪
  3. 注解无 TTL:只能靠变更 patch;无法主动标记 stale
  4. Dockerfile vs Chart:镜像默认非 root,Chart 强制 root+privileged
  5. ADR-010 与实现略有漂移:设计文曾提 init-container 模式;现实现为常驻 DaemonSet(内联一次 Collect + 循环 Mapper)

11.2 硬依赖(NVIDIA 栈)

  • NVIDIA 驱动 + NVML(go-nvml
  • 节点上可执行的 nvidia-smi(nvlink / topo)
  • runtimeClassName: nvidia(或等价 GPU runtime)
  • 节点标签 nvidia.com/gpu.presentnvsentinel.dgxc.nvidia.com/driver.installed
  • Device plugin / DRA 资源名:nvidia.com/gpunvidia.com/pgpu
  • 构建基镜像:CUDA(Dockerfile 中 nvcr.io/nvidia/cuda:...

12. 与主链路的关系(速查)

text 复制代码
正常健康闭环:
  metadata-collector 写 JSON + 注解
       ↓
  gpu/syslog/nic monitors 读 JSON → HealthEvent(可带 GPU_UUID)
       ↓
  platform-connectors → MongoDB
       ↓
  fault-quarantine → node-drainer(注解决定部分 drain)
       ↓
  fault-remediation → 维护 CR(如 GPUReset,按 UUID)

关掉 global.metadataCollector.enabled 时:

  • 监视器仍可能上报事件,但 缺 UUID / 拓扑 / SXID 精确映射
  • 部分 drain / 精确 COMPONENT_RESET 能力显著下降
  • nic-health-monitor 往往无法启动

13. 建议阅读顺序

  1. metadata-collector/main.go --- 两阶段生命周期
  2. data-models/pkg/model/gpu_metadata.go --- 对外契约
  3. pkg/collector/collector.go --- 编排与降级策略
  4. pkg/nvml/wrapper.go + topology.go --- NVML / NVLink
  5. pkg/nvml/nvlink_parser.go + pkg/nic/topo_parser.go --- CLI 解析
  6. pkg/mapper/mapper.go(含大段注释)--- 注解语义与限制
  7. charts/metadata-collector/templates/daemonset.yaml --- 调度与挂载
  8. docs/designs/010-metadata-retrieval.md --- 设计动机
  9. 任选一个消费者深挖:
    • GPU:gpu_health_monitor/metadata/reader.py
    • Syslog:syslog-health-monitor/pkg/metadata/reader.go
    • NIC:nic-health-monitor/pkg/topology/topology.go
    • Drain:node-drainer/pkg/informers/informers.go

14. 小结

metadata-collector 是 NVSentinel 节点侧事实层 :用 NVML + nvidia-smi 固化 GPU/NVSwitch/NIC 拓扑到共享 JSON,并用 kubelet 本地 API 把「哪张 GPU 属于哪个 Pod」暴露成集群可观测注解。它本身不做故障决策,但 gpu-health-monitor 的 UUID enrichment、syslog 的 PCI/SXID 关联、nic 拓扑分类、node-drainer 的部分驱逐 都建立在它之上。理解其「一次采集 + 周期注解」与降级语义,是排查「为何事件没有 GPU_UUID / 为何无法 partial drain / 为何 NVLink 误报抑制失效」的关键入口。

相关推荐
ltl3 小时前
Cilium 可观测口径:status、metrics 语义与 bpftool 边界
kubernetes
AAA@峥9 小时前
官方 Dashboard 还是 Kuboard?Kubernetes 可视化管理工具深度对比
云原生·容器·kubernetes
mounter6251 天前
MACsec 全景解析:从技术演进、核心架构到 DPU 硬件卸载与 K8s 大规模调度实战
linux·kubernetes·linux kernel·kernel·macsec
众人皆醒我独醉1 天前
AI 工作负载可观测性:DCGM + Prometheus + Grafana
面试·llm·gpu
皮皮蟹虾饺1 天前
NCCL 源码解析:通信器从出生到消亡的完整一生
linux·人工智能·ubuntu·语言模型·kubernetes
众人皆醒我独醉1 天前
GPU 集群 IaC:Terraform + Ansible 部署自动化
面试·ansible·gpu
Smoothcloud_润云1 天前
从“模型服务”到“Agent 调度”:AI 推理基础设施为什么正在重构?
llm·agent·gpu
2401_834636991 天前
保姆级 K8s 运维:RBAC 认证 + Dashboard+Local/NFS 动态卷实战
运维·容器·kubernetes
爱莉希雅&&&1 天前
Kubernetes Service 完整学习笔记
笔记·学习·kubernetes