告警反复触发或从不恢复
告警反复触发或从不恢复,多半是阈值贴着数据抖动、数据断流被当成恢复、标签集在两次判定间变了,或没设恢复条件。
同一条规则一晚上发了几十条,触发—恢复—触发—恢复;或者反过来,问题早修好了, 事件还挂在那里不走。这两种是同一套机制的两个方向,放在一页里讲。
先做个判断:是事件在抖,还是只有通知在抖? 打开告警事件的历史, 如果历史里真的是一串「触发 / 恢复」交替的记录,那是事件在抖; 如果历史里只有一条一直没恢复的事件,但你收到了很多条消息,那是通知重复, 两者的排查方向完全不同。
三个时间参数,各管一件事
| 参数 | 默认值 | 拦的是什么 |
|---|---|---|
| 执行频率 | @every 60s | 多久查一次 |
| 持续时长 | 60 秒 | 条件成立多久才产生事件 —— 拦上升沿的毛刺 |
| 留观时长 | 0(立即恢复) | 条件不成立多久才判定恢复 —— 拦下降沿的毛刺 |
留观时长拦的是恢复事件本身,不只是恢复通知。 在留观窗口内,事件仍然是「触发中」, 页面上看得到,只是还没被判恢复。这是抖动最直接的解药,也是开源版 Prometheus 规则 唯一的防抖手段——开源版这类规则没有独立的恢复条件开关,恢复条件永远是触发条件取反, 做不出「90% 报警、跌到 70% 才算恢复」这种非对称阈值。
用触发条件那种表单的类型(Elasticsearch、Loki、ClickHouse 等)才有单独的恢复条件可配。
最常见的一类:数据断了被当成恢复
这是抖动的头号原因,而且很难一眼看出来。
引擎拿不准「查询返回空」到底是「值降下来了」还是「这段时间的点丢了」, 处理方式是一律当成恢复。所以只要采集断一个周期、时序库丢一批点、 或者查询超时返回空,就会走一次恢复,下个周期数据回来又触发一次。
怎么确认是它:
- 打开这条规则的判定记录,找恢复的那个周期,看
anomaly_total是不是 0, 同时该周期的查询结果series_total也是 0 —— 是查不到序列,不是值降下去了; - 对着同一时间去即时查询做区间查询,看曲线上是不是有断点;
- 服务端
/metrics里看prometheus_tsdb_too_old_samples_total有没有在涨 (乱序/过期样本被丢,会造成断点)。
确认之后,从下面这几个方向治:
- 把留观时长设成大于一个采集断点的长度,比如采集间隔 15 秒就设 60~120 秒;
- 表达式里避免用会因为一个点缺失就整体变空的写法;
- 断点本身是采集或写入的问题,去 数据源连上了但查不到数据 治根。
顺带说清楚一个反直觉的点:查询报错和查询返回空的行为完全不同。 报错时这一轮的判定和恢复处理整个不做,事件既不刷新也不恢复; 只有正常返回空才会触发恢复逻辑。所以数据源间歇性超时反而不会造成抖动, 它造成的是「状态卡住」。
标签变了,事件就换了一条
事件是按标签集算哈希来标识的。只要标签集有任何一个键或值变了,就是另一条事件—— 旧的那条因为「不再出现」被判恢复,新的那条作为新事件触发。表现出来就是不停地 触发+恢复,但对象其实一直是同一个。
典型来源:
- 表达式里带了会变的标签,比如容器 ID、Pod 名、端口、
instance里的临时端口; - 规则上挂的工作流里有
relabel处理器,把标签改来改去; - 设备列表里这台机器的标签被人改了,而
Pushgw.LabelRewrite是开着的(默认开), 改完之后写进去的标签就变了。
怎么确认是它:翻历史事件,把相邻两条「恢复」和「触发」的标签逐个键对比,
只要能找出一个键在变,就是这个原因。治法是在表达式里用聚合把易变标签去掉
(比如 sum without(instance) (...)),或者在工作流里把它 drop 掉。
反过来:从不恢复
事件挂着不走,按这个顺序看:
- 规则还在评估吗。 判定记录停在某个时间点之后就没有了,说明引擎不再跑这条规则, 而没有评估就永远不会恢复。走 判定记录缺失或被丢弃。
- 查询一直在报错吗。 报错时恢复处理整个跳过。看
n9e_alert_rule_eval_error_total{stage="query_data"}有没有在涨, 判定记录里n9e_alert_eval_query_series_count是不是负数。 - 留观时长设得太长了吗。 窗口没走完,事件就还是触发中。
- 对象消失了吗。 机器从设备列表里删了之后,带这个
ident的事件会被当成屏蔽处理 (判定记录里 detail 是ident not exists, was muted),它不会自己恢复。 - 恢复通知被关了。 事件在页面上其实已经恢复,只是你没收到消息—— 规则上的「恢复后是否通知」是个独立开关,关掉之后恢复事件照样记录,只是不发。
通知在抖,事件没抖
如果历史里只有一条事件,但消息发了很多条,那是重复通知,不是抖动。控制它的是两个参数:
- 重复通知间隔(
notify_repeat_step,单位分钟):多久重发一次。设成 0 表示 整个告警周期只通知一次。 - 最大通知次数(
notify_max_number):0 表示不限次。
日志里能直接读出每一轮的决策,前缀是 alert_eval_<规则id> datasource_<数据源id> event-hash-<哈希>:
fired, notify_repeat_step_matched(1788440612 >= 1788438872 + 60 * 60) notify_max_number_ignore(#2 / 0)
stalled, notify_repeat_step_not_matched(1788440612 < 1788438872 + 60 * 60)
stalled, notify_repeat_step_matched(...) notify_max_number_not_matched(#5 / 5)
stalled 的意思是:事件还在告警中,但这一轮不发通知,也不写新的历史记录。
从用户视角看就是「安静下来了」。这几行同时也会出现在判定记录的事件 stage 里。
一条规则挂多个级别时,抑制开关会让同一对象只保留最严重的那一条,
关掉它就会同时收到三条——判定记录里 inhibited 计数就是被它挡掉的数量。
收集这些再去提问
- 规则 ID,以及规则上的执行频率 / 持续时长 / 留观时长 / 重复通知间隔四个值;
- 抖动时间段内的历史事件列表,保留标签列(这是判断标签漂移的关键);
- 同一时间段判定记录里的
anomaly_total、series_total、事件的stage; - 日志里
event-hash-<哈希>的所有行。
脱敏:标签值里的主机名、业务名、容器 ID 都替换掉,但要保留「哪几个键在变」这个结构, 否则问题就没法看了。
下一步
- 判定与恢复的完整模型:判定、恢复与状态变化
- 判定周期和恢复参数怎么调:判定周期与恢复
- 数据断点的根因:数据源连上了但查不到数据
- 还有哪些降噪手段:常见噪音与对策