概述
今年 8 月的一个凌晨,1 点 12 分,我的手机开始连续震动。
某电商平台的结账链路出问题了:支付网关的一个下游连接池被打满,结算服务开始疯狂重试,日志里刷满了 PAYMENT_GATEWAY_TIMEOUT。值班同学在 1 点 19 分就定位到了根因,把连接池上限和超时重试次数调回合理值,1 点 59 分流量恢复。
故障本身不复杂,47 分钟结束。真正让人崩溃的是我的手机:这 10 分钟里,告警通道收到了 400 多条通知,全部来自同一条规则,全部指向同一个问题。
规则长这样:
- alert: CheckoutPaymentTimeout
expr: count_over_time({app="checkout"} |= "PAYMENT_GATEWAY_TIMEOUT" [1m]) > 0
labels:
severity: critical
字面上它没错——日志里确实出现了 PAYMENT_GATEWAY_TIMEOUT。但它表达的意思是"过去 1 分钟里出现过一次",而它被当成了"支付正在坏"。于是每过 1 分钟,日志被"看到"一次,就报一次警;再乘上匹配到的 pod 数量,47 分钟就是几百条。
复盘的时候,我写了一句话贴在值班群里:日志告警刷屏,九成不是阈值设错了,而是把"发生过"当成了"正在发生"。
这篇文章讲的就是怎么修这个问题。核心做法是一句话:日志告警要分两级——记录规则(recording rule)先把事件流转成可以判断的状态指标,告警规则只在状态持续时报警。 日志告警不该是"日志里出现关键词就响",它应该是一个状态机。
拆成 7 个部分:
- 一、日志告警的边界:它适合管黑盒组件、事件型告警和高基数来源,不适合管资源水位和可用性
- 二、误报的根因:日志是事件流不是状态,
count_over_time > 0天生会刷屏,以及为什么必须分两级 - 三、第一级:记录规则:把"出现过"变成"多快、多密、持续多久",标签收敛到什么程度算够
- 四、第二级:告警规则:
for怎么用足、阈值为什么不能用> 0、limit设错会静默丢告警、多租户路由限制 - 五、ruler 的四个生产陷阱:WAL 回放期间不评估、ring 变更没有责任交接、local 存储无 glob、本地评估与 Grafana 不一致
- 六、落地顺序:从规则改造到值班口径的 5 个检查点
- 七、常见反模式清单:七种我都在线上见过的写法,每一种都能让前面的功夫白做
如果你还在用"日志里出现 ERROR 就报警",这篇基本能帮你把夜间被打断的次数降一个数量级。日志采集层怎么搭是另一半问题,可以看相关文章:日志监控体系:Loki + Promtail 部署。
一、日志告警的边界:它适合管什么
先划边界,不然规则一定越写越多。
1.1 三个真正适合日志告警的场景
第一种是黑盒组件。 我们不一定能控制所有组件的源码。负载均衡、第三方网关、老业务的 Java 中间件,有的根本不暴露 metrics。但它们的日志是有的。这时用日志生成一个可用性指标、再对着这个指标告警,是把遗留架构拉进可观测性体系最省事的办法——Grafana Loki 官方文档把这叫黑盒监控。
第二种是事件型告警。 有些问题你只关心"是否发生过一次",不关心持续多久。典型例子是凭据泄露:日志里出现 http://user:pass@ 这种形式的 URL,出现一次就该有人去看。Loki 官方给的示例规则就是这一类:
- name: credentials_leak
rules:
- alert: http-credentials-leaked
annotations:
message: "{{ $labels.job }} is leaking http basic auth credentials."
expr: 'sum by (cluster, job, pod) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)'
for: 10m
labels:
severity: critical
注意这条规则里其实已经藏了分层的雏形:> 0 是瞬时条件,但 for: 10m 把它变成了"持续 10 分钟都有泄露痕迹"(for 的 pending/firing 语义见 Alerting rules | Prometheus)。事件型告警可以用 > 0,但必须配 for 和去重,否则就是上面那条刷了 400 次的规则。
第三种是高基数来源。 这是日志告警最不可替代的地方。假设你想按租户维度告警:给 Prometheus 里任何一个指标加上 tenant 标签,基数就会乘以租户数量,指标库直接爆掉。而在 LogQL 里,聚合发生在查询时——数据留在日志存储里,只有聚合结果进入告警引擎。Loki 官方文档里举的例子是让 Loki 监控自己:
sum by (org_id) (rate({job="loki-prod/query-frontend"} |= "metrics.go" | logfmt | duration > 10s [1m]))
按 org_id 看哪个租户的查询慢过 10 秒。这种维度如果做成指标标签,代价完全不同。
1.2 日志告警不该做的事
边界不清,规则就会长成一片。下面这张表是我实际项目里的分工标准:
| 要监控的东西 | 用指标告警 | 用日志告警 | 判断理由 |
|---|---|---|---|
| CPU、内存、磁盘、连接数等资源水位 | ✅ | ❌ | 指标本身就是数值,且需要判断"持续高水位多久",用指标天然合适 |
| 可用性、延迟、错误率等 SLO 指标 | ✅ | ❌ | 需要长期历史与窗口函数;日志做这件事等于每次查询都全量扫 |
| 关键词首次出现的严重异常(凭据泄露、证书异常) | ❌ | ✅ | 只需"是否发生",指标层面无从下手 |
| 无法改代码的组件存活状态 | 通常不可得 | ✅ | 黑盒监控的经典用法 |
| 按租户/按业务大维度聚合 | ❌ | ✅ | 避免标签基数爆炸 |
| 需要"持续 N 分钟在坏"才能确认的告警 | ⚠️ 优先 | ⚠️ 需分层 | 可以直接做,但必须先做一级记录规则,否则必然刷屏 |
1.3 我的判断:过不了这三问,就别做成告警
我现在写规则之前会先问三个问题:
- 这个东西能不能直接采到指标? 能,就走指标链路,日志链路留给采不到的。
- 告警需要判断"持续多久"吗? 需要,就必须先做记录规则,或者干脆用指标。
- 值班的人收到之后,能不能立刻动手? 如果收到告警后能做的动作是"再打开 Grafana 看一眼",那它不是告警,是 dashboard 该干的事。
第三问最容易被人忽略,但它决定了告警通道会不会被污染。Google SRE 手册(Monitoring Distributed Systems)里反复强调一条:每条页面上报(page)的告警都应该可操作,且只针对症状而不是原因。把一个"看一眼"的东西做成 critical 告警,是在向自己未来的值班要债。
二、误报的根因:把"发生过"当成了"正在发生"
2.1 400 条告警是怎么算出来的
回到凌晨那件事。我们后来复盘时算了一笔账:
告警条数 ≈ ceil(故障持续时长 / 规则评估窗口) × 匹配到的时序数量
= ceil(47 min / 1 min) × (3 个副本 × 2 个环境标签)
≈ 282 ~ 400+
公式里没有一个参数是"错的"。1 分钟窗口是常规选择,3 副本是正常副本数。问题出在整个表达式的语义上:count_over_time(... [1m]) > 0 回答的问题是**“过去一分钟里有没有出现过”,而值班需要知道的是“现在是不是在坏、坏到什么程度”**。
这个表达式的输出是"是/否",而且只要问题还在,它就永远输出"是"。于是它每次评估都触发,每次都发一条新通知,直到问题消失。
这就是刷屏的机制:一个持续存在的问题,被一个瞬时判断反复宣告,每一次宣告都是一条新通知。
2.2 日志是事件流,不是状态
理解这件事,只要跟指标对比一下就清楚了。
Prometheus 的 up == 0 之所以能表达"现在坏了",是因为指标模型本身是周期性采样的:每 15 秒(或任意抓取间隔)目标主动汇报一次"我还在"。它是一条随时间延伸的曲线,缺一个点都意味着异常。
日志不是这样。日志是离散事件。|= "PAYMENT_GATEWAY_TIMEOUT" 匹配到的是一行文本——它在时间轴上的存在只意味着"这件事发生过一次",既不说明"现在还在发生",也不说明"发生了多少次",更不说明"严重程度"。
拿生活里的事打个比方:指标是体温计,每隔一段时间量一次,你能看到"现在 38.5 度,昨天 37 度"。日志是病历本上的一行字,“某日发烧”。你盯着病历本上"发烧"两个字,只能知道发过烧,不知道现在烧不烧。拿"病历本上有发烧两个字"去报警,等于每次翻到那一页就报一次。
2.3 两级分层:记录规则做状态机,告警规则做判断
修法就是把这两件事拆开:
- 第一级(记录规则): 把事件流压缩成数值时序。问的不再是"有没有",而是"多快、多密、占比多少、持续多久"。由 ruler 按固定间隔计算并保存成新的时间序列。
- 第二级(告警规则): 对第一级产出的状态指标做判断,用比值或速率作为阈值,再配
for要求状态持续。
日志流(离散事件)
│
├─ 第一级:记录规则,固定间隔评估
│ ex. sum by (service) (rate({app=~"checkout|payment"} |= "PAYMENT_GATEWAY_TIMEOUT" [5m]))
│ → 产出时序指标 loki:payment_timeout:rate5m{service="checkout"}
│
└─ 第二级:告警规则,判断"状态是否持续"
ex. loki:payment_timeout:rate5m > 0.5 且 for: 10m
→ 一次故障只发一次告警
为什么不干脆把两段逻辑塞进一条 LogQL 表达式,省掉记录规则?三条理由,按重要性排序:
- 告警规则的表达式只能表达"当前这一瞬间的条件",表达不了"过去一段时间的形态"。 你要"最近 10 分钟里,每分钟的速率都超过阈值",写成一条表达式会非常别扭,而且每次评估都在重复消耗查询资源。
- 记录规则的产物可以被别的消费者复用。 dashboard 直接查这个指标就能画图,不用每次刷新都扫一遍日志;需要跨集群统一视图时还能通过 remote_write 写到 Prometheus 或 Mimir。
- 评估成本可控。 一级产出一个低基数的时序,二级只是对几个数字做比较;不分层的规则会在每次评估时把日志查询重跑一遍,规则一多,ruler 的查询压力线性增长。
顺带说一句:这也解释了为什么 Loki 官方把记录规则和告警规则放在同一份规则文件里、由同一个 ruler 组件评估——它们是同一条流水线的两段,不是一个可选的高级特性。关于告警规则和记录规则在 Prometheus 体系里的语义,站内相关文章:告警策略设计:从噪声到信号里有更完整的背景。
三、第一级:记录规则把"事件"变成"可判断的状态"
3.1 记录规则在这里解决了什么
记录规则做的事情说起来很简单:按固定间隔执行一个查询,把结果存成一个新的时间序列。 这套机制在 Prometheus 里已经跑了很多年(Recording rules | Prometheus),Loki 只是把它搬到了日志侧。它的价值不在"省查询",而在它把一个语义上含糊的东西(事件流)变成了一个语义上精确的东西(数值曲线)。
判断"支付是不是在坏",用速率和比值就够精确了:
- 超时事件速率:每秒多少次(绝对值,用于发现"量变")
- 超时占全部请求的比例:百分比(用于排除"流量本来就大"的干扰)
- 是否还在增长:用
deriv()或直接对比前后窗口
这三个问题,日志本身答不了,聚合之后就能答。
3.2 一份可以直接用的记录规则
下面是改造后线上在跑的那组记录规则,去掉业务信息后基本可以照搬。评估间隔 30 秒,窗口 5 分钟:
groups:
# 第一级:把日志事件流转成低基数状态指标
- name: log_derived_state
interval: 30s
limit: 2000
rules:
# 1) 超时事件速率:每秒发生多少次支付网关超时
- record: loki:payment_timeout:rate5m
expr: |
sum by (service) (
rate({app=~"checkout|payment"} |= "PAYMENT_GATEWAY_TIMEOUT" [5m])
)
# 2) 结算链路总请求速率:作为分母
- record: loki:checkout_requests:rate5m
expr: |
sum by (service) (
rate({app="checkout"} |= "order_submit" [5m])
)
# 3) 超时占比:这才是告警该用的那个数
- record: loki:payment_timeout:ratio5m
expr: |
loki:payment_timeout:rate5m
/
loki:checkout_requests:rate5m
# 4) 错误日志占比:通用的链路健康度指标,dashboard 也在用
- record: loki:service_error:ratio5m
expr: |
sum by (service) (rate({app=~"checkout|payment"} |~ "(?i)\\b(error|exception|fatal)\\b" [5m]))
/
sum by (service) (rate({app=~"checkout|payment"} [5m]))
四个规则里只有第 1、2 条碰了原始日志,第 3、4 条纯粹是数值运算——这就是分层最直接的成本收益:昂贵的日志查询每次评估只跑一遍,下游所有消费者复用它的结果。
3.3 标签收敛:只留"值班时会用来定位"的维度
记录规则的产出是时序,就必须谈基数。我的原则只有一条:
标签就是值班时你会用来"切分"和"定位"的维度。切不动、定位不了的,一律不要进标签。
对照着看几个常见写法:
| 写法 | 是否进标签 | 原因 |
|---|---|---|
service / app | ✅ | 定位到服务是最小可操作单位 |
env / cluster | ✅ | 生产/预发、哪个机房,是路由和升级决策的依据 |
pod / instance | ⚠️ 谨慎 | 副本数一变基数就变;定位到实例通常靠 dashboard,不靠告警标签 |
tenant_id | ⚠️ 谨慎 | 除非确实按租户告警,否则基数随客户数线性增长 |
order_id / trace_id / request_id | ❌ | 无限基数,一旦进标签,ruler 会很快撑不住 |
error_code(有限枚举) | ✅ | 枚举可控且能直接指向修复动作,值得进 |
有一个特别容易犯的错误:把 pod 保留在告警规则的标签里。原因是在 K8s 环境里 pod 名带随机后缀,一次发布就是全新的一批时序。结果是同一次故障,在发布前后会被当成两个不同的问题,历史告警也对不上。除非你的告警确实需要精确到实例(比如"某台机器磁盘坏了"),否则定位到 service 就停。
3.4 改造前后的实测对比
回到那个凌晨的场景。改造前后同一个故障形态(支付网关下游连接池打满)的表现:
| 观察项 | 改造前(单级瞬时规则) | 改造后(两级分层) |
|---|---|---|
| 一次故障产生的告警条数 | 400+ | 9 |
| 首次触达时间 | 5 秒 | 约 10 分钟(for: 10m 生效后) |
| 值班夜间被打断次数(当月) | 11 | 1 |
| 故障定位耗时(中位数) | 30 分钟 | 8 分钟 |
| 告警确认率(人工确认"这条有用") | 约 1/4 | 接近 100% |
| MTTR | 约 62 分钟 | 约 37 分钟 |
| 规则评估带来的额外查询负载 | 每次评估全量扫日志 | 一级 1 次查询,二级仅数值比较 |
这张表里有两个数字值得单独说。
第一,“首次触达从 5 秒变成 10 分钟"是这次改造主动付的代价。 有人会问:那这 10 分钟里业务不就一直坏着?不会——10 分钟是告警引擎的确认窗口,不是发现窗口。真正的发现手段是 dashboard 和 SLO 燃烧率曲线(Alerting on SLOs);而"支付网关超时占比超过 2% 且持续 10 分钟"这条告警,它要拦的是"需要人介入并做决策的故障”,不是"需要人知道的变化"。
第二,“告警确认率从 1/4 到接近 100%“才是真正的收益。 告警疲劳的代价不在打扰次数,在于它会训练值班同学形成"这批告警大概不用看"的条件反射。行业调研里的数字更具体:运维团队有将近四成的工作时间花在告警分析上(当告警风暴来袭:IT 运维如何应对"信息过载"困境);企业安全运营中心平均每天收到数千条告警,分析师只来得及认真处理大约三分之一,超过六成的团队承认曾经忽略过事后被证实为真实的告警(安全告警疲劳的成因与四级降噪实践)。运维告警是同一个机制,只是名字不同。
3.5 记录规则也会把时序撑爆:一次标签失控的复盘
分层能治刷屏,但记录规则本身干的是"把日志变成时序"的活,所以它天然会把基数问题从日志侧搬到指标侧。这一步踩进去,代价比刷屏大得多。
2025 年上半年,我们在某出行项目的日志告警改造里就踩过。第一版记录规则是这么写的:
- record: loki:order_timeout:rate5m
expr: |
sum by (service, pod, endpoint, tenant_id) (
rate({app=~"order|dispatch"} |= "TIMEOUT" [5m])
)
写的时候想法很"合理”:既然要定位问题,维度当然越全越好——服务、实例、接口、租户,一个都不落。上线四个小时后,现象来了:
- ruler 的单次规则评估从平均 400 毫秒涨到 4 秒以上,个别评估周期开始超时;
- 每个 ruler 副本的 WAL 目录从几百 MB 涨到十几 GB;
- Loki 的 ingester 内存出现明显抖动,写入侧 P99 延迟从 30 毫秒涨到 200 毫秒。
根因就是标签基数。记录规则的产出是时序,新增时序数 ≈ 规则条数 × 各标签取值个数的乘积。那套环境里:
service(12) × pod(4) × endpoint(37) × tenant_id(600)
= 1,065,600 条时序(理论上限)
实际四个小时落库了 62 万条——没到上限,只是因为大量维度组合根本没有数据。但对 ruler 和 Loki 来说,这已经是灾难级的量。把标签一刀砍到两个维度之后,同一条规则的表现:
| 标签组合 | 单条规则产出的时序数 | 上线 4 小时后的表现 |
|---|---|---|
service + env | 24 | 稳定,WAL 体积无变化 |
service + pod | 48 | 数字不大,但每次发布都会产生一批全新时序,历史告警对不上 |
service + pod + endpoint | 1,776 | ruler 评估变慢,WAL 明显增长 |
service + pod + endpoint + tenant_id | 1,065,600 | 4 小时后 62 万条时序,ingester 内存抖动 |
我的经验阈值是:一条记录规则理论上能产出的时序数超过 1 万条,就不要上。 一个 20 服务、每服务 4 副本的环境,by (service) 是 20 条,by (service, pod) 是 80 条,都安全;一旦引入 endpoint 或 tenant_id,量级直接跳进五位数。上线前用一天的历史数据算一遍新增 series 数,比上线后救火便宜得多。
有人会担心"标签收敛了,定位不到具体实例怎么办”。这个担心问错了方向:告警标签的职责是"指向要处理的单元",不是"装下所有上下文"。 原始日志里的 pod、endpoint、tenant_id 字段一个都没丢,告警里带上 service 和 env,值班同学点进 dashboard 或直接查一次 LogQL,完整上下文都在。把排查用的维度塞进告警标签,等于让告警引擎替 Grafana 存索引——代价高、收益低。
3.6 分层之后,评估成本到底降了多少
“分层更省资源"这句话要有数字才算数。以下是我们在日志摄入约 120 GB/天、2 个 ruler 副本的环境里实测的对比(改造前 22 条单级告警规则;改造后 8 条记录规则 + 14 条告警规则):
| 观察项 | 改造前 | 改造后 |
|---|---|---|
| 每小时触达日志存储的聚合查询次数 | 约 1320 次(22 条 × 每分钟) | 约 960 次(8 条 × 每 30 秒) |
| 单次规则评估 P95 耗时 | 1.9 秒 | 0.4 秒 |
| Grafana 面板首屏加载 | 3.5 秒(每次刷新重新扫日志) | 1.2 秒(直接读预计算好的时序) |
| 每周因评估超时导致的漏评估 | 3~5 次 | 0 次 |
两个数字要解释一下。
第一,“每小时日志查询次数只降了 27%“这件事别过度解读。 真正的收益不在这 27%:改造前那 1320 次查询每一轮都在扫全量日志,改造后这 960 次全部集中在记录规则上,二级告警规则只是对几十个浮点数做比较。换句话说,昂贵的日志聚合从"与规则条数成正比"变成了"与记录规则条数成正比”——后来我们把告警规则从 14 条加到 40 条,日志查询次数一次没涨。
第二,面板加载时间和漏评估次数才是值班同学能直接感知的变化。 一个并发 30 人的值班室,原来每次刷新 dashboard 都会触发一轮日志扫描;改造后这些查询全部命中预计算指标。还有一个容易忽略的连带收益:记录规则的产物可以通过 remote_write 写给 Prometheus 或 Mimir,需要跨集群统一视图时,搬的是聚合结果,不是日志数据。
四、第二级:告警规则怎么写才不刷屏
4.1 for 是唯一的降噪开关,把它用足
for 的语义在 Prometheus 和 Loki 里是一致的:表达式为真之后,告警先进入 pending 状态,只有连续满足 for 指定的时长,才转为 firing 并真正发出去。中间只要有一次评估不满足,状态回退到 inactive。
- alert: CheckoutPaymentTimeoutRatio
expr: loki:payment_timeout:ratio5m{service="checkout"} > 0.02
for: 10m # 连续 10 分钟超时占比都超过 2% 才发
labels:
severity: critical
annotations:
summary: "结算链路支付超时占比过高"
description: "{{ $labels.service }} 支付网关超时占比 {{ $value | humanizePercentage }},已持续 10 分钟"
for 值怎么定,我给一个可复制的判断方法:拿这个故障的历史数据回看,找出"业务真正开始受损"到"人工介入有效"之间的最短时间窗,取它的 1.5 倍。
- 瞬时抖动类(网络抖动、GC 停顿、单次重试失败):
for: 5m起步,这类问题的自我恢复时间通常在分钟级。 - 容量类(连接池、线程池、磁盘、队列堆积):
for: 10m。这类问题有个特点——它不会自己好,所以等 10 分钟不会损失止血窗口,反而能过滤掉"业务量瞬时尖峰”。 - 事件型(凭据泄露、配置被改、证书异常):
for可以很短甚至用0m,但必须保证这条规则的触发频率天然很低。
4.2 阈值不要用 > 0
> 0 在告警规则里几乎是反模式,因为它把"存在性"当成了"异常程度”。三个替代写法:
- 比值:
错误数 / 总数 > 2%。好处是自动适应流量变化,大促期间不会因为总量上涨而误报。 - 速率:
每秒错误数 > 5。适合"错误在一定量以下就是正常背景噪声"的场景,比> 0精确得多。 - 带节奏的速率:
rate(...[5m]) > 2 * rate(...[1h] offset ...)。适合"突然变化就是异常"的场景,但可读性差,容易自己看晕,我不太推荐在告警里用。
唯一合理的 > 0 用法是事件型告警,而且必须同时满足两个条件:这条规则的候选触发频率天然很低(比如凭据泄露),以及配了足够的 for 做去重。否则请把它换成比值。
4.3 limit:它能救你,也能静默地把告警全吃掉
Loki 支持在规则组上设 limit,用来限制一组规则能产出多少条告警或样本。这个配置值得单独讲,因为它的失败模式很反直觉。
官方文档里写得很清楚:当超出上限时,该组记录规则产生的所有样本会被丢弃;如果是告警规则,该规则所有告警——active、pending、inactive 全部——会被清除,事件记为 error,规则健康状态置为 err。 默认值是 0,表示不限制。
翻译成人话:limit 是一个熔断器。它保护 ruler 不被一条失控的规则(比如某个标签基数意外爆炸的表达式)拖死,但熔断之后不是"少发几条",而是**“这一组规则的告警全部消失”**。如果你只监控了告警通道的存在性,会发现的现象是"线上炸了,但告警没来"——比刷屏危险得多。
我的用法:
- 给每条高基数风险的规则单独开一个组,设一个略高于正常峰值的
limit(比如正常峰值 5 条,limit: 50)。 - 把规则健康状态纳入元监控:任何规则组
health=err都要触发一条告警。这条告警的接收人不该是业务值班,而该是平台负责人。 - 绝不在一个包含 20 条规则的组上统一设
limit,那等于给整组挂了一个共同的熔断开关。
4.4 多租户与路由:alertmanager_url 是全局单一配置
这是 Loki 部署里最容易被误解的一点:ruler 的 alertmanager_url 是全局唯一的,一个 Loki 集群只能指向一个 Alertmanager。 它不能按租户(tenant)路由到不同的 Alertmanager,也不能给某条规则单独指定目的地。
多租户场景下的可行做法只有一条:让所有租户的告警都流向同一个 Alertmanager,在 Alertmanager 侧用租户标签做路由和隔离。 也就是说,把"谁该收到"的职责从 Loki 挪到 Alertmanager:
# Alertmanager 侧:用 Loki 附带的 tenant / labels 做分流
route:
receiver: default-oncall
group_by: ["alertname", "service", "tenant"]
routes:
- matchers: ['tenant="tenant-a"']
receiver: tenant-a-oncall
- matchers: ['severity="critical"']
receiver: pagerduty
顺带提醒一句:Loki 的 enable_alertmanager_v2 从 3.2.0 起默认就是 true,如果你的 Alertmanager 还是 v1 API 的老版本,升级 Loki 时会在这里踩坑。Alertmanager 侧的完整路由与抑制写法,站内相关文章:Alertmanager 告警路由与抑制策略讲得比较细。
4.5 完整可复制的两级规则文件
把上面所有内容拼起来,这是一份可以直接落库的规则文件(第一级 + 第二级在同一个文件里,因为同一个 ruler 会一起评估):
groups:
# ============ 第一级:记录规则,产出状态指标 ============
- name: log_derived_state
interval: 30s
rules:
- record: loki:payment_timeout:rate5m
expr: |
sum by (service) (
rate({app=~"checkout|payment"} |= "PAYMENT_GATEWAY_TIMEOUT" [5m])
)
- record: loki:checkout_requests:rate5m
expr: |
sum by (service) (rate({app="checkout"} |= "order_submit" [5m]))
- record: loki:payment_timeout:ratio5m
expr: |
loki:payment_timeout:rate5m / loki:checkout_requests:rate5m
# ============ 第二级:告警规则,只在状态持续时报 ============
- name: checkout_alerts
interval: 30s
limit: 50
rules:
# 状态型:用比值,配 for
- alert: CheckoutPaymentTimeoutRatio
expr: loki:payment_timeout:ratio5m{service="checkout"} > 0.02
for: 10m
labels:
severity: critical
team: checkout
annotations:
summary: "结算链路支付超时占比过高"
description: "{{ $labels.service }} 超时占比 {{ $value | humanizePercentage }},已持续 10 分钟"
# 事件型:允许 > 0,但必须配 for,且天然低频
- alert: CredentialsLeakedInLog
expr: |
sum by (service) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)
for: 10m
labels:
severity: warning
team: platform
annotations:
summary: "生产日志中出现明文凭据"
description: "{{ $labels.service }} 的日志里检测到疑似明文凭证,请立即确认并轮换"
落库前先做语法校验。如果 Loki 装了 lokitool,可以直接对本地规则目录做检查和比对:
# 格式化并对齐 PromQL/LogQL 表达式
lokitool rules format ./rules/log-alerting.yaml
# 对比本地规则与 Loki 里正在生效的规则(多租户需指定租户 ID)
lokitool rules diff --rule-dirs=./rules --id=fake
# 让远端与本地一致(会创建/更新/删除差异项)
lokitool rules sync --rule-dirs=./rules --id=fake
注意 --id=fake:这是单租户模式(auth_enabled: false)下的默认租户名。多租户环境要换成真实租户 ID,也可以通过 LOKI_TENANT_ID 环境变量传入。另外,Loki 3.1 之前这个工具叫 cortextool,命令参数大体相同。
五、ruler 的四个生产陷阱
这一节是这篇文章里我最想让人看到的部分。前四节讲的是"规则怎么写",这一节讲的是"规则写完为什么不生效、什么时候会静默失联"——这些话散落在官方文档的不同页面里,出事之前很难注意到。
5.1 陷阱一:ruler 回放 WAL 期间,不评估任何规则
Loki 的 ruler 会为每个租户维护一份 WAL(预写日志),用来在调度或分片变化时"懒加载"过去的评估结果。启动时,ruler 会逐个加载并回放这些 WAL——而官方文档明确写着:在回放期间,ruler 不评估任何规则,包括告警规则。
这意味着:ruler 滚动重启或升级的那段时间里,所有日志告警是静默的。 不报错,不告警,就是不发。
应对方式有三个:
- 给 ruler 加独立的存活告警。 不要用日志链路自己监控自己——ruler 停机时它什么都发不出来。这条告警必须来自指标链路(Prometheus 抓 ruler 的
/metrics)。 - 控制规则规模,缩短回放时间。 回放时间和 WAL 里的规则数量、每组规则的规模都相关,文档里也建议控制每组规则的数量。
- 把 ruler 的滚动升级安排在低峰期,并且和 ingester 的升级错开。 两者的重启窗口叠加在一起,会让你在同一个时间窗里同时丢失数据写入和告警评估。
5.2 陷阱二:ring 一变,所有规则重新随机分片,而且没有责任交接
单机 ruler 扛不住时就要做分片(enable_sharding: true,需要一个 ring)。这里有个和 ingester 很不一样的细节:
ingester 在扩容/缩容时会把责任"交接"给新的实例;ruler 不交接。 官方原文的意思是:每次有 ruler 实例加入或退出 ring,所有规则都会被重新随机分片。
后果是:扩容一个 ruler 副本,全量规则会重新分配一遍。这个过程中,规则的评估节奏会出现抖动——某些规则可能晚几个评估周期才有结果,某些规则可能瞬间被两个实例同时评估一次。对 30 秒或 1 分钟周期的告警规则来说,这种抖动大概率会让 for 的连续计数被打断,表现为"故障发生了但告警迟迟不来"。
我的做法很朴素:
- ruler 的扩缩容要单独做窗口,不要跟着应用发布一起做。 我们曾经在一次例行发布里顺手把 ruler 从 2 副本扩到 3 副本,结果当晚一条低频告警的
for计数被打断,比预期晚了一个多小时才发出来。故障不大,教训不小。 - ring 的 kvstore 用稳定后端(Consul/etcd),别用 inmemory。
inmemory在单进程演示里够用,多副本下等于每个 ruler 各有一张自己的 ring,分片结果彼此不一致,规则会被重复评估或漏评估。 - 规则组的
interval不要设得太贴近for的粒度。 比如interval: 30s配for: 1m,等于只允许一次评估波动;把for提到 5 分钟以上会稳定得多。
5.3 陷阱三:local 存储是只读后端,没有 glob,还会忽略子目录
用 ConfigMap 挂载规则文件是很多团队的选择(ruler.storage.type: local)。这里有三个必须知道的行为:
第一,目录结构必须平铺。 local 后端读取租户目录下的所有非隐藏文件,但不支持通配符匹配,且会忽略子目录。这是和 Prometheus 最不一样的地方——Prometheus 的 rule_files 可以用 /etc/prometheus/rules/*.yaml 这种 glob,很多人顺手就按原来的习惯组织目录:
/tmp/loki/rules/fake/
├── alerts/
│ └── checkout.yaml ← 被忽略,规则永远不会加载
└── recording/
└── state.yaml ← 被忽略
现象是"规则文件明明挂进去了,但 Loki 里一条规则都没有,也没有明显报错"。正确的布局是平的:
/tmp/loki/rules/fake/
├── checkout-alerts.yaml
├── log-derived-state.yaml
第二,租户目录名必须和租户 ID 对得上。 单租户模式(auth_enabled: false)下默认租户是 fake;如果你启动时用了 -auth.no-auth-tenant 自定义了值,目录名也要跟着改。如果租户目录不存在,ruler 不会退出——它会每次同步规则时报一条 error 日志(unable to list rules),并且不评估任何规则继续运行。这是最阴的一种失败:进程健康、端口正常、就是没有告警。
第三,别把 rule_path 和存储目录搞混。 ruler.storage.local.directory 是 ruler 读取规则文件的地方;rule_path(默认 /rules)是评估期间写临时文件的暂存目录。两者是不同用途,只读文件系统上必须把 rule_path 指到可写路径,否则 ruler 会在评估时写文件失败。
针对第三点,我加了一条非常便宜的兜底元告警:监控"当前加载的规则组数量"和"规则健康状态"。规则条数从 12 掉到 0,或者出现 health=err 的组,立刻告警。这条规则救过我们不止一次。
5.4 陷阱四:本地评估和 Grafana 里看到的结果不一致
规则评估有两种模式。默认的本地评估模式下,ruler 自带一个 querier,不经过 query-frontend 和 query-scheduler——这意味着它受到和普通查询一样的限制,但在大规模部署里,官方文档明确说了一句关键的话:规则在本地评估时,结果可能和你在 Grafana 里看到的不一致或不完整。
原因不难理解:大集群里同一份日志数据分布在多个 ingester 上,本地评估走的是 ruler 自己的查询路径,而 Grafana 走的是完整的 query-frontend 链路(含查询拆分、分片聚合、结果合并)。两条路径的语义边界不同,结果自然会有差异。
修法是切到远程规则评估(remote rule evaluation):让规则评估也走 query frontend。代价是评估流量进入查询链路,需要给查询侧留容量。
我的判断很直接:
- 单集群、规则简单(只有几条 rate/ratio)、日志量在每天百 GB 量级以下:本地评估够用,先别上远程评估,省一份容量规划。
- 多集群联邦、规则含大量
by (...)聚合、或者你在 dashboard 上见过"同一个数两个页面不一样":切远程评估,这不是性能优化,是正确性问题。
5.5 让规则可验证:把它当成代码管起来
上面四个陷阱里,有三个的共性是"配置看起来生效了、实际没有"。这类问题靠人眼审查看不出来,只能靠工具。
规则文件本身就是代码,我用三层来管:
第一层,语法校验。 Prometheus 生态里的 promtool check rules 能校验规则文件语法(Loki 的规则文件是 Prometheus 兼容格式,只是表达式换成 LogQL);Loki 自己的 lokitool rules format / diff 也能做格式化和差异比对。
第二层,CI 里做 diff 门禁。 Grafana 提供的 cortex-rules-action 支持指定 Loki 作为后端,在流水线里做 lint 和 sync:
- name: Lint Loki rules
uses: grafana/cortex-rules-action@master
env:
ACTION: check
RULES_DIR: rules/log-alerting
BACKEND: loki
- name: Deploy rules to Loki
uses: grafana/cortex-rules-action@master
env:
ACTION: sync
RULES_DIR: rules/log-alerting
BACKEND: loki
CORTEX_ADDRESS: http://loki-gateway.monitoring.svc:80
CORTEX_TENANT_ID: fake
把 ACTION: check 放在合并请求的必过项里,能把"表达式写错"“标签写错"“引用了不存在的指标"这类问题挡在部署之前。这里有个我踩过的坑:RULES_DIR 支持逗号分隔的多个目录,但目录里不要再用子目录分层,原因就是 5.3 里说的——local 后端会忽略子目录,CI 通过了而运行时照样不加载。
第三层,Terraform 声明式管理。 社区有 Loki 的 Terraform provider,可以把规则组当成资源来管理(loki_rule_group_alerting / loki_rule_group_recording),配合 terraform plan 就能在变更前看到"这次会新增/删除哪些规则”。想让规则走完整的代码评审流程,这条路是最顺的。
5.6 四个陷阱的排障对照表
这四个陷阱的共同点是"不报错”。下面这张表是我放在值班手册里的速查版本,按"现象 → 怎么确认 → 怎么修"三段来记:
| 陷阱 | 一线现象 | 怎么确认(5 分钟内) | 修复动作 |
|---|---|---|---|
| WAL 回放期间不评估 | ruler 重启后一段时间告警完全静默,进程本身健康 | 把 ruler 重启时间点和告警缺口时段对齐看;ruler 日志里能看到逐个租户加载并回放 WAL 的记录 | ruler 存活告警改由指标链路发出;控制规则组规模;滚动升级与 ingester 升级错峰 |
| ring 变更重新分片 | 低频告警比平时晚几十分钟到几小时;偶尔同一条告警重复发 | 把 ring 成员的变更时间和该告警 for 计时的起点对齐 | ring 用 Consul/etcd 而不是 inmemory;扩缩容单独开窗口;for 别贴 interval 太近 |
| local 存储忽略子目录 | 规则文件明明挂进去了,Loki 里一条规则都没有,或者只加载了一部分 | 用 lokitool rules print 看远端实际生效的规则集;检查目录是否平铺、租户目录名是否等于租户 ID | 目录拍平;rule_path 指向可写路径;补一条"加载规则组数量"元告警 |
| 本地评估结果不一致 | dashboard 上的曲线和告警判断的数值对不上,出现漏发或误发 | 同一段 LogQL 分别从 Grafana 和规则评估路径各查一次,对同一个时间窗比数值 | 切到远程规则评估(remote rule evaluation),给查询链路预留容量 |
关于"怎么确认",有两个命令值得写进值班手册:
# 1) 打印远端 ruler 里当前生效的规则集(地址参数以 lokitool rules print -h 的输出为准)
LOKI_TENANT_ID=fake lokitool rules print
# 2) 确认 ruler 到底暴露了哪些指标名,再决定元告警怎么写
curl -s http://loki-ruler:3100/metrics | grep -i 'ruler' | cut -d'{' -f1 | sort -u | head -30
第二条命令是我特别想强调的习惯:别凭记忆猜指标名。 ruler 暴露的指标名在不同大版本之间改过,直接从 /metrics 里捞出真实名字再写元告警,能省掉一次"写了条永远不会触发的告警"。
以及一条几乎零成本、必须加的兜底(用的也是所有 Prometheus 目标都有的 up,不依赖任何 Loki 专有指标):
groups:
- name: loki_ruler_meta
rules:
- alert: LokiRulerTargetDown
expr: up{job="loki-ruler"} == 0
for: 3m
labels:
severity: critical
team: platform
annotations:
summary: "Loki ruler 进程不可用,所有日志告警处于静默状态"
description: "ruler 停机期间不会评估任何规则,请立即确认。"
这条告警必须由 Prometheus(指标链路)发出,绝不能由 Loki 自己的告警链路发出——ruler 就是那条链路的执行者,它停了就没人能报信。
六、落地顺序:从规则改造到值班口径的 5 个检查点
如果这套东西要在一个已经跑了很多规则的环境里落地,顺序比内容重要。我的顺序是:
第一步,先把现有日志告警按"事件型/状态型"分类。 这是零成本的纸面工作,但能立刻看清问题的分布。经验上,一个用了两三年的环境里,状态型的日志告警占七成以上,而它们全都是潜在的刷屏源。
第二步,把状态型全部改成两级。 先写记录规则,再把告警规则的 expr 换成对记录规则产物的判断。这一步做完,告警条数通常会掉一个数量级。改造期间可以先保留旧规则,用不同的 severity 标签做对比观察,确认新的更准之后再删旧的。
第三步,阈值从 > 0 换成比值,for 提到 5 分钟以上。 这两条改动是这次改造里投入产出比最高的:改动量是改几个数字,效果是夜间打断次数大幅下降。
第四步,把元监控补齐。 至少三条:ruler 进程存活(从指标链路来)、加载的规则组数量、规则健康状态。这三条是前面所有陷阱的兜底,缺了它们,前面做的一切都可能在某次重启后静默失效。
第五步,把告警指标纳入事后复盘的固定字段。 我们复盘模板里现在固定有四个数:本次故障产生的告警条数、人工确认"有用"的比例、夜间被打断次数、首次触达时间。没有这四个数,“告警体系在变好还是变坏"就永远是一个凭感觉的判断。
还有两件事要在改之前想清楚,属于容量和回滚:
- 容量评估:记录规则会新增时序。新增时序数 ≈ 记录规则条数 × 每个规则产出的 series 数。我们的经验是每 10 条记录规则(标签收敛到
service+env)大约新增几百到一两千条 series,对 Loki 自身的存储压力影响很小,但如果标签失控(比如带上了 pod 或 tenant),会直接放大几十倍。上线前先用一天的数据算一遍新增 series 数。 - 回滚方案:规则文件是声明式的,回滚就是把上一版规则文件重新应用一次(
lokitool rules sync或 Terraform apply)。所以规则文件必须进 Git,而且要能回答"上周三凌晨那版规则文件长什么样”。我建议规则仓库单独留一个目录放"回滚快照",每次上线前把当前线上生效的规则集完整导出一次。
6.1 回到那条坏规则:三次改法,一次比一次准
前面讲的是方法论,这一节把开头那条刷屏规则真改一遍,让你看清每一步换来的是什么。
v1,原始规则:
- alert: CheckoutPaymentTimeout
expr: count_over_time({app="checkout"} |= "PAYMENT_GATEWAY_TIMEOUT" [1m]) > 0
labels:
severity: critical
v2,把"瞬时存在"换成速率,并加上 for:
- alert: CheckoutPaymentTimeoutRate
expr: |
sum by (service) (
rate({app="checkout"} |= "PAYMENT_GATEWAY_TIMEOUT" [5m])
) > 0.5
for: 5m
labels:
severity: critical
v3,把速率换成占比、把窗口拉长,并把表达式拆成两级:
# 第一级:记录规则
- record: loki:payment_timeout:ratio5m
expr: |
sum by (service) (rate({app=~"checkout|payment"} |= "PAYMENT_GATEWAY_TIMEOUT" [5m]))
/
sum by (service) (rate({app="checkout"} |= "order_submit" [5m]))
# 第二级:告警规则
- alert: CheckoutPaymentTimeoutRatio
expr: loki:payment_timeout:ratio5m{service="checkout"} > 0.02
for: 10m
三版在同一个 47 分钟故障下的表现:
| 版本 | 关键改动 | 一次故障的告警条数 | 残留问题 |
|---|---|---|---|
| v1 | 无(基线) | 400+ | 把"出现过"当"正在发生" |
| v2 | > 0 换成速率阈值,加 for: 5m | 4 | 绝对值阈值不随流量变化 |
| v3 | 速率换成占比,for 提到 10 分钟,拆成两级 | 1 | 需要先有分母指标;首次触达晚 10 分钟 |
v2 → v3 那一步最容易被跳过,但它是从"能用"到"可信"的分水岭。绝对值阈值的麻烦在于它同时制造误报和漏报:业务量翻三倍时,同一个阈值会被轻易冲破;业务量掉到三分之一时,真正严重的错误反而够不到阈值。换成占比之后,阈值表达的是"这条链路的失败率",和流量解耦,才谈得上稳定。
改完之后怎么验证?我的做法是用一天的历史数据回放一遍:在 Grafana 上把新的比值指标画成曲线,再把旧规则的触发时段叠上去,看新规则会在哪些时刻进入 pending、哪些时刻转 firing。只看"新规则上线后没报警"不算验证——它可能只是还没遇到那个故障形态。
6.2 告警通道的口径也要一起改
规则改完,如果通知本身还是只写一句"结算链路支付超时占比过高",值班同学照样得打开 Grafana 才能决定做什么。所以最后一件事是把通知内容分级定下来:
annotations:
summary: "结算链路支付超时占比 3.4%,已持续 11 分钟"
description: "服务:{{ $labels.service }}|当前占比:{{ $value | humanizePercentage }}|已持续:11m"
三个要点:
- 通知里必须带数值和持续时间。 拿到"占比 3.4%、持续 11 分钟"这两个数,值班同学可以直接判断要不要立刻介入;只有一句"占比过高",等于把判断成本转嫁给被叫醒的人。
severity要和响应预期绑定,不能由规则自由填。
| 级别 | 触发条件 | 通知方式 | 期望响应 |
|---|---|---|---|
critical | 占比 > 2% 且持续 10 分钟 | 电话 + 值班群 | 15 分钟内介入 |
warning | 占比 > 0.5% 且持续 5 分钟,或超时速率环比涨 3 倍 | 值班群 | 当班内查看 |
info | 规则健康状态异常、加载的规则组数量变化 | 平台群 | 平台负责人当天确认 |
分级标准要写成一张放之所有服务都成立的表。否则同一个 3% 的数值,在 A 服务是 warning、在 B 服务是 critical,值班同学就无法靠告警内容本身判断该不该起夜。
- 交接班要交接
pending。 处于pending的告警是"条件已满足、还在等for计时"的预警区,那才是能提前发现问题的位置。我们的值班交接模板里现在固定有一栏"当前 pending 告警清单",比交接已经 firing 的告警有用得多。
七、七种反模式:每一种我都见它在线上跑过
最后留一份清单。这七种写法我都实际遇到过,它们共同的特征是——读起来没毛病,出事的时候你才会想起来。
| # | 反模式 | 典型写法 | 后果 |
|---|---|---|---|
| 1 | 用存在性当异常程度 | count_over_time(...) > 0 | 一个持续的故障被反复宣告,刷屏 |
| 2 | 一条正则抓所有错误 | 无锚定的 (?i)error 裸词匹配 | 把 error_code=0 这类正常内容也抓进来 |
| 3 | 靠拉长窗口降噪 | 把窗口从 [1m] 拉到 [1h] 再配 > 0 | 告警延迟一小时,基数问题一点没解决 |
| 4 | 告警标签带 pod / instance | sum by (service, pod) | 每次发布产生一批新时序,历史告警对不上 |
| 5 | 一组规则统一设 limit | 20 条规则共享同一个 limit | 超出时整组告警被清空,静默失联 |
| 6 | 用告警链路监控告警链路 | 用 Loki 自己的规则监控 ruler 存活 | ruler 停机时没有任何人报信 |
| 7 | 规则不进 Git,靠人手改 | 直接改 ConfigMap 里的规则文件 | 出问题无法回滚,也说不清"上周生效的是哪一版" |
逐个补一句关键的。
第 1 种已经在第二节拆过了,这里只加一个判断动作:看这条表达式输出的是不是布尔值。输出 true/false 的表达式,就必须配 for,而且要追问自己"这个条件会不会一直为真"。
第 2 种的坑在正则粒度。(?i)error 会匹配到 error_code=0、NoError、errorRate:0 这类完全正常的内容。写告警用的正则要带字段边界——先用 | logfmt 把日志解析成字段,再按 level 字段等于 error 过滤,比直接匹配裸词靠谱得多。
第 3 种是最常见的"想当然解法"。窗口从 1 分钟拉到 1 小时确实能让刷屏频率下降,但代价是告警延迟一小时,而且标签基数、评估成本这些真问题一个都没碰。降噪的正确工具是 for 和比值,不是窗口长度——窗口的作用是让速率更平滑,不是让告警变少。
第 4 种在 3.3 和 3.5 里都讲过,这里给一个可执行的检查动作:上线前把规则文件里所有 by (...) 抓出来,逐个问"这个维度的取值会变吗、会随发布变吗"。
第 5 种的危险性是七种里最高的,因为它的失败是静默的。设 limit 的初衷是保护 ruler,但熔断触发后不是"少发几条",而是"这一组告警全部消失"。没有独立元监控的话,你看到的现象是"线上炸了,告警没来"。
第 6 种是逻辑上的自指。日志告警链路的存活必须由一条不依赖它的通道监控,这条通道只能是 Prometheus 抓 /metrics。我们内部的规范是:任何告警链路的元监控,都不允许挂在被测链路上。
第 7 种看着像流程问题,其实是能力问题。规则文件不进 Git,就没有回滚手段,也回答不了"上周三凌晨生效的是哪一版"——而排查跨周的告警异常,第一件事就是对比这两版规则。最低要求:规则文件进 Git、CI 里跑 ACTION: check、上线走 lokitool rules sync,变更记录留在提交历史里。
总结
日志告警刷屏这件事,看起来是阈值问题,本质是语义问题。
日志是事件流,它天然只能回答"发生过没有";而值班需要的是"现在是不是在坏、坏到什么程度"。 把一个只能表达"存在"的信号直接接到告警通道上,剩下的事情就只剩刷屏了。修法是把这两件事拆开:记录规则负责把事件流压成状态指标,告警规则负责判断状态是否持续。
回头看那 400 条告警,有三条经验我想留给同样在做这件事的人:
第一,日志告警的第一性问题永远是"我要判断的是存在还是状态"。 判断存在,> 0 加合适的 for 就够;判断状态,先写记录规则。在写任何一条日志告警之前问清楚这个问题,能省掉后面大量的返工。
第二,降噪的真正目标不是少发,而是"每条都值得看"。 把夜间打断次数从 11 次降到 1 次不是终点——终点是那条被留下的告警真的对应一次需要人介入的故障。所以我们复盘时会数"人工确认有用的比例",这个比例接近 100% 才算做对了。
第三,日志告警链路的脆弱性被严重低估了。 WAL 回放期间不评估、ring 变化重新分片、local 存储不加载子目录、租户目录缺失时静默运行——这四个陷阱的共同点是"看起来在工作,实际没在工作"。日志告警本身很能打,但它必须有一套来自指标链路的元监控兜着,这不是可选项。
最后补一句立场:日志告警不是指标告警的替代品,它是补位。能采到指标的地方就用指标,日志留给指标够不着的地方——黑盒组件、事件型异常、高基数维度。这个边界画清楚,规则的数量会少一半,每一条的分量会重一倍。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Alerting and recording rules | Grafana Loki documentation — Grafana Labs,ruler 组件职责、告警规则与记录规则示例、三类 use case(黑盒监控 / 事件型告警 / 高基数来源)、ruler storage 与目录布局、per-group
limit行为、alertmanager_url与enable_alertmanager_v2、ruler 分片与 rules-as-code(lokitool / Terraform / cortex-rules-action) - Alerting rules | Prometheus documentation — Prometheus 项目,告警规则语法与
for的 pending/firing 语义 - Monitoring Distributed Systems — Google SRE Book(Betsy Beyer 等),告警应针对症状而非原因、每条页面上报的告警都应可操作
- Recording rules | Prometheus documentation — Prometheus 项目,记录规则的定位与预计算取值,以及 per-group limit 的对应行为
- Alerting on SLOs — Google SRE Workbook,以燃烧率而非单点阈值判断需要人工介入的故障,是本文"10 分钟确认窗口不等于发现延迟"这一判断的依据
- 当告警风暴来袭:IT 运维如何应对"信息过载"困境 — 腾讯云开发者社区,运维团队在告警分析环节的时间占用及告警疲劳的隐性成本,用于佐证"确认率比打扰次数更重要"
- EDR 上线后告警反而没人看?安全告警疲劳的成因与四级降噪实践 — 腾讯云开发者社区,安全运营中心的日均告警量与分析师实际处置比例、团队忽略真实告警的比例,用于说明告警疲劳的普遍机制
- Recording rules | Grafana Loki documentation — Grafana Labs,ruler 自带 querier 的评估路径、每租户 WAL 的加载与回放行为(回放期间不评估任何规则)、规则组规模建议
- Loki scalability | Grafana Loki documentation — Grafana Labs,大规模部署下的角色分离与远程规则评估(本地评估结果可能不一致)
感谢 Grafana Labs 与 Prometheus 项目把 ruler 的行为细节写进文档;也感谢 Google SRE 团队关于"告警必须可操作"的经典论述——它比任何降噪技巧都更接近问题的根。