NVSentinel 数据流梳理
原文参考:https://docs.nvidia.com/nvsentinel/architecture/data-flow
一、整体架构
1. 设计思路
从公开文档和仓库结构看,NVSentinel 的主链路是事件驱动的:节点侧组件先把异常整理成 HealthEvent,platform-connectors 负责入库和同步 Kubernetes 状态,后面的隔离、驱逐、修复模块再从事件存储里异步消费。MongoDB Change Streams 在这里承担的是事件分发和状态流转的作用。
主链路可以按下面理解:
- 节点上的采集器(生产者)通过本地 gRPC 把故障事件报上来;
- Platform Connectors 负责集中接收、做格式校验,然后持久化到存储层;
- 集群控制面的各个模块订阅数据库变更流,异步消费处理故障;
- 隔离、驱逐、修复这些动作主要通过 Kubernetes API 执行。
2. Preflight 预检(Pod 准入检查)
这是一条独立的链路,不走 gRPC 上报:
- 以业务 Pod 的 Init 容器形式运行,属于一次性的临时进程;
- 校验失败时,通过 Unix 域套接字把事件发给本机的 Platform Connector,后面就走正常入库流程了;
- 校验没问题就不产生任何事件。多节点协同校验靠 Gang 发现 + ConfigMap 配置来实现。
3. HealthEvent 数据模型
HealthEvent 是主数据模型。GPU、网卡、系统日志、云厂商维护等事件都会尽量归一到这个结构里,方便后续模块用同一套规则处理。
主要字段
| 分类 | 字段 | 说明 |
|---|---|---|
| 来源信息 | version、agent、componentClass、nodeName | 采集组件名称、硬件类型、故障节点名 |
| 健康状态 | checkName、isFatal、isHealthy、message | 检测项、是否致命、硬件健康状态、可读的故障描述 |
| 故障处置 | recommendedAction、errorCode、entitiesImpacted | 推荐的修复动作、故障错误码、受影响的资源(GPU/NODE/POD/NIC) |
| 元数据 | metadata、generatedTimestamp | 自定义 KV(硬件 UUID、驱动版本、故障严重度)、事件生成时间 |
| 行为控制 | quarantineOverrides、drainOverrides | 全局开关:跳过节点隔离、跳过 Pod 驱逐 |
RecommendedAction 枚举值
NONE=0 无需操作
COMPONENT_RESET=2 组件重置
CONTACT_SUPPORT=5 联系技术支持
RESTART_VM=15 重启虚拟机
RESTART_BM=24 重启裸金属服务器
REPLACE_VM=25 更换虚拟机/硬件
UNKNOWN=99 未知处置方案
Entity 子结构
用来描述故障关联的资源:
- entityType:资源类型 GPU / NODE / POD / NIC
- entityValue:资源的唯一标识(GPU UUID、网卡 PCI 地址、节点名)
二、组件分类和部署形态
从 Helm chart 组织方式看,组件基本按独立工作负载部署。下面按运行位置分成三类:节点侧 DaemonSet、集群控制面 Deployment、存储层 StatefulSet。具体语言实现随版本可能变化,这里不把语言作为判断重点。
节点侧 DaemonSet(每台 GPU 节点一个 Pod,常驻采集)
只在 GPU/计算节点上部署,本地采集硬件数据,通过本机 gRPC 把事件报给本地的 Platform Connectors。
| Chart 名称 | 运行载体 | 进程形态 | 主要作用 |
|---|---|---|---|
| gpu-health-monitor | DaemonSet | 独立 Pod、常驻进程 | 对接 DCGM 采集 GPU 温度、功耗、ECC、XID、降频等健康信号,旁路输出 Prometheus 指标 |
| syslog-health-monitor | DaemonSet | 独立 Pod、常驻进程 | 读节点 journal 日志,抓 GPU XID/SXID、PCI 总线失联这类报错 |
| nic-health-monitor | DaemonSet | 独立 Pod、常驻进程 | 监控网卡、RDMA、PCIe 网络链路、硬件丢包故障,生成网卡类 HealthEvent |
| metadata-collector | DaemonSet | 独立 Pod、常驻进程 | 采集 GPU UUID、NVSwitch、网卡 PCI 拓扑等硬件元数据 |
| platform-connectors | DaemonSet | 独立 Pod、gRPC 服务 | 本机事件入口:收本地事件、格式校验、写存储,并同步 K8s NodeCondition/Event |
| preflight | Init 容器模板 | 业务 Pod 内一次性临时进程 | Pod 启动前做硬件预检,异常通过 Unix Socket 报给本地 Platform Connectors |
集群控制面 Deployment(集中运行)
这类组件运行在集群控制面,订阅 MongoDB Change Streams 来消费故障事件,负责隔离、驱逐、修复、分析和状态同步等逻辑。
故障处理模块
| Chart 名称 | 运行载体 | 主要作用 |
|---|---|---|
| fault-quarantine | Deployment | 根据 CEL 规则判断是否 cordon 节点,必要时加 taint,并记录隔离状态 |
| node-drainer | Deployment | 感知已隔离节点,按 namespace 和 GPU 粒度策略调用 K8s Eviction API 驱逐 Pod |
| fault-remediation | Deployment | 根据故障的 recommendedAction 创建 RebootNode 运维 CRD,下发硬件修复工单 |
| health-events-analyzer | Deployment | 对事件做时间窗口和模式分析,必要时升级故障或生成推荐动作 |
| labeler | Deployment | 自动给 K8s 节点打 GPU 驱动、DCGM、硬件型号标签 |
| event-exporter | Deployment | 消费事件流,向外推送告警到第三方平台(HTTP/Webhook) |
| kubernetes-object-monitor | Deployment | 监听 K8s Node/Pod/CRD 资源变更,补充集群资源状态事件 |
| slurm-drain-monitor | Deployment | K8s+Slurm 异构集群双向同步:NVSentinel 隔离节点自动 drain Slurm 节点、Slurm 手动驱逐同步告警到 NVSentinel |
| csp-health-monitor | Deployment | 轮询公有云 API,获取 VM 硬件维护、迁移计划并生成维护事件 |
运维执行模块
| Chart 名称 | 运行载体 | 主要作用 |
|---|---|---|
| janitor | Deployment | CRD 控制器,监听 RebootNode 工单,下发硬件重启/更换指令 |
| janitor-provider | Deployment | 底层驱动抽象层,对接 IPMI、裸金属机房、公有云硬件 API,屏蔽底层差异 |
集群内置服务
| Chart 名称 | 运行载体 | 主要作用 |
|---|---|---|
| incluster-file-server | Deployment | 集群静态配置服务,下发 CEL 隔离规则、故障码映射、告警模板 |
存储层 StatefulSet(持久化事件和日志)
| Chart 名称 | 运行载体 | 用途 |
|---|---|---|
| mongodb-store | StatefulSet(默认 3 副本) | 生产环境常用主存储,持久化 HealthEvent,提供 Change Streams |
| k8s-datastore | StatefulSet | 轻量测试存储,不依赖 MongoDB,用 K8s 自定义 CRD 存事件 |
| postgresql | StatefulSet | 辅助存储,存事件分析日志、Janitor 运维操作记录 |
三、端到端数据流(以 GPU 致命 XID 故障为例)
下面以一个会触发节点级处理的 GPU 故障为例。实际是否隔离、驱逐、修复,还要看部署侧规则和 dry-run 配置。
- GPU Health Monitor(DaemonSet 节点进程)通过 DCGM 检测到双比特 ECC 或高风险 XID,生成 HealthEvent;
- 事件通过本地 gRPC 推送到本机的 Platform Connectors;
- Platform Connectors 做完事件格式校验,把事件写入 mongodb-store,同时调 K8s API 更新本节点的 NodeCondition 或 Kubernetes Event;
- MongoDB 写入数据后生成 Change Stream 变更流,所有集群控制面模块异步订阅消费;
- fault-quarantine 模块读到致命故障事件后,匹配 CEL 隔离规则,调 K8s API 对节点执行 cordon,按配置决定是否加 taint,并更新事件的隔离状态;
- node-drainer 模块感知到节点已隔离后,按 namespace、GPU-only 或 partial-drain 策略调用 K8s Eviction 接口,更新驱逐状态;
- fault-remediation 模块读到事件的推荐修复动作(REPLACE_VM),创建 RebootNode 自定义资源 CRD 写入 K8s;
- janitor 控制器监听到 CRD 资源,调 janitor-provider 硬件驱动层,执行服务器重启/硬件更换操作;
- health-events-analyzer、event-exporter 同步消费事件流:前者做事件模式分析和指标输出,后者推送故障告警到外部系统;
- 旁路链路:GPU Monitor 独立输出硬件指标到 Prometheus,不占用故障事件主链路。
其他场景
- 网卡故障:nic-health-monitor 的流程和 GPU 监控类似,生成 NIC 类型的 HealthEvent,后面的隔离、驱逐、修复链路按同一套框架处理;
- Slurm 异构集群:节点完成隔离驱逐后,slurm-drain-monitor 自动调 Slurm 接口排空对应的计算节点;
- Pod 预检失败:Preflight Init 容器校验异常,通过 Unix Socket 直连本机 Platform Connectors 上报,后面的入库、处理流程和硬件故障一致;
- 非致命告警:只生成 K8s Warning 事件,不触发节点封锁、Pod 驱逐。
四、数据转换和通信规范
数据格式转换
- gRPC Protobuf(HealthEvent)→ MongoDB BSON:保留事件主体,额外补创建时间、事件处置状态字段;
- MongoDB ChangeStream → 业务模块:解析 fullDocument 提取 HealthEvent 核心结构体;
- HealthEvent → K8s Node Condition:提取故障名称、致命标记、故障描述,映射为节点健康状态;
- HealthEvent → RebootNode CRD:提取故障节点、推荐修复动作,生成运维工单资源。
通信协议
-
gRPC(HTTP/2 + Protobuf)
- 服务端:每节点的 Platform Connectors,默认端口 50051,支持 TLS 加密;
- 客户端:gpu/nic/syslog-health-monitor、metadata-collector;
- 用途:节点侧采集组件上报硬件故障事件。
-
MongoDB Change Streams
- 连接目标:mongodb-store 集群;
- 消费方:集群控制面处理模块;
- 作用:让隔离、驱逐、修复、分析等模块用同一份事件流异步推进。
-
Kubernetes API
- 鉴权:各模块独立的 ServiceAccount + RBAC 权限;
- 操作资源:core/v1 Node/Pod、policy/v1 驱逐 API、自定义 RebootNode CRD。
-
Unix 域套接字
- 只有 Preflight Init 容器用,本地上报预检异常,没有网络开销。
五、整体流程图
#mermaid-svg-pXwp82wjmt8bhfPP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pXwp82wjmt8bhfPP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pXwp82wjmt8bhfPP .error-icon{fill:#552222;}#mermaid-svg-pXwp82wjmt8bhfPP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pXwp82wjmt8bhfPP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pXwp82wjmt8bhfPP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pXwp82wjmt8bhfPP .marker.cross{stroke:#333333;}#mermaid-svg-pXwp82wjmt8bhfPP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pXwp82wjmt8bhfPP p{margin:0;}#mermaid-svg-pXwp82wjmt8bhfPP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pXwp82wjmt8bhfPP .cluster-label text{fill:#333;}#mermaid-svg-pXwp82wjmt8bhfPP .cluster-label span{color:#333;}#mermaid-svg-pXwp82wjmt8bhfPP .cluster-label span p{background-color:transparent;}#mermaid-svg-pXwp82wjmt8bhfPP .label text,#mermaid-svg-pXwp82wjmt8bhfPP span{fill:#333;color:#333;}#mermaid-svg-pXwp82wjmt8bhfPP .node rect,#mermaid-svg-pXwp82wjmt8bhfPP .node circle,#mermaid-svg-pXwp82wjmt8bhfPP .node ellipse,#mermaid-svg-pXwp82wjmt8bhfPP .node polygon,#mermaid-svg-pXwp82wjmt8bhfPP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pXwp82wjmt8bhfPP .rough-node .label text,#mermaid-svg-pXwp82wjmt8bhfPP .node .label text,#mermaid-svg-pXwp82wjmt8bhfPP .image-shape .label,#mermaid-svg-pXwp82wjmt8bhfPP .icon-shape .label{text-anchor:middle;}#mermaid-svg-pXwp82wjmt8bhfPP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pXwp82wjmt8bhfPP .rough-node .label,#mermaid-svg-pXwp82wjmt8bhfPP .node .label,#mermaid-svg-pXwp82wjmt8bhfPP .image-shape .label,#mermaid-svg-pXwp82wjmt8bhfPP .icon-shape .label{text-align:center;}#mermaid-svg-pXwp82wjmt8bhfPP .node.clickable{cursor:pointer;}#mermaid-svg-pXwp82wjmt8bhfPP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pXwp82wjmt8bhfPP .arrowheadPath{fill:#333333;}#mermaid-svg-pXwp82wjmt8bhfPP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pXwp82wjmt8bhfPP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pXwp82wjmt8bhfPP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pXwp82wjmt8bhfPP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pXwp82wjmt8bhfPP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pXwp82wjmt8bhfPP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pXwp82wjmt8bhfPP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pXwp82wjmt8bhfPP .cluster text{fill:#333;}#mermaid-svg-pXwp82wjmt8bhfPP .cluster span{color:#333;}#mermaid-svg-pXwp82wjmt8bhfPP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pXwp82wjmt8bhfPP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pXwp82wjmt8bhfPP rect.text{fill:none;stroke-width:0;}#mermaid-svg-pXwp82wjmt8bhfPP .icon-shape,#mermaid-svg-pXwp82wjmt8bhfPP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pXwp82wjmt8bhfPP .icon-shape p,#mermaid-svg-pXwp82wjmt8bhfPP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pXwp82wjmt8bhfPP .icon-shape .label rect,#mermaid-svg-pXwp82wjmt8bhfPP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pXwp82wjmt8bhfPP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pXwp82wjmt8bhfPP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pXwp82wjmt8bhfPP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持久化存储 StatefulSet
全局 Deployment 集群控制面组件
单 GPU 节点 DaemonSet 采集组件
运维执行组件
故障处理模块
本地 gRPC HealthEvent
本地 gRPC HealthEvent
本地 gRPC HealthEvent
本地 gRPC 硬件元数据
Unix Socket 预检异常
旁路硬件指标
Patch Node Condition
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
ChangeStream 订阅
Cordon 节点、加不可调度污点
节点隔离事件触发
Eviction API 优雅驱逐 Pod
创建 RebootNode 运维 CRD
监听修复工单 CRD
硬件操作指令下发
CEL 规则/故障码模板
gpu-health-monitor
独立 Pod 进程
syslog-health-monitor
独立 Pod 进程
nic-health-monitor
网卡/RDMA 监控
metadata-collector
硬件拓扑采集
platform-connectors
本地 gRPC 网关
preflight Init 容器
业务 Pod 临时校验进程
fault-quarantine 故障隔离
node-drainer 节点驱逐
fault-remediation 故障修复
health-events-analyzer 事件分析
event-exporter 外部告警推送
labeler 节点标签管理
kubernetes-object-monitor K8s 资源监听
slurm-drain-monitor Slurm 集群同步
csp-health-monitor 云厂商维护监控
janitor 运维 CRD 控制器
janitor-provider 硬件驱动适配层
incluster-file-server 配置文件服务
mongodb-store 生产主存储
k8s-datastore 轻量测试存储
postgresql 日志辅助存储
K8s API Server
Slurm Controller 异构调度集群
公有云/机房 IPMI 硬件接口
Prometheus 指标采集
六、设计上的几个特点
- 组件边界比较清楚:采集、入库、隔离、驱逐、修复、分析分别由不同组件承担,模块间主要靠事件存储衔接。
- HealthEvent 是主接口:新增故障来源时,优先把事件适配到 HealthEvent,而不是让后续模块直接理解原始日志或指标。
- Kubernetes 写路径分层:Platform Connectors 写 NodeCondition/Event,Fault Quarantine 写 cordon/taint,Node Drainer 写 Eviction,Remediation 写维护 CR。
- 事件可回放和可审计:MongoDB 保存事件和处置状态,模块重启后可以继续从事件状态推进。
- 动作强依赖配置:是否 taint、是否 drain、是否 partial drain、是否触发 repair,都不是固定行为,需要看 Helm values 和规则文件。
- 底层恢复是外部边界:NVSentinel 负责派发恢复请求和同步状态,真正的重启、换机、GPU reset 仍由 janitor-provider 或外部系统完成。
七、常见问题答疑
1. gpu-health-monitor 这些采集组件如何判断什么时候生成 HealthEvent?
结论:HealthEvent 由采集到的原始信号触发,但最终字段取值还要经过错误码映射、严重程度判断和去重状态处理。
以 gpu-health-monitor 为例,它作为 DaemonSet 周期性连接本节点或集群内的 DCGM 服务,轮询 DCGM health checks。DCGM 返回 ECC、温度、PCIe、Power、InfoROM、NVLink/NVSwitch 等健康异常后,gpu-health-monitor 会把 DCGM 结果映射成 HealthEvent,填充 checkName、isFatal、isHealthy、errorCode、recommendedAction、entitiesImpacted 和 metadata 等字段。
常见触发来源主要有几类:
- DCGM health watch 返回异常,例如 ECC 错误、NVLink down、PCIe 错误、温度或功耗异常;
syslog-health-monitor从 journal 或 dmesg 里匹配到 XID/SXID、GPU fallen off bus、PCIe AER、驱动崩溃等日志模式;nic-health-monitor检测到网卡、RDMA、链路或硬件丢包异常;csp-health-monitor轮询到云厂商维护、迁移或实例硬件风险事件;kubernetes-object-monitor通过 CEL 规则识别到 K8s 资源状态异常;preflight准入检查失败,通过本机 socket 上报一次性异常事件;- 健康恢复时,组件也可能产生
isHealthy=true的恢复类事件,用于清除 NodeCondition 或解除隔离状态。
这里要注意两点:第一,DCGM error code 到 recommendedAction 的映射通常由映射文件维护,例如 DCGM_FR_FAULTY_MEMORY 可能映射为 CONTACT_SUPPORT,轻微温度降频可能映射为 NONE;第二,采集组件会维护 entity-level 状态缓存,避免同一故障在单次启动周期内反复上报。
2. Platform Connectors 会标记 GPU 不健康吗?和 fault-quarantine 是否冲突?
结论:会标记不健康状态,但它标的是 NodeCondition 或 Kubernetes Event,不是直接做节点隔离。
platform-connectors 收到 HealthEvent 后,会做格式校验、元数据增强、override 规则处理,然后分别写入事件存储和 Kubernetes。它在 Kubernetes 侧主要更新 NodeCondition 或创建 Kubernetes Event,用于表达"节点为什么不健康",但不负责执行节点隔离。
一般行为可以按严重程度理解:
isFatal=true:更新 KubernetesNodeCondition,将对应 condition 的status置为True;isHealthy=true:更新 KubernetesNodeCondition,将对应 condition 的status置为False,表示该检查恢复;isFatal=false:通常创建 Kubernetes Event,用于记录 warning 或 transient issue,而不是持久化为故障 condition。
一个 NodeCondition 例子如下:
yaml
status:
conditions:
- type: GpuMemoryError
status: "True"
reason: HardwareFailure
message: "[DCGM_FR_FAULTY_MEMORY] GPU memory failure detected on GPU 0 - RecommendedAction: RESTART_VM"
lastTransitionTime: "2025-11-06T10:00:00Z"
这和 fault-quarantine 不冲突,两者职责不同:
platform-connectors:表达健康事实,写HealthEvent、NodeCondition或 Kubernetes Event;fault-quarantine:消费事件和 Node 上下文,根据 CEL 规则决定是否cordon、是否加taint;node-drainer:在节点被隔离后,按策略驱逐已有业务 Pod。
所以两者不冲突。Platform Connectors 说明"为什么不健康",Fault Quarantine 决定"是否禁止继续调度",Node Drainer 再决定"是否把已有负载迁走"。
3. fault-quarantine 的 CEL 规则在哪里配置?常见规则和处理结果是什么?
结论:规则通常在 Helm values 里的 fault-quarantine.ruleSets 配置,生产环境一般会用 values overlay 覆盖默认规则。
配置例子如下:
yaml
fault-quarantine:
ruleSets:
- version: "1"
name: "GPU fatal error ruleset"
priority: 100
match:
all:
- kind: "HealthEvent"
expression: "event.agent == 'gpu-health-monitor' && event.componentClass == 'GPU' && event.isFatal == true"
- kind: "Node"
expression: "!('k8saas.nvidia.com/ManagedByNVSentinel' in node.metadata.labels && node.metadata.labels['k8saas.nvidia.com/ManagedByNVSentinel'] == 'false')"
cordon:
shouldCordon: true
taint:
key: "nvidia.com/gpu-error"
value: "fatal"
effect: "NoSchedule"
CEL 规则可以同时访问 event 和 Kubernetes node 对象,因此不只能写"fatal 就隔离",也可以按节点 label、资源池、GPU 型号、维护窗口等上下文过滤。
常见规则不需要很多,生产上反而建议从少量高置信度规则开始:
- GPU Health Monitor 上报 fatal GPU event,执行 cordon;
- Syslog Health Monitor 上报 fatal XID/SXID 或 GPU fallen off bus,执行 cordon;
- 按
errorCode区分高风险和低风险错误,例如排除某些已知误报 XID; - 对 warning 或 degraded 状态只加
PreferNoSchedule,不立即强隔离; - 启用 circuit breaker,避免规则误判导致短时间大面积节点下线。
规则命中后的处理结果主要是:
cordon:设置node.spec.unschedulable=true,阻止新 Pod 调度到该节点;- 可选
taint:例如nvidia.com/gpu-xid-error=true:NoSchedule; - 添加 label 或 annotation,记录隔离原因、时间、执行组件;
- 更新事件存储中的隔离状态,供后续
node-drainer、fault-remediation消费; - 健康恢复后,如果相关检查都恢复,可自动 uncordon 并清理 taint、label、annotation。
4. node-drainer 是驱逐整机所有负载,还是可以精确到某些 GPU?
结论:默认更接近"按节点驱逐 eligible pods",但配置满足条件时可以做到 GPU-only 或按故障 GPU 做 partial drain。
node-drainer 的基本逻辑是:监听已经被 quarantine 的节点,按 namespace 策略筛选 Pod,然后调用 Kubernetes Eviction API。系统命名空间和 DaemonSet 通常会被跳过,业务命名空间按配置选择不同驱逐模式。
配置例子如下:
yaml
node-drainer:
partialDrainEnabled: true
drainGPUPods: false
systemNamespaces: "^(nvsentinel|kube-system|gpu-operator|gmp-system|network-operator)$"
userNamespaces:
- name: "training"
mode: "DeleteAfterTimeout"
- name: "inference-*"
mode: "Immediate"
- name: "*"
mode: "AllowCompletion"
namespace 层面的驱逐模式有三种:
Immediate:立即通过 Eviction API 驱逐,适合无状态服务;AllowCompletion:等待 Pod 按自身terminationGracePeriodSeconds优雅退出;DeleteAfterTimeout:等待训练任务 checkpoint,超过deleteAfterTimeoutMinutes后强制删除。
真正影响驱逐范围的是下面三个开关或条件:
- 整节点 eligible pods 驱逐:默认模式,驱逐该节点上配置命名空间内的业务 Pod;
- 只驱逐 GPU Pod :设置
drainGPUPods: true后,只驱逐请求 GPU 资源的 Pod,CPU-only 工作负载保留; - 局部 GPU 驱逐 :设置
partialDrainEnabled: true,且事件是COMPONENT_RESET,并且entitiesImpacted中包含具体 GPU UUID 时,可以只驱逐使用该故障 GPU 的 Pod。
局部 GPU 驱逐依赖 metadata-collector 维护 Pod 到 GPU UUID 的分配关系,通常通过 Pod annotation 表达,例如:
yaml
annotations:
dgxc.nvidia.com/devices: '{"devices":{"nvidia.com/gpu":["GPU-123"]}}'
这里最容易踩坑的是 partial drain 和 remediation 动作不一致。如果 COMPONENT_RESET 最后实际做的是整机 reboot,那么只驱逐部分 Pod 就不安全,因为没被驱逐的 Pod 也会被重启影响。节点级动作如 RESTART_VM、RESTART_BM、REPLACE_VM 通常应执行整节点 drain。
5. health-events-analyzer 的具体原理是什么?
结论:它不是采集入口,也不直接做 cordon、drain 或创建维修 CRD。它的价值在于把低层级事件关联起来,识别更高置信度的故障模式。
核心流程是:
- 从 MongoDB Change Streams 监听新
HealthEvent,或按时间窗口查询历史事件; - 加载分析规则,历史实现通常使用 TOML 配置和 MongoDB Aggregation Pipeline;
- 按 node、GPU UUID、errorCode、agent、时间窗口等字段聚合事件;
- 判断是否满足某个故障模式;
- 命中后生成新的健康事件、升级严重程度,或补充更明确的
recommendedAction; - 通过 publisher 把分析结果重新发布回事件链路,供
fault-quarantine、node-drainer、fault-remediation和event-exporter消费。
适合交给 Analyzer 的场景,一般是"单条事件不足以下判断,但组合或重复出现就有意义"的问题:
- XID Burst:同一节点或同一 GPU 在短时间内多次出现 XID;
- ECC 累计升级:单次可纠正 ECC 只记录,时间窗口内反复出现则升级;
- 驱动异常关联:driver crash、GPU 不可访问、Pod 失败、NodeNotReady 在短时间内组合出现;
- 局部 GPU 故障判断:只影响单张 GPU 时倾向局部 reset 和 partial drain;
- 云维护事件关联:云厂商维护窗口叠加节点负载状态,提前触发隔离或迁移建议。
当前公开信息显示,Analyzer 历史上和 MongoDB Pipeline 绑定较强。后续是否已经切到 CEL 或数据库无关规则,需要以目标版本源码为准;从设计上看,更合理的是"Store Client 取候选事件 + 规则引擎在应用层判断"。
6. remediation 恢复后,集群和负载怎么恢复?
结论:恢复后分两条线看。NVSentinel 负责节点健康闭环和重新入池;业务负载能不能恢复到原进度,主要看 Kubernetes 控制器、训练调度系统和业务 checkpoint。
节点恢复的大致流程是:
fault-remediation根据recommendedAction创建维护类 CR,例如RebootNode、TerminateNode或自定义RecoveryRequest;janitor或外部恢复系统 watch 该 CR,执行节点重启、GPU reset、实例替换、裸金属维修或调用外部IMX-CM等动作;- 恢复系统把结果写回 CR
status.conditions,例如NodeReady=True、RecoveryCompleted=True; - 节点恢复后,
gpu-health-monitor、syslog-health-monitor等继续采集健康状态; - 如果检查恢复,采集组件产生
isHealthy=true的恢复类HealthEvent; platform-connectors根据恢复事件把相关NodeCondition置为False或清除故障状态;fault-quarantine确认相关健康检查都恢复后,执行 uncordon,移除 quarantine 相关 taint、label、annotation;- 节点重新变为可调度,回到资源池。
因此,节点能否重新入池不能只看 remediation CR 完成,还要看后续健康事件是否确认恢复,以及 quarantine 状态是否解除。
负载恢复则主要由 Kubernetes 原生控制器或上层调度系统完成:
Deployment/ReplicaSet:Pod 被驱逐后自动在健康节点创建新副本;StatefulSet:按 StatefulSet 语义重建对应 Pod;Job/CronJob:是否重试取决于backoffLimit、restart policy 和任务状态;- Kubeflow、Volcano、Run:ai、Ray、MPI Operator、Slurm 等训练调度系统:根据各自语义重新排队、重启或恢复作业;
- 裸 Pod:被驱逐后不会自动恢复。
如果是长时间训练任务,能不能续跑不由 NVSentinel 保证,而取决于业务是否有 checkpoint、任务控制器是否支持重试、调度系统是否会重新提交任务。
对于不同恢复动作,后续行为也不同:
RESTART_VM、RESTART_BM、REPLACE_VM:通常整节点 drain,修复后节点重新入池,原负载由调度系统在任意健康节点重建;COMPONENT_RESET:如果启用 partial drain 且事件包含故障 GPU UUID,可以只驱逐使用该 GPU 的 Pod,reset 后节点继续可用;- 外部恢复系统介入:NVSentinel 通过 CR status 和后续健康事件确认结果,不直接执行底层根因定位和维修动作。
从系统边界看,NVSentinel 负责"发现故障、隔离节点、驱逐风险负载、派发恢复请求、确认节点恢复并重新入池"。业务是否恢复到原进度,属于 workload controller、训练框架和 checkpoint 机制的职责。