值班工程与告警疲劳治理

值班不是「排个班表」,而是一套需要工程化设计的系统。本文讲透告警疲劳的成因、值班轮换与主备/影子制度、告警分级与路由升级、去重聚合抑制与基于 SLO 的降噪、告警与 Runbook 的绑定、交接与复盘,以及值班健康的度量指标与常见坑,帮助团队把值班从沉重负担变回可靠底座。

「谁值班」看起来是个排班问题,实际上是可靠性的地基。一个设计糟糕的值班体系会迅速腐蚀团队:告警天天响、大部分是误报、真正的问题被淹没、值过班的人第二天写不出代码,最后所有人都在想办法「不上班」。

值班工程(On-call Engineering)要做的,是把值班当成一个需要设计的系统:轮换怎么排、告警怎么分级、噪音怎么降、告警响了要做什么、值完班怎么交接和复盘、健康度怎么度量。本文逐条讲清这套系统。

1. 告警疲劳的成因

1.1 什么是告警疲劳

告警疲劳(Alert Fatigue):
  值班人收到的告警数量超过其有效处理能力,
  导致对告警「脱敏」——不再认真看,直接忽略或批量静音。

后果:
  - 真故障被淹没在噪音里,MTTD(发现时长)反而变长
  - 值班人长期处于应激状态,离职意愿上升
  - 团队对告警系统失去信任,绕过告警直接看仪表盘

1.2 四个根因

根因一:阈值型告警泛滥
  CPU > 80% 就告警 → 但没有一个用户因此受影响 → 纯噪音

根因二:无分级
  磁盘 90% 和 核心服务宕机走同一条通道 → 重要告警被稀释

根因三:告警不可操作
  收到「服务异常」,但不知道看什么、做什么 → 只能 escalate 或忽略

根因四:缺少抑制与关联
  数据库抖一下,20 个下游服务同时告警 → 20 条噪音掩盖 1 个根因

1.3 健康的量级

一个可参考的健康标准(按值班班次计):
  - 每班次「打扰型」告警(需要立即响应):< 2 条
  - 每班次全部告警(含自动恢复):< 10 条
  - 误报率(响了但无需行动):< 20%
  - 夜间(00:00~08:00)告警占比:< 10%

超出这些量级,说明告警治理没做到位,而不是「系统太不稳定」。

2. 值班轮换设计

2.1 轮换的基本参数

轮换周期:
  一周一轮(常见):交接成本低,但夜间负担集中在一人
  一天一轮:负担分散,但交接频繁、上下文丢失
  半天一轮(follow-the-sun):跨时区团队,白天覆盖,夜间无人

推荐:跨时区团队用 follow-the-sun(跟随太阳)实现 24 小时覆盖;
      单时区团队用一周主班 + 一周后备,避免单人连续值夜。

2.2 主备与影子制度

三层结构:
  主责(Primary):第一响应人,负责确认与处置
  后备(Secondary):主责未在 SLA 内响应时接管(通常 5~15 分钟)
  影子(Shadow):新人跟班学习,不直接响应,但全程旁听
# 值班排班(示意)
schedule:
  rotation: weekly
  handoff: "每周一 10:00"
  layers:
    - name: primary
      response_sla: "5m"
    - name: secondary
      response_sla: "15m"
      escalate_to: primary
    - name: shadow
      response_sla: "none"
      purpose: "新人培训"
  compensation:
    - "值主班一周,次周调休 1 天"
    - "夜间被唤醒超过 2 次,次日免除会议与发布"

2.3 可持续性设计

防止值班「劝退」的几条硬规则:
  1. 主班不连续超过 7 天
  2. 夜间被唤醒 ≥ 2 次,次日不安排重要工作
  3. 值完主班后至少 1 天恢复期(不进主班)
  4. 值班有明确的补偿机制(调休或津贴)
  5. 新人必须先当影子至少一个轮换周期
  6. 值班人数 ≥ 4,才能形成可持续轮换(否则等于轮值过密)

值班设计的具体组织形态(SRE 与 Dev 的责任边界、嵌入式值班)可参考 SRE 实践 中的组织模型部分。

3. 告警分级与路由

3.1 分级标准

分级应基于「用户影响」而非「技术指标」:

  P1(Critical):核心业务不可用或大面积受影响 → 立即唤醒,全员响应
  P2(High):部分功能降级或影响部分用户 → 立即响应,值班处理
  P3(Medium):潜在风险或非核心功能异常 → 工作时间内处理
  P4(Low):信息性通知,无需行动 → 只进渠道,不打扰人

3.2 分级路由

# Alertmanager 路由(示意)
route:
  receiver: "default-slack"
  group_by: ["alertname", "service", "cluster"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers: ['severity="critical"']
      receiver: "pagerduty-p1"
      group_wait: 10s
      repeat_interval: 1h
    - matchers: ['severity="high"']
      receiver: "pagerduty-p2"
    - matchers: ['severity="medium"']
      receiver: "slack-ops"
    - matchers: ['severity="low"']
      receiver: "slack-info"
      repeat_interval: 24h
路由的关键是「分流」:
  P1/P2 走呼叫(PagerDuty/电话),P3 走 IM,P4 只归档。
  如果所有告警都走呼叫通道,呼叫通道的价值就被稀释了。

3.3 升级路径

升级(Escalation)机制:
  第一级:主责,5 分钟未确认 → 升级
  第二级:后备,10 分钟未确认 → 升级
  第三级:团队负责人 / 领域专家 → 30 分钟未确认 → 升级
  第四级:事故指挥(Incident Commander)接管,启动事故流程

要点:升级不是「惩罚」,而是「防止告警无人响应」的兜底。
      未确认(未 ack)才升级,而不是「未解决」就升级。

事故升级后的处置流程(指挥、沟通、复盘)可参考 事故响应 。

4. 降噪:去重、聚合、抑制、基于 SLO

4.1 去重与聚合

去重(Dedup):相同指纹的告警只保留一条(如同一实例同一指标重复触发)
聚合(Grouping):相关告警合成一条通知(如某服务的所有实例同时告警)

Alertmanager 的 group_by 决定聚合维度:
  group_by: [alertname, service] → 同一服务同类告警合成一条
  若按 instance 分组 → 每个实例一条,噪音爆炸

4.2 抑制

抑制(Inhibition):当高优先级告警存在时,抑制相关的低优先级告警

典型规则:
  - 节点宕机 → 抑制该节点上所有实例的告警(否则几百条)
  - 数据库主库故障 → 抑制下游服务的「依赖超时」告警
  - 网络分区 → 抑制受影响区域的所有告警
# Alertmanager 抑制规则
inhibit_rules:
  - source_matchers: ['severity="critical"', 'alertname="NodeDown"']
    target_matchers: ['severity=~"warning|info"']
    equal: ["node"]
  - source_matchers: ['alertname="DatabasePrimaryDown"']
    target_matchers: ['alertname=~"ServiceDependencyTimeout|DBConnectionFailed"']
    equal: ["cluster"]

4.3 基于 SLO 的告警

阈值型告警(CPU > 80%)的最大问题是「与用户影响脱钩」。基于 SLO 的告警直接用「错误预算燃烧速率」触发,天然只在对用户有影响时响。

# 多窗口燃烧率告警(快速燃烧 + 慢速燃烧)
groups:
  - name: slo-burn-rate
    rules:
      - alert: ErrorBudgetBurnFast
        expr: |
          (sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
           / sum(rate(http_requests_total[5m])) by (job)) > (14.4 * 0.001)
          and
          (sum(rate(http_requests_total{status=~"5.."}[1h])) by (job)
           / sum(rate(http_requests_total[1h])) by (job)) > (14.4 * 0.001)
        for: 2m
        labels: { severity: critical }
        annotations:
          summary: "{{ $labels.job }} 错误预算快速燃烧"
          runbook_url: "https://runbooks.internal/burn-rate"

SLO 与错误预算的完整推导可参考 SLO 与错误预算 ,告警规则与通知渠道的工程细节(分组、静默、路由)与本文的降噪策略互补。

4.4 阈值告警的定位

阈值告警不是不能用,而是要改变定位:
  症状型告警(SLO/可用性)→ 触发呼叫,因为用户受影响
  原因型告警(CPU/内存/磁盘)→ 只进仪表盘或 IM,供诊断用

判断一条告警该不该呼叫,问一句:
  「用户现在受影响了吗?」——是则呼叫,否则不呼叫。

5. 可操作性与 Runbook 绑定

5.1 告警必须可操作

一条合格的告警通知应包含:
  1. 是什么:告警名 + 一句话描述
  2. 影响:哪个服务、哪个区域、影响多少用户
  3. 证据:关键指标链接(仪表盘、日志查询)
  4. 动作:第一件事做什么(Runbook 链接)
  5. 升级:多久未解决该找谁

缺失 3、4、5 的告警 = 不可操作 = 噪音。

5.2 告警与 Runbook 绑定

annotations:
  summary: "订单服务 P99 延迟超过 2s"
  description: "{{ $labels.job }} 在 {{ $labels.region }} 的 P99 达到 {{ $value }}s"
  dashboard_url: "https://grafana.internal/d/order-svc"
  runbook_url: "https://runbooks.internal/order-latency"
  escalation: "30 分钟未恢复,联系 order-platform"

Runbook 的编写与自动化(Runbook as Code、自愈脚本)可参考 运维自动化与 Runbook as Code ——当同一告警反复出现且处置步骤固定时,就应该把它自动化掉,而不是反复人工处理。

5.3 告警评审机制

定期评审(每两周一次):
  1. 列出本周期所有告警,按「是否可操作」分类
  2. 不可操作的告警:要么删除,要么改造成可操作
  3. 反复出现但无需人工的告警:自动化或降级为信息
  4. 从未触发过的告警:检查是否失效(阈值过松或指标断流)
  5. 记录每条告警的处置时长,识别「卡住」的环节

6. 交接与复盘

6.1 班次交接

交接清单(每次换班必过):
  □ 当前未关闭的告警与事故
  □ 进行中的变更/发布
  □ 已知的脆弱点(如某节点磁盘告警中)
  □ 需要跟进的事项(临时缓解措施、待观察项)
  □ 环境异常(如监控采集有缺口)
# 生成交接摘要(示意:从 PagerDuty API 拉取本班次数据)
curl -s -H "Authorization: Token $PD_TOKEN" \
  "https://api.pagerduty.com/incidents?since=$(date -u -v-12H +%Y-%m-%dT%H:%M:%SZ)&statuses[]=triggered&statuses[]=acknowledged" \
  | jq -r '.incidents[] | "\(.created_at) [\(.urgency)] \(.title)"'

6.2 复盘与改进

复盘(Postmortem)的核心不是「谁错了」,而是「系统哪里该改」:
  - 时间线:从第一个信号到完全恢复的每一步
  - 根因:技术根因 + 组织/流程根因
  - 改进项:每条改进项要有负责人与到期日
  - 告警质量:本次事故中,告警是「太晚」还是「太多」?
告警质量的三个反思问题:
  1. 用户受影响多久后我们才知道?(MTTD 是否合格)
  2. 有没有误报/重复告警干扰?(噪音是否掩盖了信号)
  3. 处置时缺了什么信息/权限?(可操作性是否足够)

无责备文化是复盘有效的前提,这一点在 SRE 的组织实践中已有系统总结。

7. 度量与常见坑

7.1 值班健康度量

告警量类:
  - 每班次打扰型告警数(目标 < 2)
  - 误报率(目标 < 20%)
  - 夜间告警占比(目标 < 10%)

响应类:
  - 告警确认时长(ack time)
  - MTTD(Mean Time To Detect):从故障发生到告警触发
  - MTTR(Mean Time To Repair):从告警到恢复

健康类:
  - 值班人满意度(定期问卷)
  - 值班导致的次日产出下降(间接度量)
  - 因告警疲劳导致的漏响应次数

7.2 常见坑

坑现象对策
阈值告警泛滥噪音淹没信号症状型呼叫、原因型进面板
无分级重要告警被稀释P1~P4 分级 + 分流通道
无抑制级联告警爆炸抑制规则 + 关联
告警不可操作收到不知做什么绑定 dashboard 与 runbook
无升级机制告警无人响应未确认自动升级
值班人数不足轮换过密,劝退≥ 4 人 + 主备 + 影子
无交接上下文丢失标准化交接清单
复盘无跟踪改进项烂尾改进项带负责人与到期日
无度量退化无人知上报告警量与误报率

7.3 一句话原则

值班工程的目标 = 让「被叫醒」这件事重新变得有意义。
  降噪到可处理、分级到准确、告警到可操作、复盘到有改进。

小结

值班不是排班表,而是一套需要设计的系统。治理告警疲劳的核心动作有四步:先降噪(去重、聚合、抑制,把量压到人可处理的水平)、再分级(按用户影响分级并分流到呼叫/IM/归档通道)、然后可操作(每条告警绑定仪表盘与 Runbook)、最后闭环(交接、复盘、度量,持续改进)。

衡量这套系统是否健康,看四个数就够了:每班次打扰型告警数、误报率、夜间告警占比、MTTD。当值班从「天天被吵醒、大部分是误报」变成「偶尔被叫醒、每次都真有大事」,团队才真正有了可靠的运营底座。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「devops」更多文章

  1. 基础设施代码测试:Terratest、Kitchen 与 InSpec
  2. 发布列车与版本节奏治理
  3. 依赖升级自动化:Renovate 与 Dependabot 实践