跳至内容
25.4 把观察契约变成告警

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 / 3d

fast:

- 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-ch24

fast、slow 与 ticket 是一套政策

规则长/短窗burnfor路由
fast1h / 5m14.4x2mSEV-1 page
slow6h / 30m6x5mSEV-2 page
budget3d / 6h1x15mticket

它们不是三个独立“数据库报警”:

  • 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 acceptance

owner 还要决定:

  • 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 disappears

for 不是简单 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 changes

25.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/ownercapacity、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 boundary

monitoring 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/order

receiver 中的 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
credentials

amtool 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=0notification 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

变更后的最小测试矩阵

改动必测
expressionnormal/threshold/below/above/missing
windowonset、pending、firing、recovery
labelsgrouping、dedup、cardinality
routeexpected sink、default、no real integration
inhibitionpositive + must-not-inhibit
annotationowner/action/runbook/dashboard
recordingexact arithmetic and labels
enginedry run、unit、error metrics
deploymentcanary、rollback、receipt;本章未执行

本节验收

你应当能说明:

  1. burn rate 如何由 objective target 计算;
  2. 14.4x 为什么只是 policy starting point;
  3. 两个窗口分别防什么问题;
  4. instance ratio 为什么不是 event ratio;
  5. latency rule 为什么只能走 proposed test;
  6. correctness 为什么没有 error budget;
  7. for 为什么不能修复错误 expression;
  8. recording 与 dependent alert 为什么要分 group;
  9. group_by 为什么不默认带 instance;
  10. fast/slow/ticket 应如何抑制;
  11. metamonitoring 只能抑制哪些派生告警;
  12. page 最低要绑定哪些动作字段;
  13. synthetic rule test 与 offline route test 各证明什么;
  14. 为什么它们仍不能证明真实 pager delivery。

上一节:SQL 可观测基线 · 返回本章目录 · 下一节:Pigsty 可观测体系 · 查看全书目录 · 查看索引中心

最后更新于