RFC: 人工隔离准确性增强
1. 特性概述
本特性实现了设备的人工隔离功能,支持基于故障频率的自动隔离,并具备软件故障识别能力,避免非硬件原因导致的误隔离问题。
2. 需求背景
2.1 问题描述
-
人工隔离需求:当设备在特定频率下出现故障时,需要将该设备进行人工隔离,且这种隔离需要手动解除,不同于device plugin上报的preseparate、separate级别的故障。
-
误隔离问题:
- dp生成的芯片故障状态缺少任务级别的全局视角
- 准确性不足导致误隔离问题
- 由于非硬件原因(如软件问题)导致一个任务下短时间内反复大量出现aic timeout故障
- 导致device plugin侧感知到该故障达到了故障频率,将大量设备置为人工隔离状态
- 最终导致大批量节点处于无法调度的状态
2.2 解决方案
-
支持故障频率配置:
- 允许用户自定义故障频率升级人工隔离的规则
- 避免无逃生和用户修改能力
-
解决非硬件故障误判:
- 对于软件故障导致的故障不计入频率故障的计数
- 定义规则:若一个任务下30秒内多张卡同时出现了同一个故障,则认为不是硬件故障导致,不进行频率故障计数
3. 设计方案
3.1 整体架构
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ 故障收集模块 │ │ 人工隔离管理模块 │ │ Kubernetes ConfigMap │
└──────────┬────────────┘ └──────────┬────────────┘ └──────────┬────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ 故障计数模块 │ │ 任务级故障管理器 │ │ 全局配置管理 │
└──────────┬────────────┘ └──────────┬────────────┘ └──────────┬────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ 软件故障识别 │ │ 故障频率控制 │ │ 故障恢复策略 │
└──────────┬────────────┘ └──────────┬────────────┘ └──────────┬────────────┘
│ │ │
└────────────────┬───────────────┴────────────────┬──────────────┘
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ 人工隔离执行 │ │ 隔离状态维护 │
└───────────────────────┘ └───────────────────────┘
3.2 核心组件
3.2.1 故障计数模块 (counter.go)
- 功能:记录设备的故障次数和时间
- 数据结构:
type FaultCounter struct { // key: node, value: dev fault info faults map[string]map[string]devFault mutex sync.RWMutex } type devFault struct { // key: fault code, value: fault times fault map[string][]int64 } - 核心逻辑:
- 为每个设备的每个故障代码维护故障时间列表
- 定期清理过期的故障记录
- 判断故障频率是否达到阈值
3.2.2 任务级故障管理器 (job_fault_mgr.go)
- 功能:针对故障卡上有任务的场景,从任务维度管理故障,识别软件故障
- 核心逻辑:
- 收集同一任务下的所有故障
- 滑动窗口算法(默认30秒)
- 软件故障判断:一个任务下30秒内多张卡同时出现了同一个故障,则认为不是硬件故障导致,不进行频率故障计数
- 非软件故障则传递给故障计数模块
- 适用场景:专门处理故障卡上有任务的情况,避免软件问题导致的批量误隔离
关键实现细节:
// AddFault 方法处理任务级故障
func (m *JobFaultManager) AddFault(newFault *Fault) {
if newFault == nil {
return
}
// 无任务ID的故障直接传递给故障计数模块
if newFault.JobId == "" {
Counter.AddFault(newFault)
return
}
// 有任务ID的故障进入软件故障判断流程
hwlog.RunLog.Infof("fault enters the process of determining software fault: %+v", newFault)
// ... 任务级故障处理逻辑
}
软件故障识别核心算法:
// firstItemIsSfwFault 判断第一个故障是否为软件故障
// 规则:一个任务下30秒内多张卡同时出现了同一个故障,则认为不是硬件故障导致
func firstItemIsSfwFault(faults []*Fault) bool {
if len(faults) == 0 {
return false
}
fault0 := faults[0] // 获取第一个故障作为基准
for idx, fault := range faults {
if idx == 0 {
continue
}
// 同一任务下,30秒内,不同设备出现相同故障 → 软件故障
if fault.Code == fault0.Code &&
fault.ReceiveTime <= fault0.ReceiveTime+m.slidingWindow &&
(fault.NodeName != fault0.NodeName || fault.DevName != fault0.DevName) {
hwlog.RunLog.Infof("fault: %+v, is software fault", fault0)
return true
}
}
hwlog.RunLog.Infof("fault: %+v, is not software fault", fault0)
return false
}
// deleteSameWithFirstFault 处理完第一个故障后,删除相关故障记录
func (m *JobFaultManager) deleteSameWithFirstFault(faults []*Fault, isSftFault bool) []*Fault {
fault0 := faults[0]
var faultsAfterDelete []*Fault
for idx, fault := range faults {
if idx == 0 {
continue
}
// 在滑动窗口内的相同故障
if fault.Code == fault0.Code && fault.ReceiveTime <= fault0.ReceiveTime+m.slidingWindow {
if isSftFault {
// 软件故障:所有相关故障都不计入频率计数
hwlog.RunLog.Infof("fault: %+v, is software fault", fault)
continue
}
// 硬件故障:同一设备的故障计入频率计数
if fault.NodeName == fault0.NodeName && fault.DevName == fault0.DevName {
hwlog.RunLog.Infof("fault: %+v, is not software fault", fault)
Counter.AddFault(fault)
continue
}
}
// 其他故障保留,继续处理
faultsAfterDelete = append(faultsAfterDelete, fault)
}
return faultsAfterDelete
}
工作流程:
- 收集同一任务下的所有故障记录
- 对故障记录按时间顺序排序
- 以第一个故障为基准,检查30秒内是否有其他设备出现相同故障
- 如果是软件故障,所有相关故障都不计入频率计数
- 如果是硬件故障,同一设备的故障计入频率计数
- 处理完后删除已处理的故障记录,继续处理下一批故障
3.2.3 人工隔离管理模块 (cm.go)
- 功能:管理人工隔离状态,持久化到ConfigMap
- 核心数据结构:
type Cache struct { manualInfo map[string]NodeCmInfo mutex sync.RWMutex } type NodeCmInfo struct { Total []string // key: dev name, value: dev fault Detail map[string][]DevCmInfo } type DevCmInfo struct { FaultCode string FaultLevel string LastSeparateTime int64 // unit: millisecond } - 核心功能:
- 添加/删除人工隔离设备
- 维护隔离设备的详细信息
- 与ConfigMap同步隔离状态
3.2.4 全局配置管理 (config.go)
- 功能:管理人工隔离策略的配置参数
- 核心配置项:
type ManuallySeparatePolicy struct { Enabled bool `yaml:"enabled"` Separate struct { FaultWindowHours int `yaml:"fault_window_hours"` // 故障统计窗口 FaultThreshold int `yaml:"fault_threshold"` // 故障阈值 } `yaml:"separate"` Release struct { FaultFreeHours int `yaml:"fault_free_hours"` // 自动释放时间 } `yaml:"release"` }
4. 实现细节
4.1 软件故障识别算法
// 检查是否为软件故障
func firstItemIsSfwFault(faults []*Fault) bool {
if len(faults) == 0 {
return false
}
fault0 := faults[0]
for idx, fault := range faults {
if idx == 0 {
continue
}
// 同一任务下,30秒内,不同设备出现相同故障
if fault.Code == fault0.Code &&
(fault.NodeName != fault0.NodeName || fault.DevName != fault0.DevName) {
return true
}
}
return false
}
4.2 故障频率判断逻辑
// 判断是否达到故障频率阈值
func (c *FaultCounter) isReachFrequency(faultTimes []int64) bool {
// 故障次数未达到阈值
if len(faultTimes) < conf.GetSeparateThreshold() {
return false
}
// 阈值为1时直接返回
if conf.GetSeparateThreshold() == conf.MinFaultThreshold {
return true
}
// 获取最近的N次故障时间
recentTimes := faultTimes[len(faultTimes)-conf.GetSeparateThreshold():]
if len(recentTimes) < conf.GetSeparateThreshold() {
return false
}
// 判断最近N次故障是否在指定时间窗口内
if recentTimes[conf.GetSeparateThreshold()-1]-recentTimes[0] < conf.GetSeparateWindow() {
return true
}
return false
}
4.3 人工隔离执行流程
// 处理频率故障,执行人工隔离
func (c *FaultCounter) dealFrequencyFault(fault FaultInfo) {
// 记录日志
hwlog.RunLog.Infof("node: %s, dev: %s, code: %s, reach frequency threshold, set to manually separate",
fault.NodeName, fault.DevName, fault.FaultCode)
// 清理该设备的故障计数
c.clearDevFault(fault.NodeName, fault.DevName, fault.FaultCode)
// 添加到人工隔离缓存
FaultCmInfo.AddSeparateDev(fault)
}
4.4 系统初始化流程
人工隔离功能的初始化和启动在component/clusterd/main.go中完成,包含以下关键步骤:
func main() {
// ... 其他初始化代码 ...
// 加载全局配置
conf.TryLoadGlobalConfig()
// 监控全局配置变化
go conf.WatchGlobalConfig(ctx)
// 处理人工隔离NPU故障必须在故障处理器中心之前执行
// 因为需要先加载和初始化人工隔离状态,故障处理器才能正确处理故障
dealManuallySeparateNPUFault(ctx)
// ... 其他初始化代码 ...
// 启动故障处理器中心
faultmanager.GlobalFaultProcessCenter.Work(ctx)
// ... 其他初始化代码 ...
}
// dealManuallySeparateNPUFault 处理人工隔离NPU故障的初始化
func dealManuallySeparateNPUFault(ctx context.Context) {
// 初始化人工隔离缓存(必须在处理函数之前执行)
manualfault2.InitFaultCmInfo()
// 从ConfigMap加载人工隔离信息
manualfault.LoadManualCmInfo()
// 启动人工隔离管理进程
go manualfault.ProcessManuSep(ctx)
}
初始化顺序说明:
-
配置加载与监控:
conf.TryLoadGlobalConfig():加载初始配置,包括人工隔离策略的各项参数conf.WatchGlobalConfig(ctx):启动配置监控,支持运行时动态更新配置
-
人工隔离初始化:
InitFaultCmInfo():初始化全局人工隔离缓存LoadManualCmInfo():从Kubernetes ConfigMap加载已有的人工隔离信息ProcessManuSep(ctx):启动人工隔离管理进程,定期检查和更新隔离状态
-
故障处理器启动:
faultmanager.GlobalFaultProcessCenter.Work(ctx):启动故障处理器中心
5. 配置说明
5.1 配置结构
manuallySeparatePolicy:
enabled: true # 是否启用人工隔离功能
separate:
fault_window_hours: 24 # 故障统计窗口(小时)
fault_threshold: 3 # 故障阈值(次)
release:
fault_free_hours: -1 # 自动释放时间(小时,-1表示不自动释放)
5.2 配置约束
| 配置项 | 最小值 | 最大值 | 说明 |
|---|---|---|---|
| fault_window_hours | 1 | 720 | 故障统计窗口,单位小时 |
| fault_threshold | 1 | 50 | 触发人工隔离的故障次数 |
| fault_free_hours | 1 | 240 | 自动释放时间,-1表示不自动释放 |
6. 工作流程
6.1 故障处理流程
flowchart TD
A[收集设备故障信息] --> B{是否有任务ID?}
B -->|否| C[直接进行故障计数]
B -->|是| D[添加到任务级故障管理器]
D --> E{是否为软件故障?}
E -->|是| F[标记为软件故障,不计入频率]
E -->|否| C
C --> G{是否达到故障频率?}
G -->|是| H[执行人工隔离]
G -->|否| I[更新故障计数]
H --> J[更新隔离状态到clusterd-manual-info-cm]
J --> K[结束]
I --> K
F --> K
流程说明:
- 当设备故障达到设定的频率阈值时,系统执行人工隔离操作
- 隔离操作完成后,系统会将隔离状态更新到名为
clusterd-manual-info-cm的ConfigMap中 - 该ConfigMap位于
cluster-system命名空间,用于持久化存储集群中所有人工隔离的芯片信息
clusterd-manual-info-cm ConfigMap示例:
Name: clusterd-manual-info-cm
Namespace: cluster-system
Data
====
localhost.localdomain:
----
{"Total":["Ascend910-0","Ascend910-2","Ascend910-3"],"Detail":{"Ascend910-0":[{"FaultCode":"8C084E00","FaultLevel":"ManuallySeparateNPU","LastSeparateTime":1770811685650}]}}
ConfigMap字段说明:
| 参数 | 说明 |
|---|---|
<localhost.localdomain> |
节点名称,对应集群中的节点名称,作为Data的key |
Total |
该节点上所有人工隔离芯片的名称列表 |
Detail |
芯片故障详细信息,以芯片名称为key |
- <Ascend910-0> |
芯片名称,对应节点上的具体芯片 |
- FaultCode |
故障码,标识触发人工隔离的具体故障类型 |
- FaultLevel |
故障级别,此处固定为ManuallySeparateNPU表示人工隔离 |
- LastSeparateTime |
达到人工隔离频率时的最后一次故障时间(毫秒)。如果已经触发人工隔离的故障再次达到人工隔离频率,该时间会被刷新 |
6.2 人工隔离管理流程
flowchart TD
A[定期检查隔离状态] --> B{是否需要更新ConfigMap?}
B -->|是| C[更新/创建ConfigMap]
B -->|否| D[检查是否有手动解除的设备]
C --> D
D --> E{是否有手动解除的设备?}
E -->|是| F[同步更新缓存]
E -->|否| G[检查是否需要自动释放]
F --> G
G -->|是| H[执行自动释放]
G -->|否| I[结束]
H --> I
7. 测试验证
7.1 功能测试
-
基本功能测试:
- 模拟设备故障,验证故障计数功能
- 触发频率故障,验证人工隔离执行
- 手动解除隔离,验证状态同步
-
软件故障识别测试:
- 模拟同一任务下多张卡同时出现相同故障
- 验证该故障被识别为软件故障,不计入频率计数
-
配置测试:
- 修改故障频率配置,验证配置生效
- 测试不同配置组合的行为
7.2 性能测试
- 高并发测试:模拟大量设备同时故障,验证系统稳定性
- 长时间运行测试:持续运行数天,验证内存泄漏和性能衰减
8. 兼容性和风险
8.1 兼容性
- 与现有故障处理机制兼容
- 支持动态配置更新
- 支持平滑升级和回滚
8.2 风险评估
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| 配置错误导致误隔离 | 设备被错误隔离,影响资源利用率 | 配置验证、灰度发布、快速回滚机制 |
| 软件故障识别不准确 | 仍可能发生误隔离 | 持续优化算法、增加人工干预机制 |
| 性能问题 | 影响系统整体性能 | 性能测试、优化数据结构和算法 |
9. 总结
本特性实现了基于故障频率的人工隔离功能,并通过任务级故障管理识别软件故障,有效避免了非硬件原因导致的误隔离问题。该特性具有灵活的配置选项,支持用户根据实际需求调整故障频率规则,确保系统的可靠性和可维护性。通过clusterd-manual-info-cm ConfigMap,用户可以直观地查看集群中所有人工隔离的芯片信息,方便进行运维管理。