一夜响了七十次告警,用户却什么都没遇到
目标
在真实数据之上统计四条基于原因的告警和一条基于症状的告警,并根据呼叫数量和无效呼叫的时长,决定哪些发给人、哪些下放。
为什么重要
增加告警很容易,减少告警很难。难的原因是没有依据——“这条告警是不是太吵了?”是一种意见,而“这条告警在 12 小时内响了 65 次,其中 230 分钟用户根本没有遭遇任何事情”是数据。有了数据,会议就会变短。而且同样的数据还能决定持续时间这个旋钮的取值。告警设计不是挑选阈值,而是在人能够承受的呼叫总量之内,挑选要检测什么。
步骤
- 在
/root/obs-alert-symptom/rules/causes.yml中写 Prometheus 规则文件。组名是causes,其中有四条告警。CauseQueueDepth在queue_depth{job="shop-api",queue="orders"}超过 100 时响起;CauseDiskPredict在用node_filesystem_avail_bytes{job="node"}最近 1 小时的趋势预测 6 小时后低于 0 时响起;CauseLatencyTail在以最近 5 分钟为准的 p99 延迟超过 0.5 秒时响起;CauseTrafficLow在以最近 5 分钟为准的每秒请求数低于 55 时响起。必须能通过promtool check rules的检查。这一步不要加for:。 - 创建
/root/obs-alert-symptom/count.py。以python3 count.py '<경보 식>' <for 분>(占位符依次为告警表达式、for 的分钟数)调用时,按 1 分钟间隔扫描最近 12 小时,输出一行episodes=<호출 수> minutes=<울린 분>(占位符依次为呼叫次数、响起的分钟数)。当条件为真的分钟连续持续了 L 分钟时,如果 L 大于等于for + 1,就算作一次呼叫,响起的时间是L - for分钟。如果不给for,就按 0 处理。 - 创建
/root/obs-alert-symptom/counts.tsv。没有表头,共四行,每行是以制表符分隔的三个字段<경보이름> <호출 수> <울린 분>(占位符依次为告警名称、呼叫次数、响起的分钟数)。for设为 0,写第 1 步规则文件中的四条告警,名称保持原样。值用第 2 步的工具求得。 - 从第 3 步中挑出响起次数最多的一条告警,把
for依次改为 0、5、15 分钟,再统计一遍。在/root/obs-alert-symptom/for-effect.tsv中写三行,没有表头,每行是<for 분> <호출 수> <울린 분>(占位符依次为 for 的分钟数、呼叫次数、响起的分钟数)。 - 在
/root/obs-alert-symptom/rules/symptom.yml中写symptom组和一条告警SymptomErrorRatio。条件是以最近 5 分钟为准的 5xx 响应比例超过 1%,并附上for: 5m和runbook_url注解。通过promtool check rules之后,把同一个表达式按for为 0 统计,在/root/obs-alert-symptom/symptom.txt中写一行episodes=<수> minutes=<분>(占位符依次为次数、分钟数)。 - 创建
/root/obs-alert-symptom/overlap.py。以python3 overlap.py '<원인 식>' '<증상 식>'(占位符依次为原因表达式、症状表达式)调用时,按 1 分钟间隔扫描最近 12 小时,输出一行cause_minutes=<원인이 참인 분> outside_minutes=<그중 증상이 참이 아닌 분>(占位符依次为原因为真的分钟数、其中症状不为真的分钟数)。用这个工具分别测量四条基于原因的告警,在/root/obs-alert-symptom/falsepages.tsv中写四行以制表符分隔的三个字段<경보이름> <원인 분> <증상 밖 분>(占位符依次为告警名称、原因分钟数、症状之外的分钟数)。 - 在
/root/obs-alert-symptom/triage.tsv中写五行。每行是以制表符分隔的三个字段<경보이름> <page|ticket|dashboard> <근거>(占位符依次为告警名称、page、ticket 或 dashboard 之一、依据),四条基于原因的告警和SymptomErrorRatio都要写。规则有两条——症状告警必须是page;并且在第 6 步中症状之外的分钟数超过 60 分钟的原因告警,不能设为page。依据中必须至少包含一个前面几步得到的数字,并且至少 20 个字符。 - 在
/root/obs-alert-symptom/rules/page.yml中只放第 7 步归为page的告警。组名是page,每条告警都必须有至少 5 分钟的for:、labels中的severity: page、annotations中的runbook_url。通过promtool check rules之后,把这些告警按各自的for值统计,把呼叫次数的总和,用一行pages_after=<수>(占位符为数量)写入/root/obs-alert-symptom/after.txt。
参考
- 工作目录是
/root/obs-alert-symptom。规则文件请放在/root/obs-alert-symptom/rules/下——如果放在/etc/prometheus/rules/,这个 Pod 的 Prometheus 就会真的开始评估它们。 - 规则检查:
promtool check rules <파일>(占位符为文件)。查询确认:promq "<PromQL>"。 - 范围数据:向
/api/v1/query_range传入query、start、end、step。条件为假的时刻,根本没有样本。 - 常见错误:把条件为真的样本数直接当作呼叫次数。一段连续区间才是一次呼叫。
- 常见错误:漏掉“加了
for之后检测会相应变慢”这一事实,只夸耀呼叫次数。 - Monitoring Distributed Systems (SRE Book 第 6 章)、Alerting on SLOs (SRE Workbook)、Being On-Call (SRE Book 第 11 章)、Alerting rules、HTTP API — range queries
把四条基于原因的告警写成规则文件
在 /root/obs-alert-symptom/rules/causes.yml 中写 Prometheus 规则文件。组名是 causes,其中有四条告警。CauseQueueDepth 在 queue_depth{job="shop-api",queue="orders"} 超过 100 时响起;CauseDiskPredict 在用 node_filesystem_avail_bytes{job="node"} 最近 1 小时的趋势预测 6 小时后低于 0 时响起;CauseLatencyTail 在以最近 5 分钟为准的 p99 延迟超过 0.5 秒时响起;CauseTrafficLow 在以最近 5 分钟为准的每秒请求数低于 55 时响起。必须能通过 promtool check rules 的检查。这一步不要加 for:。
规则文件的骨架是 groups: → - name: → rules: → - alert: 和 expr:。预测是把范围向量和以秒为单位的未来时长交给 predict_linear。p99 是把按 le 汇总的 rate 交给 histogram_quantile。检查用 promtool check rules /root/obs-alert-symptom/rules/causes.yml。
制作统计会响起多少次的工具
创建 /root/obs-alert-symptom/count.py。以 python3 count.py '<경보 식>' <for 분>(占位符依次为告警表达式、for 的分钟数)调用时,按 1 分钟间隔扫描最近 12 小时,输出一行 episodes=<호출 수> minutes=<울린 분>(占位符依次为呼叫次数、响起的分钟数)。当条件为真的分钟连续持续了 L 分钟时,如果 L 大于等于 for + 1,就算作一次呼叫,响起的时间是 L - for 分钟。如果不给 for,就按 0 处理。
向 /api/v1/query_range 传入 start、end、step,就会得到范围数据。条件为假的分钟本身没有样本,所以只要构造出响应中包含的时间戳集合,在 1 分钟的网格上统计连续区间即可。只使用 Python 标准库(urllib.request、json、time)。
四条基于原因的告警在 12 小时内各响了多少次
创建 /root/obs-alert-symptom/counts.tsv。没有表头,共四行,每行是以制表符分隔的三个字段 <경보이름> <호출 수> <울린 분>(占位符依次为告警名称、呼叫次数、响起的分钟数)。for 设为 0,写第 1 步规则文件中的四条告警,名称保持原样。值用第 2 步的工具求得。
如果从规则文件中取出表达式交给工具,就能减少手工誊写的错误。这一步的关键是四个数字相差多大——其中一个会响 60 多次。
仅凭一个持续时间,呼叫能减少多少
从第 3 步中挑出响起次数最多的一条告警,把 for 依次改为 0、5、15 分钟,再统计一遍。在 /root/obs-alert-symptom/for-effect.tsv 中写三行,没有表头,每行是 <for 분> <호출 수> <울린 분>(占位符依次为 for 的分钟数、呼叫次数、响起的分钟数)。
条件表达式保持不变,只改第二个参数。除了呼叫次数减少,也要看看失去了什么——响起的分钟数减少,意味着检测相应变慢。
写一条基于症状的告警
在 /root/obs-alert-symptom/rules/symptom.yml 中写 symptom 组和一条告警 SymptomErrorRatio。条件是以最近 5 分钟为准的 5xx 响应比例超过 1%,并附上 for: 5m 和 runbook_url 注解。通过 promtool check rules 之后,把同一个表达式按 for 为 0 统计,在 /root/obs-alert-symptom/symptom.txt 中写一行 episodes=<수> minutes=<분>(占位符依次为次数、分钟数)。
比率是用 5xx 的 rate 之和除以全部 rate 之和。要点是,这条告警响的次数远远少于那些基于原因的告警。runbook_url 放在 annotations 之下。
在用户没有遭遇任何事情的时间里,响了多少分钟
创建 /root/obs-alert-symptom/overlap.py。以 python3 overlap.py '<원인 식>' '<증상 식>'(占位符依次为原因表达式、症状表达式)调用时,按 1 分钟间隔扫描最近 12 小时,输出一行 cause_minutes=<원인이 참인 분> outside_minutes=<그중 증상이 참이 아닌 분>(占位符依次为原因为真的分钟数、其中症状不为真的分钟数)。用这个工具分别测量四条基于原因的告警,在 /root/obs-alert-symptom/falsepages.tsv 中写四行以制表符分隔的三个字段 <경보이름> <원인 분> <증상 밖 분>(占位符依次为告警名称、原因分钟数、症状之外的分钟数)。
分别构造两个表达式的“为真的分钟集合”,再统计差集即可。把第 2 步工具的一半拆成函数,就可以直接使用。症状之外的分钟数越大,说明“用户完好无损,却只是把人叫醒”的时间越长。
用数字作依据,区分呼叫、工单和仪表板
在 /root/obs-alert-symptom/triage.tsv 中写五行。每行是以制表符分隔的三个字段 <경보이름> <page|ticket|dashboard> <근거>(占位符依次为告警名称、page、ticket 或 dashboard 之一、依据),四条基于原因的告警和 SymptomErrorRatio 都要写。规则有两条——症状告警必须是 page;并且在第 6 步中症状之外的分钟数超过 60 分钟的原因告警,不能设为 page。依据中必须至少包含一个前面几步得到的数字,并且至少 20 个字符。
症状之外的分钟数接近 0 的原因告警,意味着它几乎只在与症状相同的时间响起,所以可以保留为呼叫,节省调查时间。反过来,接近一半时间为真的告警如果设为呼叫,就会逼着人去建过滤器。
只保留用于呼叫的规则文件
在 /root/obs-alert-symptom/rules/page.yml 中只放第 7 步归为 page 的告警。组名是 page,每条告警都必须有至少 5 分钟的 for:、labels 中的 severity: page、annotations 中的 runbook_url。通过 promtool check rules 之后,把这些告警按各自的 for 值统计,把呼叫次数的总和,用一行 pages_after=<수>(占位符为数量)写入 /root/obs-alert-symptom/after.txt。
如果从规则文件中读取 alert 名称、expr、for 并交给第 2 步的工具,就无需手工相加。for 的值像 5m 这样是字符串,所以必须转换成以分钟为单位的数字。请与第 3 步的呼叫次数总和比较一下。