跳到主要内容

告警反复触发或从不恢复

告警反复触发或从不恢复,多半是阈值贴着数据抖动、数据断流被当成恢复、标签集在两次判定间变了,或没设恢复条件。

同一条规则一晚上发了几十条,触发—恢复—触发—恢复;或者反过来,问题早修好了, 事件还挂在那里不走。这两种是同一套机制的两个方向,放在一页里讲。

先做个判断:是事件在抖,还是只有通知在抖? 打开告警事件的历史, 如果历史里真的是一串「触发 / 恢复」交替的记录,那是事件在抖; 如果历史里只有一条一直没恢复的事件,但你收到了很多条消息,那是通知重复, 两者的排查方向完全不同。

三个时间参数,各管一件事​

参数默认值拦的是什么
执行频率@every 60s多久查一次
持续时长60 秒条件成立多久才产生事件 —— 拦上升沿的毛刺
留观时长0(立即恢复)条件不成立多久才判定恢复 —— 拦下降沿的毛刺

留观时长拦的是恢复事件本身,不只是恢复通知。 在留观窗口内,事件仍然是「触发中」, 页面上看得到,只是还没被判恢复。这是抖动最直接的解药,也是开源版 Prometheus 规则 唯一的防抖手段——开源版这类规则没有独立的恢复条件开关,恢复条件永远是触发条件取反, 做不出「90% 报警、跌到 70% 才算恢复」这种非对称阈值。

用触发条件那种表单的类型(Elasticsearch、Loki、ClickHouse 等)才有单独的恢复条件可配。

最常见的一类:数据断了被当成恢复​

这是抖动的头号原因,而且很难一眼看出来。

引擎拿不准「查询返回空」到底是「值降下来了」还是「这段时间的点丢了」, 处理方式是一律当成恢复。所以只要采集断一个周期、时序库丢一批点、 或者查询超时返回空,就会走一次恢复,下个周期数据回来又触发一次。

怎么确认是它:

  1. 打开这条规则的判定记录,找恢复的那个周期,看 anomaly_total 是不是 0, 同时该周期的查询结果 series_total 也是 0 —— 是查不到序列,不是值降下去了;
  2. 对着同一时间去即时查询做区间查询,看曲线上是不是有断点;
  3. 服务端 /metrics 里看 prometheus_tsdb_too_old_samples_total 有没有在涨 (乱序/过期样本被丢,会造成断点)。

确认之后,从下面这几个方向治:

  • 把留观时长设成大于一个采集断点的长度,比如采集间隔 15 秒就设 60~120 秒;
  • 表达式里避免用会因为一个点缺失就整体变空的写法;
  • 断点本身是采集或写入的问题,去 数据源连上了但查不到数据 治根。

顺带说清楚一个反直觉的点:查询报错和查询返回空的行为完全不同。 报错时这一轮的判定和恢复处理整个不做,事件既不刷新也不恢复; 只有正常返回空才会触发恢复逻辑。所以数据源间歇性超时反而不会造成抖动, 它造成的是「状态卡住」。

标签变了,事件就换了一条​

事件是按标签集算哈希来标识的。只要标签集有任何一个键或值变了,就是另一条事件—— 旧的那条因为「不再出现」被判恢复,新的那条作为新事件触发。表现出来就是不停地 触发+恢复,但对象其实一直是同一个。

典型来源:

  • 表达式里带了会变的标签,比如容器 ID、Pod 名、端口、instance 里的临时端口;
  • 规则上挂的工作流里有 relabel 处理器,把标签改来改去;
  • 设备列表里这台机器的标签被人改了,而 Pushgw.LabelRewrite 是开着的(默认开), 改完之后写进去的标签就变了。

怎么确认是它:翻历史事件,把相邻两条「恢复」和「触发」的标签逐个键对比, 只要能找出一个键在变,就是这个原因。治法是在表达式里用聚合把易变标签去掉 (比如 sum without(instance) (...)),或者在工作流里把它 drop 掉。

反过来:从不恢复​

事件挂着不走,按这个顺序看:

  1. 规则还在评估吗。 判定记录停在某个时间点之后就没有了,说明引擎不再跑这条规则, 而没有评估就永远不会恢复。走 判定记录缺失或被丢弃。
  2. 查询一直在报错吗。 报错时恢复处理整个跳过。看 n9e_alert_rule_eval_error_total{stage="query_data"} 有没有在涨, 判定记录里 n9e_alert_eval_query_series_count 是不是负数。
  3. 留观时长设得太长了吗。 窗口没走完,事件就还是触发中。
  4. 对象消失了吗。 机器从设备列表里删了之后,带这个 ident 的事件会被当成屏蔽处理 (判定记录里 detail 是 ident not exists, was muted),它不会自己恢复。
  5. 恢复通知被关了。 事件在页面上其实已经恢复,只是你没收到消息—— 规则上的「恢复后是否通知」是个独立开关,关掉之后恢复事件照样记录,只是不发。

通知在抖,事件没抖​

如果历史里只有一条事件,但消息发了很多条,那是重复通知,不是抖动。控制它的是两个参数:

  • 重复通知间隔(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 计数就是被它挡掉的数量。

收集这些再去提问​

  1. 规则 ID,以及规则上的执行频率 / 持续时长 / 留观时长 / 重复通知间隔四个值;
  2. 抖动时间段内的历史事件列表,保留标签列(这是判断标签漂移的关键);
  3. 同一时间段判定记录里的 anomaly_total、series_total、事件的 stage;
  4. 日志里 event-hash-<哈希> 的所有行。

脱敏:标签值里的主机名、业务名、容器 ID 都替换掉,但要保留「哪几个键在变」这个结构, 否则问题就没法看了。

下一步​