V2 独立版愿景

告警管理

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_HIGHWARN节点全部 GPU 平均计算使用率 ≥ 阈值(默认 90%)节点
资源水位GPU_MEMORY_HIGHWARN节点显存占用(已用/总量)≥ 阈值(默认 85%)节点
资源水位MEMORY_HIGHWARN系统内存使用率 ≥ 阈值(默认 85%)节点
任务事件TASK_FAILEDERROR任务进入 failed 且最后变动在 5 分钟回看窗口内任务
任务停滞TASK_STALLED_PENDINGWARNpending 停留 > 600s(可配)类型聚合
任务停滞TASK_STALLED_SCHEDULEDERRORscheduled 停留 > 120s(可配),派单链路断类型聚合
任务停滞TASK_STALLED_RUNNINGERRORrunning 任务的节点心跳静默 > max(300s, 4×离线阈值)类型聚合
任务停滞TASK_STALLED_STOPPING/DRAINING/WAKINGERROR过渡态停留 > 300s(可配)类型聚合
任务停滞TASK_STALLED_PREEMPTINGERROR抢占确认停滞 > 300s类型聚合
任务停滞TASK_WAKE_QUEUE_STALLEDWARN休眠唤醒排队 > 1800s(训练借卡属正常等待,阈值刻意放宽)类型聚合
账实背离TASK_GHOST_CONTAINERERRORagent 仍在监管容器,服务端账上任务已终态/换节点/不存在(2 分钟宽限)节点
账实背离MIG_PARTITION_ORPHANEDERROR控制面放弃销毁、残留在卡上的 MIG 分区(占 GI 槽位导致超卖)节点
节点故障NODE_OFFLINEERROR心跳静默 > 45s(连丢约 9 拍),启动静默期内不判节点
节点故障GPU_LOSTERROR节点在心跳但登记在册的卡连续 3 拍缺席/采集失败节点
服务健康AGENT_TASK_UNHEALTHYERRORagent 容器健康探针连续失败达阈值节点
服务健康ENDPOINT_UNHEALTHYERROR推理后端被网关探测摘除(连续失败达端点阈值)任务
预测预警GPU_FAULT_PREDICTEDWARN显存硬件错误计数趋势劣化,预测窗口内可能不可纠正错误节点
系统自检ALERT_RESOLUTION_STUCKWARN/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_HIGH90常规开关+阈值
rule-2 内存使用率告警MEMORY_HIGH85常规开关+阈值
rule-3 任务失败告警TASK_FAILED0开关;threshold 仅随告警行展示
rule-4 GPU显存使用率告警GPU_MEMORY_HIGH85开关+阈值(曾长期缺失导致该检查从未触发,后补默认开启)
rule-5 任务停滞告警TASK_STALLED0fail-open 开关;阈值不走规则表,走 config.yaml alerts.task_stall
rule-6 推理后端探测摘除告警ENDPOINT_UNHEALTHY0fail-open 开关;判据在端点 health_check 配置
rule-7 GPU显存故障预测告警GPU_FAULT_PREDICTED0fail-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_ABSENTagent 心跳 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缩容排空方向是终止,容器已随节点消失,"排空并停止"事实上达成
sleeping10 分钟宽限后记唤醒需求,交唤醒队列换节点重建重建休眠实例要付模型冷加载代价,短抖动不值得;宽限期内不动

只有全部任务处置成功才打迁移标记(失败则下一轮重试),心跳恢复的瞬间标记与宽限计时清零、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 且状态为 ACTIVEPOST /api/v1/alerts/{id} {"action":"acknowledge"}置 ACKNOWLEDGED,首次点击记 acknowledged_at(写一次永不可改,重复点击不推后)
解决非 viewer 且状态为 ACTIVE 或 ACKNOWLEDGEDPOST /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 存储时该区块显示"未启用多渠道通知器"。

后端逻辑概念表

概念定义代码出处
AlertMonitor30s 一轮的告警巡检协程,持有离线阈值 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 超时),失败仅 WARNcore/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 任务记唤醒需求迁回 pendingcore/pkg/api/alert.go:337
AlertVisibility读接口三档可见范围(运营者/租户 admin/member),fail-closedcore/pkg/api/alert.go:2041
写权限三档ack/resolve 非 viewer 且可见;delete 需 admin 且本租户独占;create 需 system:writecore/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 落库共享,不会因重启放回已判丢的卡。掉卡场景整体未经真机验证。

<!-- 状态标注说明: 真机验证=生产环境实证闭环 / 已实现=代码+测试钉住 / 实验性=机制可用但规则有效性未经真实数据验证 / 占位-不可宣传=未实现,不作宣传 -->