告警管理
HeteroFlow 是本站的异构算力与大模型调度产品。告警管理。告警管理 告警管理是 HeteroFlow 面向运维人员的"眼睛"子系统:服务端以 30 秒一轮的固定节拍巡检节点心跳、GPU/内存水位、任务状态机、推理后端探测与 MIG 分区账实,把"平台正在坏、正在停摆、正在悄悄劣化"的每一类形态翻译成一条条可确认、可消解、可复盘的告警记录…
告警管理
告警管理是 HeteroFlow 面向运维人员的"眼睛"子系统:服务端以 30 秒一轮的固定节拍巡检节点心跳、GPU/内存水位、任务状态机、推理后端探测与 MIG 分区账实,把"平台正在坏、正在停摆、正在悄悄劣化"的每一类形态翻译成一条条可确认、可消解、可复盘的告警记录。它不满足于"把异常写进日志"——日志会轮转,告警不会:每条记录都带状态机(ACTIVE → ACKNOWLEDGED → RESOLVED)、带"凭什么消解"的 resolution 留痕、带按身份收紧的读写权限,并且能实时外发到企业微信/钉钉/飞书值班群。
整套系统有一条贯穿的设计纪律:只在拿到"条件已不成立"的正向证据时才消解告警。查询失败、数据缺失、"这一轮没看到"一律不消解——那些是"不知道",不是"已恢复"。这条纪律来自真实事故:2026-08-18 生产库中 197 条 ACTIVE 告警里 188 条指向已删除的实体、最早一条滞留近三个月,根因正是"去重有了、消解没有",去重退化成永久抑制。此后消解链路、孤儿清扫、72 小时元告警看门狗逐一补齐,并在生产环境验证了完整闭环(节点重启触发 NODE_OFFLINE、心跳恢复自动 RESOLVED)。
功能总览
| 能力 | 一句话说明 | 状态 |
|---|---|---|
| 告警类型全集 | 6 大类 20 种内置类型:资源/任务/账实/节点/服务/预测 | 已实现 |
| 告警监控循环 | AlertMonitor 30 秒一轮,规则每轮热加载 | 已实现 |
| 告警生命周期 | ACTIVE→ACKNOWLEDGED→RESOLVED 状态机,resolution 四值留痕 | 真机验证 |
| 自动消解 | 心跳恢复/水位回落/任务离开 failed 等正向证据驱动的 RECOVERED 消解 | 真机验证 |
| 孤儿告警清扫 | 引用实体已删除的告警按 ENTITY_DELETED 消解,绝不记成"已恢复" | 已实现(生产清零落地) |
| 元告警看门狗 | 非终态超 72 小时的告警升级为 ALERT_RESOLUTION_STUCK,分两类结论 | 已实现 |
| 聚合与去重 | 节点/任务/类型三种作用域去重,停滞类聚合成一条并就地刷新 | 已实现 |
| 多渠道外发 | 企业微信/钉钉/飞书群机器人 + 通用 webhook 兼容出口,配置热更新 | 已实现 |
| 租户可见性 | 运营者全平台/租户管理员本租户/成员仅自己,读写分三档 | 已实现 |
| 告警规则管理 | 规则 CRUD,阈值可调,三种兜底类规则 fail-open | 已实现 |
| GPU 故障预测告警 | 显存 ECC 错误计数趋势预测,窗口到期自动对账 | 实验性 |
| 邮件/Slack 渠道 | 未实现,当前仅三家群机器人 webhook | 占位-不可宣传 |
告警规则引擎
告警类型全集
告警类型不是散落的字符串,而是登记在 alertTypeScopes 作用域表里的一等公民(core/pkg/api/alert.go:55):每新增一种类型必须登记作用域(去重维度),否则静态门禁测试直接转红。当前全集 20 种,分六类:
| 类别 | 类型 | 级别 | 触发判据 | 去重作用域 |
|---|---|---|---|---|
| 资源水位 | GPU_HIGH | WARN | 节点全部 GPU 平均计算使用率 ≥ 阈值(默认 90%) | 节点 |
| 资源水位 | GPU_MEMORY_HIGH | WARN | 节点显存占用(已用/总量)≥ 阈值(默认 85%) | 节点 |
| 资源水位 | MEMORY_HIGH | WARN | 系统内存使用率 ≥ 阈值(默认 85%) | 节点 |
| 任务事件 | TASK_FAILED | ERROR | 任务进入 failed 且最后变动在 5 分钟回看窗口内 | 任务 |
| 任务停滞 | TASK_STALLED_PENDING | WARN | pending 停留 > 600s(可配) | 类型聚合 |
| 任务停滞 | TASK_STALLED_SCHEDULED | ERROR | scheduled 停留 > 120s(可配),派单链路断 | 类型聚合 |
| 任务停滞 | TASK_STALLED_RUNNING | ERROR | running 任务的节点心跳静默 > max(300s, 4×离线阈值) | 类型聚合 |
| 任务停滞 | TASK_STALLED_STOPPING/DRAINING/WAKING | ERROR | 过渡态停留 > 300s(可配) | 类型聚合 |
| 任务停滞 | TASK_STALLED_PREEMPTING | ERROR | 抢占确认停滞 > 300s | 类型聚合 |
| 任务停滞 | TASK_WAKE_QUEUE_STALLED | WARN | 休眠唤醒排队 > 1800s(训练借卡属正常等待,阈值刻意放宽) | 类型聚合 |
| 账实背离 | TASK_GHOST_CONTAINER | ERROR | agent 仍在监管容器,服务端账上任务已终态/换节点/不存在(2 分钟宽限) | 节点 |
| 账实背离 | MIG_PARTITION_ORPHANED | ERROR | 控制面放弃销毁、残留在卡上的 MIG 分区(占 GI 槽位导致超卖) | 节点 |
| 节点故障 | NODE_OFFLINE | ERROR | 心跳静默 > 45s(连丢约 9 拍),启动静默期内不判 | 节点 |
| 节点故障 | GPU_LOST | ERROR | 节点在心跳但登记在册的卡连续 3 拍缺席/采集失败 | 节点 |
| 服务健康 | AGENT_TASK_UNHEALTHY | ERROR | agent 容器健康探针连续失败达阈值 | 节点 |
| 服务健康 | ENDPOINT_UNHEALTHY | ERROR | 推理后端被网关探测摘除(连续失败达端点阈值) | 任务 |
| 预测预警 | GPU_FAULT_PREDICTED | WARN | 显存硬件错误计数趋势劣化,预测窗口内可能不可纠正错误 | 节点 |
| 系统自检 | ALERT_RESOLUTION_STUCK | WARN/ERROR | 任意告警非终态滞留 > 72 小时(元告警) | 类型聚合 |
几个判据的刻意设计值得点名:
- GPU_HIGH 按节点平均而非单卡:一张卡打满不告警,整节点持续满载才算异常(
checkGPUHigh,alert.go:1281)。 - TASK_STALLED_RUNNING 绝不按运行时长判定:训练任务合法地跑 8 小时,判据是"所属节点多久没上报心跳"(
runningStallVerdict),且阈值显著大于节点离线阈值——节点刚离线时 NODE_OFFLINE 已经在叫,running 停滞要表达的是更严重的事:"离线迁移本该把任务捞回来,却没捞回来"。 - TASK_WAKE_QUEUE_STALLED 独立成类:休眠任务等训练任务让出物理卡是预期内的正常等待,若与"卡住=故障"的停滞共用 300s 阈值,正常长排队会打成噪音(alert_stall.go:100)。
- TASK_FAILED 只看 updated_at:曾经的判据导致"任务死得越快、能被告警的窗口越窄"——秒级失败的容器(镜像拉不下、启动即 OOM)几乎永远不产生告警;修正后 30 秒一轮的巡检在 5 分钟窗口内必然扫到(alert.go:1421 起的长注释记录了复现数据)。
默认规则与阈值语义
数据库无规则时自动落 7 条默认规则(defaultAlertRules,alert.go:125):
| 规则 | 类型 | 默认阈值 | 语义 |
|---|---|---|---|
| rule-1 GPU使用率告警 | GPU_HIGH | 90 | 常规开关+阈值 |
| rule-2 内存使用率告警 | MEMORY_HIGH | 85 | 常规开关+阈值 |
| rule-3 任务失败告警 | TASK_FAILED | 0 | 开关;threshold 仅随告警行展示 |
| rule-4 GPU显存使用率告警 | GPU_MEMORY_HIGH | 85 | 开关+阈值(曾长期缺失导致该检查从未触发,后补默认开启) |
| rule-5 任务停滞告警 | TASK_STALLED | 0 | fail-open 开关;阈值不走规则表,走 config.yaml alerts.task_stall |
| rule-6 推理后端探测摘除告警 | ENDPOINT_UNHEALTHY | 0 | fail-open 开关;判据在端点 health_check 配置 |
| rule-7 GPU显存故障预测告警 | GPU_FAULT_PREDICTED | 0 | fail-open 开关;多信号判据在环境变量,阈值列留 0 不放凑数数字 |
规则表的语义分两种,混用会踩坑:
- 常规规则(rule-1~4)fail-closed:规则不存在或 enabled=false,检查直接跳过。
- 兜底规则(rule-5~7)fail-open:规则不存在时照样告警,只有显式 enabled=false 才关闭。理由写在代码里:这类告警兜的是"静默停摆",如果它自己因为"数据库里没这条规则"(老库升级、规则表被清过)就静默不跑,等于重蹈 GPU_MEMORY_HIGH 那个覆辙——实现完整、默认关闭、从上线起从未触发,且没有任何信号告诉你它没在工作。
另有两处阈值不走规则表:NODE_OFFLINE 的判定阈值是代码常量 45 秒(DefaultNodeOfflineThreshold,server.go:28),规则里的 threshold 值只写进告警行展示、不参与判定;停滞类阈值走 config.yaml alerts.task_stall(pending_seconds 600 / scheduled_seconds 120 / running_seconds / transitional_seconds / wake_queue_seconds,<=0 时用内置默认)。
评估周期与规则加载
AlertMonitor 由主程序以 30 秒间隔启动(v2/cmd/main.go:907),每轮 checkAlerts()(alert.go:644)固定顺序执行:重载规则(每轮从数据库热读,改规则无需重启)→ 节点离线 → GPU/显存/内存水位 → 任务失败及回收 → 任务停滞 → 孤儿清扫 → 元告警自检 → 容错事件表节流清理。清扫排在自检之前是刻意的:先清扫再自检,自检看到的孤儿才是"清扫都清不掉的",那才真是链路故障。
去重与防抖
去重按作用域三种键(alertTypeScopes 登记表强制与消解作用域一致):
| 作用域 | 去重键 | 一条记录代表 | 适用类型 |
|---|---|---|---|
| 节点 | (type, node_id) | 一个节点的一个问题 | NODE_OFFLINE、GPU_HIGH、GPU_LOST 等 |
| 任务 | (type, task_id) | 一个任务的一个问题 | TASK_FAILED、ENDPOINT_UNHEALTHY |
| 类型 | type | 全平台同类问题的一批对象 | 停滞类七种 + 元告警 |
去重的判据是"存在非终态同类告警"(NonTerminalAlertStatuses,含 ACKNOWLEDGED)——运维点过确认的告警同样压住重复,不会被确认操作刷屏。防抖的另一面是聚合:停滞类把同状态的全部停滞任务聚合成一条消息,点名卡得最久的至多 10 个任务、其余归并为计数;已存在同类活跃告警时不新建而是就地刷新内容(refreshStalledAlert,alert_stall.go:780)——message/value/task_id 跟着最新一轮判定走,但 created_at、status、acknowledged_at 绝不碰:created_at 承载"问题存在多久",是 72 小时看门狗唯一的判据,每轮重建等于把陈年故障永久伪装成"刚刚发生"。
告警生命周期
┌─────────┐ 运维确认(首次记 acknowledged_at) ┌──────────────┐
│ ACTIVE │ ────────────────────────────────→ │ ACKNOWLEDGED │
└────┬────┘ └──────┬───────┘
│ 条件恢复(正向证据)/实体删除/缺席硬证据 │ 人工解决(resolution=MANUAL)
└───────────────────────────────────────────→ ┌──────────┐
自动消解 │ RESOLVED │ 终态,不可再操作
└──────────┘三个状态里只有 RESOLVED 是终态(IsTerminalAlertStatus)。对已 RESOLVED 的告警再发 acknowledge/resolve 一律 400 拒绝——把已消解的告警拽回非终态会让它重新抑制同类新告警,覆盖已有 resolution 则是篡改审计材料(alert.go:2258 起的拒绝逻辑写明了这两条理由)。
消解必须回答"凭什么"。resolution 字段四个值,在复盘时是四种完全不同的结论(常量见 core/pkg/storage/postgres/alert.go:31):
| resolution | 含义 | 典型来源 |
|---|---|---|
| RECOVERED | 触发条件本身解除了(拿到正向证据) | 节点心跳恢复、水位回落、任务离开 failed |
| ENTITY_DELETED | 引用的节点/任务已被删除,故障有没有恢复无从得知 | 孤儿清扫 |
| MANUAL | 运维在告警页手工点了"解决"("我看过了、我认了") | 人工操作 |
| AUTO_CONFIRMED_ABSENT | agent 心跳 docker inspect 实测清单连续多轮不含该任务,容器确已不在 | 幽灵容器缺席证据链 |
消解路径各有分工:resolveRecoveredTaskFailedAlerts(alert.go:748)只认"任务离开 failed 状态"这一条正向证据,任务被删除时它刻意不消解——那不是恢复,交给孤儿清扫按 ENTITY_DELETED 收尾,两条路径分家让两种结论在数据上永远分得开。
孤儿清扫(sweepOrphanedAlerts,alert.go:700)每轮按不变量收口:不管实体是怎么消失的(删除 API、回滚补偿、人工直接 DELETE),引用实体已不存在的告警 30 秒内一定被看见并消解。两条铁律:不删除告警行(告警是审计材料,"这个节点 5 月 28 日离线过"是事实);不记成"已恢复"(无从得知就是无从得知)。2026-10-04 运维行动中该机制在生产环境完成幽灵告警清零。
元告警看门狗(checkStuckAlerts,alert.go:839):任意告警非终态滞留超 72 小时(alertStuckThreshold,alert.go:796)即触发 ALERT_RESOLUTION_STUCK。它先分类再下结论——清扫都清不掉的孤儿 = 消解链路确有故障(ERROR);实体仍在、条件仍成立的 = 运维积压(WARN);分类查询失败 = 如实说"无法判定"。结论类别变化时旧记录归档、新建并重新外发(WARN 升 ERROR 是要叫人的),类别不变则就地刷新计数。看门狗只报警不自动消解:自动 RESOLVE 超期告警正是"用假成功掩盖真实情况"。
停滞性能细节:聚合告警的作用域表里没有实体作用域的类型(有意不参与孤儿清扫——聚合告警即使点名了某个任务,被点名者被删也不代表整批问题消失,各自都有按类型消解的路径)。
节点离线:告警之外的处置联动
NODE_OFFLINE 不只是"叫一声"。判定离线的同时,告警循环会对该节点上的任务做分状态处置(migratableTaskStatuses / nodeGoneFinalizeStatuses,alert.go:257-291):
| 任务状态 | 处置 | 理由 |
|---|---|---|
| running / scheduled / starting / waking | 迁回 pending 换节点重排 | 这些状态只能由该节点的 agent 推进,节点失联后永不自愈 |
| stopping | 就地判 stopped | 用户已下令停止,迁回 pending 等于把停掉的任务重跑一遍 |
| draining | 就地判 completed | 缩容排空方向是终止,容器已随节点消失,"排空并停止"事实上达成 |
| sleeping | 10 分钟宽限后记唤醒需求,交唤醒队列换节点重建 | 重建休眠实例要付模型冷加载代价,短抖动不值得;宽限期内不动 |
只有全部任务处置成功才打迁移标记(失败则下一轮重试),心跳恢复的瞬间标记与宽限计时清零、NODE_OFFLINE 告警按 RECOVERED 消解。服务端自身重启后有启动静默期兜底:重启后的一段时间(等于离线阈值)内不判离线,防止"停机期间没收到的心跳还没补上"被批量误判成全部节点掉线、误迁一堆健康任务。
通知渠道与投递
告警落库成功后由 notifyAlert(alert.go:1912)异步外发:独立 context + 15 秒整体超时(含一次重试),推送失败只记 WARN、绝不影响告警已落库的事实,慢网络也拖不住 30 秒的巡检循环。
装配的是 MultiChannelNotifier(pkg/plugin/plugins/group_bot_notifier.go):
| 渠道 | 加签 | 说明 |
|---|---|---|
| 企业微信群机器人 | 无需 | webhook URL 中 key 即凭据 |
| 钉钉群机器人 | HMAC-SHA256,secret 作 key,毫秒时间戳 | 官方 webhook |
| 飞书群机器人 | HMAC-SHA256,"ts\nsecret" 作 key(方向与钉钉相反),秒时间戳 | 签名校验可选 |
| 通用 webhook | - | 环境变量 ALERT_WEBHOOK_URL 的 legacy 出口,POST 原始事件 JSON,供 Alertmanager/n8n/自建脚本消费,与群机器人并存 fan-out |
四个工程细节:
1. 配置热更新:三渠道配置整块 JSON 存 system_config 表(key=alert_notify_channels),Notify 时按需读取——在设置页改完保存即生效,无需重启服务端。
2. 级别路由:min_level 两档——"all"全推,"error"只推 ERROR/CRITICAL 及以上。
3. 假成功防线:三家 HTTP 200 不代表投递成功,必须解析响应体 errcode/code——"机器人被移出群""加签错误"只有 body 里看得到。
4. 凭据脱敏:GET 从不明文回显 webhook_url/secret——企业微信的凭据就是 URL 里的 ?key=,钉钉是 ?access_token=,明文回显等于泄露凭据。回给前端的只有打码串(前 4 位 + ****)与"已配置"标志;PUT 留空表示保持原值,要改就整段重粘。
设置页每渠道有"发送测试"按钮:走真实网络发一条测试告警到目标群,把真实成功/失败回给前端,不拿"接口返回 200"冒充"群里收到了"。前端 alert.ts 类型定义与后端 20 种告警类型逐项一致,并有 e2e 用例读 Go 源码比对,防漂移。
与容错事件(task_fault_events)的分工:告警是"面向人的运维信号"——回答"有什么在叫、处理到哪一步";容错事件是"任务级处置台账"(DDL 038,core/pkg/faultevent)——在状态转移落库成功后记录"平台对任务做了什么"(迁移、重排、判死),写入是旁路(失败只记 WARN 不影响主流程)。一次节点掉卡会同时产生 GPU_LOST 告警(给人看)和受影响任务的容错事件(给账本看),两者受众与生命周期都不同。
页面功能介绍
/alerts 告警管理页
顶部统计条(4 张卡片):活跃 / 已确认 / 已解决(来自 GET /api/v1/alerts/stats,按调用者可见范围计数)+ 告警规则数。状态栏的 ACTIVE 计数即三态统计中第一键。
筛选器:级别下拉(全部/INFO/WARN/ERROR)与状态下拉(全部/活跃/已确认/已解决),切换即回第 1 页重新取数;右侧"刷新"按钮手动重拉。
告警列表(8 列):级别(彩色徽章)/ 类型 / 消息 / 阈值 / 当前值 / 状态 / 时间 / 操作。前端排序固定为 ACTIVE → ACKNOWLEDGED → RESOLVED、同状态按时间倒序,最要紧的排最前。每页 50 条(PAGE_SIZE=50),底部"共 N 条,第 x/y 页"分页器带上一页/下一页。加载失败不再静默呈现空表,而是红字报错并给重试按钮——"没拉到数据"不被伪装成"没有告警"。
已解决徽章下的 resolution 说明:状态列在 RESOLVED 时追加一行小字,把库里四个值翻译成人话——"条件已解除"(RECOVERED)/"引用对象已删除(非恢复)"(ENTITY_DELETED)/"人工关闭"(MANUAL)/"缺席证据自动确认"(AUTO_CONFIRMED_ABSENT),鼠标悬停显示完整 note。这是面板上防"假成功"的最后一道翻译。
操作列(按角色动态渲染):
| 按钮 | 出现条件 | 调用 | 后端行为 |
|---|---|---|---|
| 确认 | 非 viewer 且状态为 ACTIVE | POST /api/v1/alerts/{id} {"action":"acknowledge"} | 置 ACKNOWLEDGED,首次点击记 acknowledged_at(写一次永不可改,重复点击不推后) |
| 解决 | 非 viewer 且状态为 ACTIVE 或 ACKNOWLEDGED | POST /api/v1/alerts/{id} {"action":"resolve"} | 置 RESOLVED + resolved_at,resolution=MANUAL |
| 删除 | 平台 admin 或租户 admin(任意状态) | DELETE /api/v1/alerts/{id} | 销毁记录;共享资源上的告警仅平台运营者可删(403 带理由透出,前端如实显示不笼统报"删除失败") |
viewer 角色看不到操作列(只读);member 能确认/解决自己可见的告警但不能删除。删除是销毁审计材料,档位比确认/解决高一档。
告警规则区块(仅平台 admin 可见):规则列表(名称/类型/阈值/启用状态)+ 添加/编辑/删除。规则表单四个字段:规则名称、规则类型(下拉 5 种:GPU 高/GPU 显存高/内存高/任务失败/节点离线)、阈值(百分比数字)、启用开关。删除有确认对话框。注意表单只覆盖 5 种资源类常规类型;其余 15 种类型由平台内置路径产生,其中 3 种兜底类的开关(TASK_STALLED/ENDPOINT_UNHEALTHY/GPU_FAULT_PREDICTED)通过规则行的启用状态控制——列表里能看到、能关,阈值不在表单里配。
/settings 设置页 · 告警外发渠道
系统设置页内嵌"告警外发渠道"区块(AdminRoute):顶部推送级别单选(全部告警 / 仅 ERROR 及以上),下面三张渠道卡片(企业微信/钉钉/飞书),每张含启用开关、Webhook 地址输入框、(钉钉/飞书)加签密钥输入框、"发送测试"按钮、可折叠的"如何获取 Webhook"三步图文指引。已配置的 webhook/密钥显示脱敏串占位符"已配置(qyap****),留空保持不变"。保存后回读脱敏结果并提示"即时生效,无需重启"。后端为非 PostgreSQL 存储时该区块显示"未启用多渠道通知器"。
后端逻辑概念表
| 概念 | 定义 | 代码出处 |
|---|---|---|
| AlertMonitor | 30s 一轮的告警巡检协程,持有离线阈值 45s 与迁移/宽限计时 | core/pkg/api/server.go:31;v2/cmd/main.go:907 |
| alertTypeScopes | 类型→作用域登记表,漏登记静态门禁转红 | core/pkg/api/alert.go:55 |
| 作用域三值 | 节点(type,node_id)/任务(type,task_id)/类型聚合,去重与消解必须同键 | core/pkg/api/alert.go:1533 |
| defaultAlertRules | 库中无规则时的 7 条默认规则(rule-1~7) | core/pkg/api/alert.go:125 |
| fail-open 开关 | 规则不存在视为开启,显式 enabled=false 才关闭(TASK_STALLED/ENDPOINT_UNHEALTHY/GPU_FAULT_PREDICTED) | core/pkg/api/alert_stall.go:132 |
| createAlert | 建告警并返回"是否真的写入",落库成功才外发通知 | core/pkg/api/alert.go:1962 |
| resolveAlert | 显式带 resolution 的消解入口,空 resolution 存储层直接报错 | core/pkg/api/alert.go:1552 |
| notifyAlert | 异步外发(独立 context + 15s 超时),失败仅 WARN | core/pkg/api/alert.go:1912 |
| 孤儿清扫 | 引用实体已删除的告警按 ENTITY_DELETED 消解 | core/pkg/api/alert.go:700 |
| 元告警看门狗 | 非终态超 72h 触发 ALERT_RESOLUTION_STUCK,先分类后结论 | core/pkg/api/alert.go:839 |
| refreshStalledAlert | 聚合停滞告警就地刷新内容,created_at/status 不碰 | core/pkg/api/alert_stall.go:780 |
| 启动静默期 | 服务端重启后 NodeOffline 时长内不判离线,防批量误判 | core/pkg/api/alert.go:1142;server.go:49 |
| sleeping 离线宽限 | 节点离线超 10 分钟才为 sleeping 任务记唤醒需求迁回 pending | core/pkg/api/alert.go:337 |
| AlertVisibility | 读接口三档可见范围(运营者/租户 admin/member),fail-closed | core/pkg/api/alert.go:2041 |
| 写权限三档 | ack/resolve 非 viewer 且可见;delete 需 admin 且本租户独占;create 需 system:write | core/pkg/api/alert.go:2055 |
| MultiChannelNotifier | 三家群机器人适配器,配置存 system_config 热读,解析响应体 errcode 防假成功 | pkg/plugin/plugins/group_bot_notifier.go |
| GPU_LOST 判定 | 连续 3 拍缺席判丢、连续 2 拍在场判恢复,判定态经 gpus.lost_at 落库 | core/pkg/api/gpu_loss_detect.go:87 |
| 缺席证据链 | live_task_ids 实测清单连续多轮不含该任务 → AUTO_CONFIRMED_ABSENT,agent ≥ v2.4.9 才认 | core/pkg/api/node_ghost_absence.go |
| task_fault_events | 任务级容错事件台账(处置留痕),与告警(运维信号)分工 | core/pkg/faultevent/faultevent.go;schema.go:610 |
API 参考
| 接口 | 说明 |
|---|---|
GET /api/v1/alerts?level=&status=&limit=&offset= | 告警列表,返回 {data:[...], total:N};可见范围按调用者身份收紧,与 stats 同源判定 |
GET /api/v1/alerts/stats | 按状态计数 {ACTIVE, ACKNOWLEDGED, RESOLVED},同样按身份过滤 |
POST /api/v1/alerts | 创建告警(system:write,仅平台 admin 持有——这是"往值班群发消息"的能力),创建后同样外发 |
POST /api/v1/alerts/{id} | 动作接口,body {"action":"acknowledge"或"resolve"};viewer 403、不可见 404、终态 400 |
DELETE /api/v1/alerts/{id} | 删除告警;需租户 admin/平台运营者,且告警归属本租户独占(共享资源 403) |
GET/POST/PUT/DELETE /api/v1/alerts/rules | 规则 CRUD(system:write);DELETE 用 ?id= 传参;写后即时热加载 |
/api/v1/alert-rules | 规则接口的兼容别名,行为同上 |
GET/PUT /api/v1/alerts/notify-config | 渠道配置读写(system:write);GET 脱敏回显,PUT 留空保持原值,启用渠道必须有 http(s) webhook |
POST /api/v1/alerts/test-notify | 向指定渠道(wecom/dingtalk/feishu)真实发送测试告警,返回 {success, message} |
告警数据模型(alerts 表,schema.go:356):id/level/type/message/node_id/task_id/threshold/value/status/created_at/resolved_at/resolution/resolution_note/acknowledged_at。阈值与当前值两列是 DOUBLE PRECISION 而非 REAL——曾因 float32 存储精度让"读回来的值恒不等于刚算出的值",迁移 017 做了类型收敛,schema 注释里留了实测数据。规则表(alert_rules,schema.go:340)仅 5 个业务字段:id/name/type/threshold/enabled。
已知边界
1. 消解不推送通知:外发只在告警创建时发生一次;告警恢复、实体删除消解均只落库,群内不会收到"已恢复"消息,恢复状态需在面板查看。同问题持续存在期间也不重复推送(就地刷新不外发)。
2. 规则可配维度有限:级别(WARN/ERROR)由各检查代码写死,不可通过规则配置;NODE_OFFLINE 与 TASK_FAILED 的 threshold 字段仅随告警行展示、不参与判定(判定用 45s 常量与 5 分钟回看窗口);停滞类阈值只在 config.yaml 配置。
3. 规则表单只覆盖 5 种类型:前端下拉仅资源类常规类型;其余类型无法通过页面创建规则,兜底类开关靠默认行 enabled 字段控制。
4. GPU_FAULT_PREDICTED 为实验性:预测规则未经真实劣化数据验证,告警文案刻意只说"预测"并注明判据与窗口,不承诺准确率;命中后不自动消解,窗口到期经对账(窗口内确无不可纠正 ECC 增长)才按 RECOVERED 收口。预测规则本身的信号分层与回填口径详见《GPU 健康与故障预测》。
5. 节点作用域告警对普通成员不可见:节点没有"创建者"概念,member/viewer 看不到节点类告警(NODE_OFFLINE/GPU_HIGH/GPU_LOST 等),是否放宽待产品决策(fail-closed:宁可少看,不误放开)。
6. 邮件/Slack 渠道未实现:通知仅企业微信/钉钉/飞书群机器人 + 通用 webhook 出口。
7. 多实例部署时仅主实例巡检:AlertMonitor 收在选主门内,主实例挂掉需重新选主后告警循环才恢复。
8. 依赖 PostgreSQL:非 PG 后端不创建告警、通知器不装配(设置页显示未启用)。
9. 无单条告警详情 GET 接口:/api/v1/alerts/{id} 仅支持 POST(动作)与 DELETE,详情通过列表接口获取。
10. GPU_LOST 连续计数在进程内存:服务端重启后未判定的缺席需重新累计确认;已判定状态经 gpus.lost_at 落库共享,不会因重启放回已判丢的卡。掉卡场景整体未经真机验证。
<!-- 状态标注说明: 真机验证=生产环境实证闭环 / 已实现=代码+测试钉住 / 实验性=机制可用但规则有效性未经真实数据验证 / 占位-不可宣传=未实现,不作宣传 -->