TT Lab
开始
学习 学习路径 课程

FDE综合实战:仓库收到了三次相同订单

凌晨三点,交接文档里的命令是错的

在 TT Lab 中继续学习

目标

为接手的运维团队构建“指标 → 告警条件 → 运行手册条目 → 可执行的检查命令”这条链,并由评分器启动模拟服务的多种故障模式,通过演练确认这条链指向了正确的条目。

为什么重要

交接文档只在写成的那天是对的。如果告警在什么条件下触发、触发后用什么确认,与代码分开写,服务一变文档就过时,而这件事要到凌晨值班人员敲命令时才会暴露。所以告警规则要做成脚本读取的数据,运行手册的检查行要写成用退出码作答的命令,再用检查器和演练反复确认这些命令真的能运行。不把沉默的仪表板(抓取不到指标)当作正常,也是这条链的一部分。

预计 75 分钟。请在默认 60 分钟会话结束前用“+时间”延长(最长 180 分钟)。会话结束后 /root/drill 中的文件会消失,想保留的代码请在结束前另外保存。

材料

步骤

  1. 以正常模式启动模拟服务,把 /metrics 响应不加处理地保存到 /root/drill/normal.prom。从原文的 # TYPE 行中,把 이름 형식(占位符依次为名称和类型)每行一个地写到 /root/drill/families.txt。
  2. 编写 /root/drill/promtext.py。python3 promtext.py FILE 对每个样本输出 {"name","labels","value","timestamp"} 对象的 JSON 数组。值为数字,无穷大和 NaN 是字符串 "+Inf"、"-Inf"、"NaN",没有时间戳则为 null。跳过注释和空行,正确读取标签值中的逗号、花括号以及 \\、\"、\n 转义。只要有一个错误的样本行,就以退出码 2 结束。
  3. 把交接备忘录中的六个告警转写到 /root/drill/alerts.json。格式是 {"alerts": [{"alert","severity","runbook","rule"}]},rule.kind 是 threshold(metric、op、value)、ratio(metric、denominator、op、value)、expires_within(metric、seconds)、scrape、absent(metrics)之一。评分器会在包含边界值的多种情形下判定你的规则。
  4. 编写 /root/drill/diagnose.py。python3 diagnose.py --url URL [--rules 경로](占位符为规则文件的路径;默认为 /root/drill/alerts.json)抓取一次 URL/metrics,用规则判定,并输出 {"target": URL, "firing": [{"alert","severity","runbook","labels"}]}。labels 是该样本的标签。退出码在没有告警时为 0,有告警时为 1。阈值要从规则文件中读取。
  5. 修改 /root/drill/diagnose.py,让它把抓取失败升级为告警。连接失败、HTTP 不是 200、格式错误、没有 orders_up 时,只发出一个 OrdersTargetDown(labels 为 {}),退出码为 2。缺少必需指标时,每个缺失的指标发出一个 OrdersMetricAbsent,其 labels 为 {"metric": 이름}(占位符为指标名称)。
  6. 把六个告警的运行手册条目写入 /root/drill/runbook.md。条目由 ## 런북이름(占位符为运行手册名称)标题以及 - 경보:、- 확인:、- 판단:、- 조치:、- 에스컬레이션: 五行组成。检查行是用反引号包起来的一行命令,用 $ORDERS_URL 指向服务,用 bash -o pipefail -c 运行时,正常为 0,属于该故障则以非 0 值立即结束。
  7. 编写 /root/drill/check_runbook.py。python3 check_runbook.py 런북.md --url URL(占位符依次为运行手册文件名和服务地址)对每个条目,在环境变量 ORDERS_URL 下用 bash -o pipefail -c 在 5 秒限制内运行检查命令,并按运行手册的顺序输出 [{"id","check","exit","status","reason"}]。status 在退出码为 0 时是 ok,否则是 broken。reason 是 ok、timeout、not-found(127)、exit N、no-check(没有检查行)之一。只要有一个 broken,就以退出码 1 结束。对供应商运行手册运行它,找出已坏的行。
  8. 编写值班演练脚本 /root/drill/oncall.sh。bash oncall.sh URL 通过 diagnose.py 得到告警,对每个告警找到 runbook.md 中的条目并运行检查命令,输出 {"target": URL, "incidents": [{"alert","severity","runbook","labels","check_exit","confirmed","escalation"}]}。confirmed 在检查命令以非 0 值结束(故障得到确证)时为 true,escalation 原样取自运行手册的升级上报行。有事件时退出码为 1,没有则为 0。

参考

原样抓取指标原文

把正常模式模拟服务的 /metrics 响应保存到 /root/drill/normal.prom,把 TYPE 行中的 이름 형식 列表(占位符依次为名称和类型)保存到 /root/drill/families.txt。

# TYPE <이름> <형식>(占位符依次为名称和类型)这一行,每个指标族只有一行。对于 histogram 族,以 TYPE 行中的名称为准,样本中会带着 _bucket、_sum、_count 出现。用 awk 选出前两列是 # 和 TYPE 的行即可。

不被逗号和引号欺骗的解析器

编写把 Prometheus 文本暴露格式转换为样本对象 JSON 数组的 /root/drill/promtext.py。有错误的样本行时,以退出码 2 结束。

标签部分不能用 split(',') 切分。在引号之内,逗号和 } 是值的一部分,反斜杠之后的字符是转义。请做一个逐字符读取的小状态机(是否在引号内、是否紧跟在反斜杠之后)。值用 float() 读取,但要把 JSON 中没有的无穷大和 NaN 转成字符串。

把交接备忘录中的六个告警变成数据

把 /opt/lab/drill/brief.md 中的告警表转写成 /root/drill/alerts.json 规则。

“低于”与“不超过”、“超过”与“不低于”是不同的 op。证书指标是过期时刻,所以用 expires_within;队列不用深度,而用最老消息的年龄。抓取失败写 scrape,必需指标列表写在 absent 的 metrics 中。

抓取一次并用规则判定

编写抓取指标、用 alerts.json 判定并以 JSON 输出触发的告警的 /root/drill/diagnose.py(无告警为 0,有告警为 1)。

把样本按名称分组后,就可以只取出每种规则所需的部分。ratio 要把分子和分母按相同标签配对。expires_within 比较的是“指标值 − 当前时刻”。如果导入上一步的 promtext.py,就不需要重写解析器。

不要把沉默的仪表板当作正常

修改 /root/drill/diagnose.py,把抓取失败以 OrdersTargetDown 和退出码 2 升级为告警,把缺失的必需指标以 OrdersMetricAbsent 升级为告警。

urlopen 对连接失败抛出 URLError,对非 200 的响应抛出 HTTPError。解析器的 ParseError 也属于抓取失败。如果把这三种情况变成空的样本列表,所有比较就都会为假,告警数量变成 0 个——这本身就应该是一个告警。absent 按“是否一个以该名称命名的样本都没有”来判定。

用退出码作答的运行手册

把六个告警的条目写入 /root/drill/runbook.md。检查命令必须实际做到:正常为 0,出现对应故障时为非 0。

诊断端点(/debug/deps、/debug/disk、/debug/tls、/debug/queue)是 JSON,所以 jq -e 很合适。-e 在结果为 false 或 null 时给出退出码 1。curl 要加上 -f(把 4xx、5xx 视为失败)和 -m(时间限制)。target-down 还要能抓住返回 200 的维护页面,所以要检查响应中是否有 orders_up 样本。

运行供应商运行手册,找出已坏的行

编写实际运行运行手册中的检查命令并加以判定的 /root/drill/check_runbook.py,并对 /opt/lab/drill/vendor-runbook.md 运行。

subprocess.run(timeout=…) 在超时后只会杀掉 bash,管道后面留下的 curl 抓着输出管道,等待可能永远不会结束。请用 Popen(start_new_session=True) 启动,超时后用 os.killpg 连同进程组一起切断。退出码 127 表示“找不到命令”。

从告警到升级上报的值班演练

编写一次性跑完 diagnose.py → runbook.md → 检查命令 → 升级上报的 /root/drill/oncall.sh。

diagnose.py 的退出码 1、2 不是错误,而是判定结果,所以不要用 set -e 让脚本中断。运行手册的解析和带时间限制的运行,可以导入第 7 步 check_runbook.py 中的函数重复使用。confirmed 表示“检查命令是否确证了故障”,所以退出码不为 0 时为 true。