25.4 把观察契约变成告警
规则语法正确,只证明 parser 接受了它。
一条生产告警还必须同时通过:
semantic
expression 真正测量合同定义的事件吗?
temporal
window / scrape / eval delay / for / recovery 正确吗?
governance
page、ticket 还是 diagnostic?谁接受了这个政策?
actionability
owner 能执行首个安全动作吗?
routing
grouping / inhibition / receiver 会制造风暴或静默吗?
exercise
正常、异常、缺失、错误和恢复都被测试了吗?本章生成的文件:
它们是教学候选,不是在线配置。
25.4.1 SLO 燃烧率与用户影响
从 error ratio 到 burn rate
若 SLO target 为:
$$ S = 99.9% = 0.999 $$
允许的 bad ratio:
$$ B = 1 - S = 0.001 $$
某窗口实际 bad ratio 为 $E$,燃烧率:
$$ \text{burn rate} = \frac{E}{B} $$
例如:
actual bad ratio 1.44%
budget ratio 0.1%
burn rate 14.4x若持续 28 天,会花掉 14.4 倍预算;若只持续 1 小时,约花掉:
$$ \frac{14.4 \times 1h}{28d} \approx 2.14% $$
这解释了为什么 14.4x/1h 常被用作“快速消耗约 2% 月度预算”的起点。它是 政策起点,不是自然常数。
为什么使用两个窗口
只用长窗口:
stable
but slow to detect a sudden severe outage只用短窗口:
fast
but noisy and easy to flap组合:
bad_ratio_long > threshold
and on (service, operation_class, environment)
bad_ratio_short > threshold长窗确认预算风险,短窗确认问题仍在发生。
本章可用性 fast burn
记录规则预先计算五个窗口:
5m / 30m / 1h / 6h / 3dfast:
- alert: PG36ShopAvailabilityFastBurn
expr: |
pg36_shop:sli_availability_bad_ratio:rate1h > (14.4 * 0.001)
and on (service, operation_class, environment)
pg36_shop:sli_availability_bad_ratio:rate5m > (14.4 * 0.001)
for: 2m标签固定:
class: symptom
route: page
severity: SEV-1
objective_id: SLO-AVAILABILITY
owner_function: service
runbook_id: RB-USER-SYMPTOM
governance_status: accepted-ch24fast、slow 与 ticket 是一套政策
| 规则 | 长/短窗 | burn | for | 路由 |
|---|---|---|---|---|
| fast | 1h / 5m | 14.4x | 2m | SEV-1 page |
| slow | 6h / 30m | 6x | 5m | SEV-2 page |
| budget | 3d / 6h | 1x | 15m | ticket |
它们不是三个独立“数据库报警”:
- fast 处理快速用户损害;
- slow 处理持续预算风险;
- ticket 处理应进入 backlog 的可靠性债务。
当 fast firing 时,同一 service/environment/objective 的 slow 和 ticket 可以 被抑制,避免一个事件三次通知;原始 alert state 和证据仍保留。
burn threshold 必须跟 target 绑定
延迟目标是 99%:
budget ratio 0.01
14.4x threshold 0.144可用性目标是 99.9%:
budget ratio 0.001
14.4x threshold 0.0144把同一个绝对 error ratio 复制过去会错一个数量级。
规则生成器若使用 policy object,应从:
objective target
window
burn policy计算,而不是在 YAML 中散落魔法数。
event ratio,不是 instance ratio
错误写法:
count(pg_up == 0) / count(pg_up)它算的是实例比例,不是用户 event ratio。三节点中一个 down:
- 服务可能完全正常;
- 某些 read path 可能降级;
- failover 可能正在进行; -用户失败比例不一定是 1/3。
正确 availability SLI 来自 application outcome:
sum by (service, operation_class, environment) (
rate(pg36_shop_request_outcomes_total{
eligible="true",
outcome!="good"
}[5m])
)
/
sum by (service, operation_class, environment) (
rate(pg36_shop_request_outcomes_total{
eligible="true"
}[5m])
)instrumentation 要预初始化 bounded outcome series,否则“没有 bad outcome series”与“零 bad events”会混淆。
latency 规则暴露了治理缺口
第 24 章定义 SLO-LATENCY,但 accepted alert list 没有 latency candidate。
本章没有悄悄将它变成生产 page,而是:
alert: PG36ShopLatencyFastBurn
route: test
severity: candidate
governance_status: proposed-not-accepted规则和测试都存在,生产门禁仍拒绝。这体现:
technical readiness
!= policy acceptanceowner 还要决定:
- latency threshold 是否 250 ms;
- eligible event 与 availability 是否相同;
- success 但慢、failure 且慢如何计数; -低流量如何处理;
- page severity; -首个安全动作; -与 availability page 如何去重。
freshness 必须使用 commit-correlated event
规则:
pg36_shop:sli_freshness_bad_ratio:rate1h > (14.4 * 0.01)
and on (service, read_path, environment)
pg36_shop:sli_freshness_bad_ratio:rate5m > (14.4 * 0.01)分母是携带已知 commit token 的 probe,bad 是五秒内不可见。
不要替换成:
pg_lag > 5因为 pg_lag 的语义、空闲行为和用户 path 都不同。
正确性不燃烧
- alert: PG36ShopCorrectnessMismatch
expr: pg36_shop_reconciliation_mismatches > 0
for: 0m它没有:
burn rate
error budget
planned exclusion
slow ticket before page一个未解释 mismatch 就进入完整性响应。缺失或 stale reconciliation 是另一个 control failure,不是零 mismatch。
恢复就绪也是 control
候选:
pg36_shop_restore_evidence_age_seconds
> 90 * 24 * 60 * 60它应:
- block 高风险 release 或建立 ticket; -引导查看不可变 restore manifest; -安排隔离 restore drill; -不把 backup success 当替代。
当前第 24 章已定义 control objective,但告警候选仍待接受,所以走 test sink。
容量预测先要求 reviewed
pg36_shop_capacity_horizon_days < 14
and on (service, environment)
pg36_shop_capacity_forecast_reviewed == 1为什么第二个条件重要?
short history
step change
seasonality
retention change
one-time load
broken collector都可能产生虚假线性 forecast。未经评审的模型不应 page。即使 reviewed,也默认 ticket,因为当前没有用户损害。
用户影响必须写进规则
同一个 threshold 若不能说明 user impact,就不能决定 severity:
CPU > 90%
user impact unknown
availability fast burn
eligible order attempts failing/unreconciled
correctness mismatch
acknowledged state may be invalid or cross-tenant
monitoring path broken
service health unknown这也是为什么 PG36HostCpuHigh 保持 diagnostic。
25.4.2 阈值、持续时间、去抖、抑制与分组
threshold 是政策,不是颜色
阈值来源可以是:
- SLO/error budget;
- control objective; -容量 horizon; -安全/完整性不变量; -组件规格; -统计/历史基线; -供应商硬限制。
每种来源对应不同动作。不要在 Grafana 中选一个“看起来红”的值,然后反向写 解释。
for 的状态机
典型:
inactive
expression false/no matching series
pending
expression true, for not elapsed
firing
expression remains true for duration
inactive again
expression false or series disappearsfor 不是简单 sleep:
- evaluation interval 决定检查粒度;
- rule error 与 no-data 的行为不同;
- restart/state persistence 要配置;
- data delay 可能让最新点不可见;
- label set 改变会创建新的 alert identity; -短暂 false 可重置 pending。
VMAlert 文档说明:no data 会重置 pending;evaluation error 则不会按普通 false 处理,而会保留此前状态。必须监控 rule error,不能只看业务 alert。
for 不能修复错误语义
CPU > 90% for 10m仍然不知道用户是否受损。
pg_archiver_failed_count > 0 for 5m仍然会把历史失败永久当当前失败。
先修 expression,再用 for 处理持续性。
scrape、evaluation 与 delay
假设:
scrape interval 30s
storage visibility up to 30s
evaluation interval 1m
for 2m“2m 后 page”不代表现实事件开始后精确两分钟:
event -> next scrape -> ingest -> visible eval -> pending -> later eval -> firing可能更久。规则验收应测 end-to-end detection time,而不是只读 YAML。
VMAlert 支持 group eval_delay/query latency offset 等。取值要根据真实 ingestion
延迟,并在 rule test 与 dashboard query 中使用一致时间对齐。
recording 与 alert group 分离
VMAlert 一个 group 内顺序执行 rule,但 recording result 异步写回 remote storage。若同组后续 alert 立即读取前一个 recording result,可能看不到本轮 结果。
不要:
groups:
- name: bad-chain
rules:
- record: A
expr: ...
- alert: B
expr: A > ...本章分成:
pg36-shop-sli-recording
pg36-postgresql-diagnostic-recording
pg36-observation-meta-recording
pg36-shop-slo-alerts
pg36-shop-control-alerts
pg36-postgresql-proposed-alerts
pg36-observation-path-alerts测试用 group_eval_order 先运行 recording groups,再运行 alert groups。
rule result limit
错误 label 可能让一条 rule 产生数十万结果。VMAlert group 支持 result limit; 生产应按预计 cardinality 设置并监控 exceeded error。
限制不是修复:
- 先保证 labels 有界; -再估算 series; -限制作为故障保护; -超限时 health unknown,不能默默丢结果; -监控 rule error。
Alertmanager group_by
本章:
group_by:
- service
- environment
- alertname没有把 ins 放进 user symptom grouping。原因:
one user symptom
may correlate with many instances
group by instance
can create one page per component组件信息在诊断包和 dashboard,下游 page 围绕服务事件。
group timing
group_wait: 30s
group_interval: 5m
repeat_interval: 4h含义大致是:
- group_wait:新组第一次通知前等待相关 alert 聚合;
- group_interval:组变化后再次发送的最小间隔;
- repeat_interval:持续 alert 重复提醒间隔。
取值要与 severity 匹配。SEV-1 等 30 秒可能可以,也可能太慢;真实策略需 notification SLO 和业务 owner 接受。
dedup identity 来自 labels
改变 label 会创建新 alert:
release changes
instance changes
error_message changes
queryid changes因此不要把高变化字段放 alert label。它们会:
-重置 for;
-打破 dedup;
-增加通知;
-让 silence 失效。
inhibition 只去重,不删除事实
本章第一条:
source
availability fast burn, SEV-1
target
availability slow burn or budget ticket
equal
service / environment / objective_id它不会抑制:
-不同 service; -不同 environment; -freshness; -correctness; -monitoring path。
第二条:
monitoring path broken
inhibits only derived-missing alerts
with observation_dependency=true
for same service/environment它不抑制独立 blackbox 看到的 user symptom。
正确性永不被普通抑制
本章对抗测试会拒绝任何 target matcher 包含:
PG36ShopCorrectnessMismatch
class=integrity原因:
monitoring broken
does not make known corruption less important
availability outage
does not excuse cross-tenant mismatch可以在 incident tooling 中关联同一事件,但不能静默正确性 page。
silence 与 inhibition 不同
inhibition 是配置中的关系规则;silence 是带 matcher 和期限的操作。
silence 必须:
- owner;
- reason;
- start/end;
- exact matcher;
- replacement observation;
- incident/change reference;
- review/expiry。
不要为了维护直接 silence 整个 cluster 的所有 alert。planned maintenance 也 不应从 SLO event 中事后删除。
recovery 与 flapping
恢复条件可以:
- expression 直接 false;
-使用 hysteresis;
-使用
keep_firing_for(若平台与政策采用); -由 Alertmanager repeat/group timing 控制通知; -要求独立 verification。
不要只让 threshold 出现巨大滞后,导致真实恢复长时间仍 page。
测试至少覆盖:
brief spike does not fire
sustained condition fires
condition recovers
one window recovers but other does not
series disappears
rule query errors
label identity changes25.4.3 每条告警绑定所有者、证据和首个安全动作
page 是打断权
page 会打断一个人的当前工作或睡眠。因此最低合同:
current user/integrity/durability/observation-path impact
accountable owner function
tested route
runbook
first safe action
verification
dashboard/source
silence policy
expiry/review没有这些字段,报警不是“先上线再补文档”的半成品,而是潜在事故放大器。
route class
| route | 何时使用 | 例子 |
|---|---|---|
| page | 需要立即有人介入 | fast burn、correctness、monitoring path |
| ticket | 当前可排期但有 deadline/owner | capacity、slow reliability debt |
| diagnostic | 解释 symptom,不独立打断 | replica distance、CPU、pool queue |
| test | 尚未治理接受或只做演练 | latency candidate、archive candidate |
severity 不应与 component 数值机械绑定。
owner_function,不是个人名字
owner_function: service真实 Alertmanager route 再映射到当前值班表。好处:
-人员轮换不改 rule; -服务责任与平台执行分开; -可以审查 role 是否可达; -可以测试 fallback escalation。
本章空 receiver 只是验证标签分支,未证明真实 on-call 可达。
首个安全动作要降低不确定性或损害
fast availability:
freeze latest risky release
reconcile unknown outcomes before retrying writes不是:
restart PostgreSQL
kill top query
fail over
increase pool这些数据库动作可能扩大未知写结果。
freshness:
route affected read-after-write journey to primary
preserve commit tokens正确性:
freeze affected writes
preserve reconciliation boundarymonitoring path:
establish independent blackbox view
before changing monitored database首个动作不是完整修复,而是事故中最不容易后悔的下一步。
evidence link 不能带 credential
source/dashboard URL:
dashboard://pg36-shop-slo
runbook id
rule source id
time window不要:
https://user:password@monitor/...
signed URL with long-lived token
raw log query containing tenant/orderreceiver 中的 secret 也不能进入 Git。
annotation 要短而确定
通知首屏包含:
what
user impact
scope
starts at
first safe action
runbook
dashboard不要塞:
-全部诊断 SQL; -数百行日志; -未经验证的 root cause; -变化的 error text; -密码/token; -“请检查”这种无动作文本。
runbook 需要停止线
告警 runbook 不是:
1. 登录数据库
2. 看看
3. 重启它至少包括:
- entry condition;
- identity/target;
- authority;
- safe read-only queries;
- hypotheses;
- first action;
- stop condition;
- escalation;
- mutation approval;
- verification;
- evidence。
一个 page 若 runbook 第一条就是 destructive action,应退回评审。
dashboard 不负责结论
dashboard URL 负责定位:
- SLI windows;
- event count;
- release marker;
- entry/DB/host correlation;
- metamonitoring。
runbook 负责:
-如何解释; -如何复核; -什么能做; -什么不能做。
Grafana panel 不能表达完整权限和停止线。
alert review expiry
第 24 章 candidate 带 expires_for_review。规则应定期检查:
- SLO target 是否变;
- metric/version 是否变;
- query cost;
- false positive/negative;
- page actionability;
- route ownership;
- runbook 是否可执行;
- last drill;
- label cardinality;
- notification receipt。
“从未触发”可能是服务稳定,也可能是规则永远不匹配。VMAlert 有 never-firing 检测思路,但仍要用合成测试证明。
actionless instance-down 为什么被拒绝
候选:
PostgresInstanceDownWithoutAction
route page缺少:
- user impact;
- owner;
- runbook;
- first safe action。
一个 replica 可能计划下线且服务正常。替代:
topology dashboard retains instance state
endpoint/user symptom drives page
imminent durability/HA loss may have separately governed alert这不是“不监控实例”,而是不把所有 component state 都升级为打断。
25.4.4 用演练验证告警,而不是等生产事故
规则需要单元测试
测试文件指定:
rule_files:
- recording-rules.yml
- alert-rules.yml
evaluation_interval: 1m
group_eval_order:
- recording groups...
- alert groups...然后注入:
input_series:
- series: '...rate1h{service="pg36_shop",...}'
values: "0.02x3 0x5"检查:
1m pending, no firing alert
3m firing with exact labels/annotations
7m recovered, no alert为什么不直接在在线 VMAlert 测
向在线 datasource/Alertmanager 发合成数据会:
-污染 production-like time series; -触发真实 route; -创建 silence/incident confusion; -和现有规则相互作用; -难以证明完全清理。
本章使用 vmalert-tool:
start isolated VictoriaMetrics
ingest synthetic series
evaluate rules
compare expected samples/alerts
exit and remove temp workspace这是 L1 ephemeral,不是 live deployment。
测记录规则的算术
输入:
good counter +100/min
bad counter +2/min期望:
$$ \frac{2}{100+2} = 0.019607843… $$
工具实际查询 recording result,防止:
- selector 写反; -分母只算 good; -label join 丢失; -window 名与内容不一致; -expression 语法只“看起来对”。
测 alert labels 和 annotations
测试不仅看 alertname,还看:
service
operation_class/read_path
environment
class
route
severity
objective_id
owner_function
runbook_id
governance_status
observation_dependency
first_safe_action
verification
dashboard这样 candidate 被误改成 page,或 accepted rule 被误改成 proposed,会使测试或 validator 失败。
测 missing
输入:
expected_service_traffic = 1
no pg36_sli_sample_fresh series期望:
PG36ShopSLIMissing
route=test
class=derived-missing
observation_dependency=true不要用零 bad ratio 代替。
还应分别测试:
- expected traffic 为零;
- SLI fresh;
- exporter down;
- rule query error;
- independent probe fails; -label rename。
测 route,不发送通知
Alertmanager sandbox receiver 只有名字:
receivers:
- name: pg36-shop-page-sink
- name: pg36-shop-ticket-sink
- name: pg36-platform-page-sink
- name: pg36-platform-ticket-sink
- name: pg36-proposed-rule-sink
- name: pg36-null-sink
- name: pg36-default-sink没有:
webhook_configs
email_configs
slack_configs
pagerduty_configs
credentialsamtool config routes test 根据标签离线解析 receiver。八个用例覆盖:
availability page
correctness page
service ticket
platform ticket
metamonitoring page
diagnostic null
proposed test
unknown default测 inhibition 的正反例
五个用例:
fast inhibits same-objective slow true
meta inhibits derived missing true
meta inhibits independent user symptom false
meta inhibits correctness false
fast crosses service boundary false只测“应该抑制”不够;错误的 broad matcher 往往会吞掉最重要的告警。
测规则引擎本身
在线当前快照:
groups 17
alert rules 50
recording rules 698
group errors 0
rule errors 0
alert states 50 inactive
current alerts 0这是已有 Pigsty 规则,不包括本章 31 条候选规则;本章从未部署它们。
metamonitoring 还应观察:
- evaluation duration vs interval;
- missed iteration;
- datasource error;
- remote write error;
- notifier error;
- state restore;
- no-series/never-firing;
- data delay;
- result limit。
“当前无告警”不是通知测试
current alerts=0、notification failures=0 不能证明:
- route matcher 正确;
- receiver secret 有效; -外部服务接收; -值班人员可达; -升级路径工作。
真实 notification canary 要穿过:
probe
-> ingestion
-> rule
-> VMAlert notifier
-> Alertmanager grouping/route
-> external receiver
-> receipt acknowledgment本章没有权限触达真实 receiver,覆盖矩阵保持 real_receipt_canary=false。
变更后的最小测试矩阵
| 改动 | 必测 |
|---|---|
| expression | normal/threshold/below/above/missing |
| window | onset、pending、firing、recovery |
| labels | grouping、dedup、cardinality |
| route | expected sink、default、no real integration |
| inhibition | positive + must-not-inhibit |
| annotation | owner/action/runbook/dashboard |
| recording | exact arithmetic and labels |
| engine | dry run、unit、error metrics |
| deployment | canary、rollback、receipt;本章未执行 |
本节验收
你应当能说明:
- burn rate 如何由 objective target 计算;
- 14.4x 为什么只是 policy starting point;
- 两个窗口分别防什么问题;
- instance ratio 为什么不是 event ratio;
- latency rule 为什么只能走 proposed test;
- correctness 为什么没有 error budget;
for为什么不能修复错误 expression;- recording 与 dependent alert 为什么要分 group;
- group_by 为什么不默认带 instance;
- fast/slow/ticket 应如何抑制;
- metamonitoring 只能抑制哪些派生告警;
- page 最低要绑定哪些动作字段;
- synthetic rule test 与 offline route test 各证明什么;
- 为什么它们仍不能证明真实 pager delivery。
上一节:SQL 可观测基线 · 返回本章目录 · 下一节:Pigsty 可观测体系 · 查看全书目录 · 查看索引中心