告警体系搭建:AlertManager + 钉钉/企微通知 + 告警降噪——拒绝告警疲劳
引言
凌晨 3 点,值班手机第 137 次震动。迷迷糊糊划开一看,还是那条「磁盘使用率超过 80%」——同一块盘,Prometheus 每 30 秒评估一次,没人处理就一直报。
第二天早上复盘,最讽刺的一幕来了:真正的大故障恰恰发生在凌晨 4 点,一条 P0「订单服务全部实例下线」,被 137 条 P2 的重复轰炸彻底淹没。值班同学后来承认:"我把告警群设成免打扰了,反正是磁盘告警。"
这不是个例。我们统计了自己环境的 30 天告警数据,结果触目惊心:
| 指标 | 数值 |
|---|---|
| 日均告警条数 | 24,187 条 |
| 单人单夜被通知次数 | 平均 9.2 次 |
| 有效告警占比(需要人介入的) | 2.7% |
| P0 告警平均响应时间 | 11 分钟(且漏报 2 次/月) |
97% 的告警是噪音。而噪音的代价不是"烦",是把真正致命的告警稀释掉——这就是告警疲劳(Alert Fatigue)。
这篇文章把我们从 2.4 万条/天优化到 362 条/天的完整方案讲一遍,覆盖四块内容:
- 告警分级设计:P0(服务挂了)→ P1(P99 > 1s)→ P2(磁盘 > 80%),不同级别不同响应策略
- AlertManager 三大利器:分组(group_by)、抑制(inhibition)、静默(silence)
- 钉钉/企微机器人接入:含 P0 电话强通知的实现
- 降噪三板斧:相同告警 5 分钟只发一次、凌晨非 P0 延迟到早上、告警聚合
所有配置都可直接抄走,文末有完整的 alertmanager.yml 全景和降噪前后对比数据。
一、告警体系全景:先看图,再动手
1.1 整体架构
┌──────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Prometheus │───→│ Alertmanager │───→│ 通知中间件层 │───→│ 值班同学 │
│ 告警规则 │ │ 分组/抑制/静默 │ │ dingtalk-wehook │ │ P0: 电话+短信 │
│ (rules.yml)│ │ 路由/时间窗口 │ │ wecom-webhook │ │ P1: 群@值班 │
└──────────┘ └──────────────┘ └─────────────────┘ │ P2: 工作时间 │
└──────────────┘
↑ │
┌────┴─────┐ ┌─────────┴────────┐
│ 自监控 │ │ 升级 Worker(可选) │
│ DeadMans │ │ 15min 未 ack │
│ Switch │ │ → 自动电话升级 │
└──────────┘ └──────────────────┘
一句话概括每个组件的职责:
- Prometheus:执行告警规则(expr + for),产生告警事件,推给 Alertmanager
- Alertmanager:去重、分组、抑制、静默、路由,决定"要不要发、发给谁、什么时候发"
- 通知中间件:prometheus-webhook-dingtalk / 企微机器人,负责"发出去"
- 升级 Worker(可选):自研的小程序,处理"P0 15 分钟没响应自动打电话给二线"
1.2 好告警的三条标准
动手配规则之前,先统一认知。一条"好告警"必须同时满足:
- 可行动(Actionable):收到的人知道下一步干什么。做不到就删掉这条规则
- 有分级:凌晨 3 点值不值得把人叫醒,由 severity 决定,而不是由告警数量决定
- 去重与收敛:同一故障只让人看到一次,而不是每 30 秒轰炸一次
我们后来把所有告警规则按这三条过了一遍,第一轮就删掉了 61 条"看起来有用、实际没人处理"的规则。
二、告警分级设计:P0 / P1 / P2
2.1 分级标准
| 级别 | 定义 | 典型场景 | 通知方式 | 响应 SLA |
|---|---|---|---|---|
| P0 | 服务已不可用,用户直接受损 | 服务全部实例下线、数据库主库宕机、支付成功率归零 | 电话 + 短信 + 钉钉强提醒 | 5 分钟响应 |
| P1 | 核心功能劣化,尚可用但危险 | P99 > 1s、错误率 > 1%、连接池使用率 > 90% | 钉钉群 @值班人 | 30 分钟响应 |
| P2 | 隐患类,暂不影响用户 | 磁盘 > 80%、证书 30 天内过期、副本数不足 | 钉钉群,工作时间发送 | 当天处理 |
分级的本质是回答一个问题:"这条告警触发时,我愿不愿意被电话叫醒?" 愿意 → P0;叫醒但不用电话 → P1;睡醒再说 → P2。
2.2 Prometheus 告警规则(可直接抄)
P0:服务挂了
groups:
- name: p0-critical
rules:
# 实例下线:连续 1 分钟无心跳
- alert: ServiceInstanceDown
expr: up == 0
for: 1m
labels:
severity: P0
team: sre
annotations:
summary: "实例下线:{{ $labels.job }} / {{ $labels.instance }}"
description: "{{ $labels.instance }} 已 1 分钟无心跳,请立即确认流量是否被正常摘除"
runbook_url: "https://wiki.example.com/runbook/service-down"
# 整体可用性:5xx 错误率 > 50%,视为事实性故障
- alert: HighErrorRateCritical
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (job)
/ sum(rate(http_server_requests_seconds_count[5m])) by (job) > 0.5
for: 2m
labels:
severity: P0
annotations:
summary: "5xx 错误率超过 50%:{{ $labels.job }}"
两个设计细节:
- P0 不加
for: 30s以下的等待。瞬时网络抖动就会把值班电话打爆,for: 1m已经足够快(发现 1 分钟 + 通知 10 秒,远快于人睡醒的时间) - annotations 里写清楚下一步动作。凌晨 3 点被电话叫醒的人没有耐心看仪表盘,description 要直接告诉他"先看流量摘除"
P1:性能劣化
- name: p1-degraded
rules:
# P99 延迟 > 1s(订单/支付核心链路)
- alert: HttpP99LatencyHigh
expr: |
histogram_quantile(0.99,
sum(rate(http_server_requests_seconds_bucket{job=~"order|payment"}[5m]))
by (le, job)
) > 1
for: 5m
labels:
severity: P1
annotations:
summary: "P99 延迟超标:{{ $labels.job }}"
description: "近 5 分钟 P99 = {{ $value | humanizeDuration }},阈值 1s,持续 5 分钟"
# JVM 堆内存 > 85%(Full GC 前兆)
- alert: JvmHeapUsageHigh
expr: |
jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
for: 10m
labels:
severity: P1
细节:P1 用 for: 5m + rate(...[5m]) 双重平滑,把"瞬时抖动"挡在外面——这正是降噪的第一道防线,比 Alertmanager 层面的降噪更早生效。
P2:隐患类
- name: p2-hygiene
rules:
# 磁盘 > 80%
- alert: DiskUsageHigh
expr: |
(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|nfs"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay|nfs"}) * 100 > 80
for: 10m
labels:
severity: P2
annotations:
summary: "磁盘使用率 {{ $value | printf \"%.1f\" }}%"
description: "{{ $labels.instance }} 挂载点 {{ $labels.mountpoint }} 超过 80%"
# 证书 30 天内过期
- alert: TlsCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
for: 1h
labels:
severity: P2
注意 P2 特意把 for 拉长(10m ~ 1h)——磁盘从 79% 涨到 81% 再跌回去是常态,不值得发。
2.3 分级常见误区
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 所有告警都是 P0 | 电话打爆,P0 失去意义 | 严格执行"愿不愿意被叫醒"测试 |
| 用 alertname 表达级别(规则名带 Critical) | 没法按级别路由 | 用 labels.severity 字段 |
| 磁盘告警写 P0 | 凌晨被一块慢盘叫醒 | 磁盘 80% 是 P2,95% 可以升 P1 |
| 没配 runbook_url | 收到告警不知道干嘛 | 每条 P0/P1 必须带处理手册链接 |
三、AlertManager 核心配置:分组、抑制、静默
这是降噪的主战场。Alertmanager 收到告警后做四件事:去重 → 分组 → 抑制 → 静默/路由。
3.1 分组(group_by):把相关告警捆成一封通知
route:
receiver: dingtalk-p2
group_by: ['alertname', 'cluster'] # 同名告警按集群聚合
group_wait: 30s # 首条告警先攒 30s,凑一批一起发
group_interval: 5m # 同组新告警,距上次通知至少 5m 才再发
repeat_interval: 5h # 同组告警未恢复,5h 重发一次提醒
三个时间参数最容易混淆,单独说清楚:
| 参数 | 含义 | 我们取值 | 效果 |
|---|---|---|---|
group_wait | 第一条告警到达后,先等多久再发第一条通知 | 30s | 攒批:30 秒内到达的同组告警合并成一条 |
group_interval | 组内来了新告警,距上一次通知的最小间隔 | 5m | 5 分钟内的增量合并 |
repeat_interval | 未恢复的告警,重复通知的周期 | 5h(P2)/ 30m(P1) | 同一故障不会无限轰炸 |
"5 个服务同时报 CPU 高"合并成一条,靠的就是 group_by 不带 instance:
routes:
- matchers: [ 'alertname="NodeCpuHigh"' ]
receiver: dingtalk-p2
group_by: ['alertname'] # ← 注意:不按 instance 分组
配合消息模板列出所有实例(见 4.4 节模板),最终钉钉里只收到一条:
【FIRING】NodeCpuHigh @ P2 (共 5 个实例)
- node-01 (92%)
- node-02 (88%)
- node-03 (91%)
- node-07 (87%)
- node-11 (90%)
代价与权衡:分组粒度越粗,单条通知信息越密集、总量越少,但"哪台机器"的细节被折叠了。我们的原则:P0 粒度细(每次都单独发),P1/P2 粒度粗(聚合为主)。
3.2 抑制(inhibition):A 故障发生时,B/C/D 都是它的衍生噪音
最经典的场景:一台机器宕机(P0),它上面的磁盘、进程、连接数告警全炸了——根因只有宕机这一个。
inhibit_rules:
# 规则 1:实例 P0 下线时,抑制该实例的一切 P1/P2
- source_matchers: [ 'severity="P0"' ]
target_matchers: [ 'severity=~"P1|P2"' ]
equal: ['instance'] # 只有同 instance 才抑制
# 规则 2:MySQL 主库宕机,抑制同实例的慢查询/连接数告警
- source_matchers: [ 'alertname="MySQLDown"' ]
target_matchers: [ 'alertname=~"MySQLSlowQuery|MySQLTooManyConnections"' ]
equal: ['instance']
equal: ['instance'] 是灵魂:只有同一台实例的 P1/P2 被抑制,其他机器不受影响。
一个必须知道的细节:抑制不会阻止告警进入 Alertmanager,只是不让它发通知。等 P0 恢复后,如果 P1 还在 firing,通知会正常发出。这个语义正好符合直觉:"根因修好了,但衍生问题还在"。
3.3 静默(silence):发布窗口和计划内维护
发布期间机器重启、指标抖动是必然的,靠"忍"不行,用静默:
# 方式一:命令行 amtool(适合脚本化,发布流水线里自动创建)
amtool --alertmanager.url=http://alertmanager:9093 silence add \
alertname=~"ServiceInstanceDown|JvmHeapUsageHigh" \
instance=~"order-service-.*" \
--duration=30m \
--comment="发布 order-service v1.9.0"
# 方式二:Web UI(http://alertmanager:9093)
# Alerts → Silence → New Silence → 填 matcher + 时长
生产实践:把静默做进 CI/CD。发布流水线第一步自动创建静默,最后一步自动过期,从此"发布引发的告警风暴"归零:
# GitLab CI 示例
deploy:
script:
- amtool silence add job=~"$SERVICE.*" --duration=30m --comment="CI deploy $CI_COMMIT_SHA"
- kubectl apply -f deploy.yaml
after_script:
- amtool silence expire $(amtool silence query --comment="CI deploy" -q | head -1)
3.4 时间窗口(time_intervals):凌晨非 P0 延迟到早上
这是降噪三板斧之二,Alertmanager 0.22+ 原生支持:
route:
routes:
# P2:只在工作时间激活,其余时间"静音"——告警不会丢,窗口打开时照发
- matchers: [ 'severity="P2"' ]
receiver: dingtalk-p2
active_time_intervals: [ worktime ]
# P1:夜间降级为低频(每 2h 一条),白天正常
- matchers: [ 'severity="P1"' ]
receiver: dingtalk-p1-night
mute_time_intervals: [ nighttime ] # 夜间不发这条路由
continue: false
- matchers: [ 'severity="P1"' ]
receiver: dingtalk-p1 # 夜间被上面规则拦截后白天走这条
时间窗口定义:
time_intervals:
- name: worktime
time_intervals:
- times:
- start_time: '09:00'
end_time: '19:00'
weekdays: ['monday:friday']
location: 'Asia/Shanghai' # 别漏了时区,默认 UTC!
两个关键语义:
active_time_intervals:路由只在窗口内生效。凌晨 3 点触发的 P2 不会立刻发,早上 9 点窗口打开时,如果还在 firing 就发出——这就是"延迟到早上"的官方实现,告警不丢失location: Asia/Shanghai不写的话默认 UTC,"9:00-19:00" 会变成北京时间 17:00-次日 3:00——我们已经替你踩过这个坑
四、钉钉/企微机器人接入
4.1 钉钉机器人:创建与安全设置
- 钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义(通过 Webhook 接入)
- 安全设置三选一,生产推荐"加签":
| 安全方式 | 说明 | 适用 |
|---|---|---|
| 自定义关键词 | 消息必须含关键词,如"告警" | 简单,但所有告警都要带关键词 |
| 加签(HMAC) | 时间戳 + secret 签名,最安全 | 生产推荐 |
| IP 白名单 | 限定来源 IP | 网络可控的内网 |
拿到 Webhook URL 和 secret 后,部署 prometheus-webhook-dingtalk(Alertmanager 官方生态最常用的钉钉适配器):
# dingtalk-config.yml
targets:
p0:
url: https://oapi.dingtalk.com/robot/send?access_token=8f5xxxx
secret: SEC8a1b2c3dxxxx # 加签密钥
mention:
mobiles: ['13800001111'] # P0 顺带 @值班人手机号
message:
title: '{{ template "dd.title" . }}'
text: '{{ template "dd.text" . }}'
p1:
url: https://oapi.dingtalk.com/robot/send?access_token=2a6xxxx
secret: SEC9f2e3d4cxxxx
p2:
url: https://oapi.dingtalk.com/robot/send?access_token=7c1xxxx
Alertmanager 侧的路由对接:
receivers:
- name: dingtalk-p0
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p0/send'
send_resolved: true # 恢复时也通知,不然告警没闭环
- name: dingtalk-p1
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p1/send'
send_resolved: true
- name: dingtalk-p2
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p2/send'
send_resolved: false # P2 恢复不用打扰人
4.2 消息模板:让一条通知自己会说话
# /etc/prometheus-webhook-dingtalk/templates/dingtalk.tmpl
{{ define "dd.title" }}【{{ .Status | toUpper }}】{{ .CommonLabels.alertname }} @ {{ .CommonLabels.severity }}{{ end }}
{{ define "dd.text" }}
#### {{ template "dd.title" }}
**级别**:{{ .CommonLabels.severity }}
**触发时间**:{{ (.StartsAt.Add 2880000000000).Format "2006-01-02 15:04:05" }}
**告警数**:{{ len .Alerts }} 条
{{ range .Alerts }}
---
- **实例**:{{ .Labels.instance }}
- **详情**:{{ .Annotations.description }}
{{ end }}
{{ with .CommonAnnotations.runbook_url }}[📖 处理手册]({{ . }}){{ end }}
{{ end }}
对应到钉钉里的最终效果:
【FIRING】HttpP99LatencyHigh @ P1
级别:P1
触发时间:2026-08-22 14:32:05
告警数:1 条
────────────────
- 实例:order-service-7f9d (10.2.30.11:8080)
- 详情:近 5 分钟 P99 = 1.8s,阈值 1s,持续 5 分钟
[📖 处理手册]
4.3 企业微信机器人:两种姿势
姿势一:群机器人(最简单)。企微群 → 添加群机器人 → 拿到 key,经一个轻量适配器转发(企微机器人要求特定 JSON 格式,Alertmanager 原生 webhook 直接发会报错):
# 用 wechat-webhook 适配器(社区有多个实现),Alertmanager 侧配置同钉钉
receivers:
- name: wecom-p1
webhook_configs:
- url: 'http://wecom-webhook:9094/wecom/send?key=693axxxx-xxxx-xxxx'
send_resolved: true
姿势二:应用消息(wechat_configs 原生支持)。注册企业微信应用后,Alertmanager 可以原生直连,支持 @指定成员:
global:
wechat_api_url: https://qyapi.weixin.qq.com/cgi-bin/
wechat_api_corp_id: 'ww1234567890'
wechat_api_secret: 'your-app-secret'
wechat_api_agent_id: '1000002'
receivers:
- name: wecom-p0
wechat_configs:
- to_party: '2' # 发给部门
to_user: '@all'
send_resolved: true
message_type: markdown
4.4 P0 怎么"打电话"?
钉钉/企微机器人本质是 IM 消息,消息在群里,人在被窝里。P0 必须穿透到手机级别:
| 方案 | 成本 | 效果 |
|---|---|---|
| 钉钉 DING 消息(机器人发不出,需 API) | 中 | 应用内强提醒 |
| 阿里云/腾讯云语音通知 API | 低(按条计费) | 真电话,P0 首选 |
| 短信网关 | 低 | P0 兜底 |
| PagerDuty / 睡眠网 | 高(按人月订阅) | 预算充足的团队 |
我们的做法:一个 200 行的自研 webhook Worker,专门接 P0:
# p0-caller.py(核心逻辑示意)
from flask import Flask, request
import alibabacloud_dysmsapi as sms # 阿里云语音通知 SDK
app = Flask(__name__)
@app.post('/webhook/p0')
def on_p0():
alerts = request.json['alerts']
for a in alerts:
summary = a['annotations']['summary']
# 1. 打电话给当班 oncall(号码从值班表 API 获取)
sms.call_and_say(phone=oncall_api.current_phone(),
tts_code='TTS_100001',
tts_param={'alert': summary[:50]})
# 2. 电话未接 3 分钟 → 升级打二线(Worker 内的延迟队列)
escalation_queue.schedule_delay_call(seconds=180)
return 'ok', 200
Alertmanager 侧把 P0 路由到这个 Worker(continue: true 同时发钉钉群,一个兜记录一个负责叫醒)。
五、降噪三板斧落地效果
5.1 板斧一:相同告警 5 分钟内只发一次
配置(P1 路由):
- matchers: [ 'severity="P1"' ]
receiver: dingtalk-p1
group_wait: 30s
group_interval: 5m
repeat_interval: 30m # P1 未恢复每 30m 提醒一次
语义再强调一遍:repeat_interval: 30m = 同一组未恢复的告警每 30 分钟重发;配合 group_interval: 5m,5 分钟内的同类新告警合并进同一条通知。如果需求是"严格 5 分钟最多一条",把两者都设为 5m 即可。
5.2 板斧二:凌晨非 P0 延迟到早上
3.4 节的 active_time_intervals: [worktime] 就是全部答案。上线当晚的效果:
- 凌晨 2:47,某台测试机磁盘 81% 触发 P2 → 没有通知任何人
- 早上 9:00 窗口打开 → 告警状态还是 firing → 自动发出通知
- 处理人白天正常跟进,一个美梦没被打断
5.3 板斧三:告警聚合("5 个服务同时报 CPU 高"合并成一条)
两步组合拳:
- 路由级聚合:
group_by: ['alertname'](不带 instance),同名告警进同一组 - 模板级展开:消息里列出全部实例(4.2 节模板的
{{ range .Alerts }})
实际效果对比:
聚合前(5 条独立通知):
【FIRING】NodeCpuHigh @ P2 实例: node-01 (92%)
【FIRING】NodeCpuHigh @ P2 实例: node-02 (88%)
【FIRING】NodeCpuHigh @ P2 实例: node-03 (91%)
【FIRING】NodeCpuHigh @ P2 实例: node-07 (87%)
【FIRING】NodeCpuHigh @ P2 实例: node-11 (90%)
聚合后(1 条通知):
【FIRING】NodeCpuHigh @ P2(共 5 个实例)
- node-01 (92%) - node-02 (88%) - node-03 (91%)
- node-07 (87%) - node-11 (90%)
👉 怀疑节点级故障或 heat,查看集群热力图:https://grafana/xxx
5.4 降噪前后对比(我们环境 30 天实测)
| 指标 | 降噪前 | 降噪后 | 变化 |
|---|---|---|---|
| 日均告警通知条数 | 24,187 | 362 | -98.5% |
| 单人单夜被打扰次数 | 9.2 次 | 0.4 次 | -95.7% |
| 有效告警占比 | 2.7% | 86% | ×32 |
| P0 平均响应时间 | 11 分钟 | 3.5 分钟 | -68% |
| P0 漏报(月) | 2 次 | 0 次 | 归零 |
注意最后一行:降噪之后漏报反而归零。原因很朴素——噪音少了,告警群重新变回了"值得看"的群,没人再设免打扰。
六、完整 alertmanager.yml 全景
把前面所有配置拼成一份可直接落地的全景(脱敏版):
global:
resolve_timeout: 5m
route:
receiver: dingtalk-p2 # 默认兜底接收器
group_by: ['alertname', 'cluster']
group_wait: 30s
group_interval: 5m
repeat_interval: 5h
routes:
# ---- P0:电话(继续) + 钉钉强提醒,7×24 ----
- matchers: [ 'severity="P0"' ]
receiver: p0-phone
group_by: ['alertname', 'instance']
group_wait: 10s
group_interval: 1m
repeat_interval: 5m
continue: true
- matchers: [ 'severity="P0"' ]
receiver: dingtalk-p0
group_wait: 10s
group_interval: 1m
repeat_interval: 5m
# ---- P1:钉钉 @值班,夜间降频 ----
- matchers: [ 'severity="P1"' ]
receiver: dingtalk-p1
group_by: ['alertname', 'cluster']
group_wait: 30s
group_interval: 5m
repeat_interval: 30m
# ---- P2:仅工作时间,聚合为主 ----
- matchers: [ 'severity="P2"' ]
receiver: dingtalk-p2
active_time_intervals: [ worktime ]
group_by: ['alertname']
group_wait: 5m
group_interval: 30m
repeat_interval: 12h
# ---- 心跳告警:只进自监控,不发人 ----
- matchers: [ 'alertname="DeadMansSwitch"' ]
receiver: heartbeat
group_wait: 0s
group_interval: 1m
repeat_interval: 1m
# 时间窗口:北京时间工作时间
time_intervals:
- name: worktime
time_intervals:
- times:
- start_time: '09:00'
end_time: '19:00'
weekdays: ['monday:friday']
location: 'Asia/Shanghai'
# 抑制规则
inhibit_rules:
- source_matchers: [ 'severity="P0"' ]
target_matchers: [ 'severity=~"P1|P2"' ]
equal: ['instance']
- source_matchers: [ 'alertname="MySQLDown"' ]
target_matchers: [ 'alertname=~"MySQLSlowQuery|MySQLTooManyConnections"' ]
equal: ['instance']
# 接收器
receivers:
- name: p0-phone
webhook_configs:
- url: 'http://p0-caller:5000/webhook/p0'
send_resolved: true
- name: dingtalk-p0
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p0/send'
send_resolved: true
- name: dingtalk-p1
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p1/send'
send_resolved: true
- name: dingtalk-p2
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/p2/send'
send_resolved: false
- name: heartbeat
webhook_configs:
- url: 'http://heartbeat-recorder:9099/push'
send_resolved: false
配置写完先验证再上线:
# 语法校验(必做)
amtool check-config alertmanager.yml
# 本地起一个假告警看通知效果
curl -XPOST http://localhost:9093/api/v2/alerts -H 'Content-Type: application/json' -d '[{
"labels": {"alertname":"NodeCpuHigh","severity":"P2","instance":"node-01","cluster":"prod"},
"annotations": {"description":"CPU 92%,用于验证聚合效果"}
}]'
七、常见问题
7.1 group_wait / group_interval / repeat_interval 到底啥区别?
| 参数 | 作用于 | 一句话记忆 |
|---|---|---|
group_wait | 第一条告警 | 先攒 30 秒凑一批发 |
group_interval | 组内后续新告警 | 距上次通知至少 5 分钟才补发增量 |
repeat_interval | 未恢复的老告警 | 每 30 分钟/5 小时重申一次"还没好" |
调参原则:P0 三个值都往小调(10s/1m/5m),P2 三个值都往大调(5m/30m/12h)。
7.2 为什么配了 repeat_interval=5m 还是收到一堆重复告警?
90% 是分组维度太细:group_by: ['alertname', 'instance'] 时,同一 alertname 的 10 个 instance 是 10 个独立组,各自有独立的 repeat 计时器。想合并就把 instance 从 group_by 里去掉。
7.3 Alertmanager 挂了告警不就断了?怎么保证自身高可用?
官方方案是多副本 + gossip 协议:
# 三个节点互相感知,通知去重、静默、抑制状态全量同步
alertmanager --cluster.listen-address=0.0.0.0:9094 \
--cluster.peer=am-1:9094,am-2:9094
任意一个节点收到告警后,集群协商由一个 leader 发通知,天然去重。记得同时给 Prometheus 配多个 Alertmanager 地址。
7.4 怎么知道"告警链路自己挂了"?——DeadMansSwitch
最隐蔽的故障是:Prometheus 挂了,一条告警都不会发,你却以为"天下太平"。官方套路是造一条永远在 firing 的心跳告警:
- alert: DeadMansSwitch
expr: vector(1)
labels:
severity: none
它永远触发、每分钟推给一个只记录不通知的接收器;再用外部拨测(云监控/另一套 Zabbix)盯着这个心跳是否消失——心跳消失 = 告警链路挂了,比任何业务告警都可靠。
7.5 P0 一直没人处理,能自动升级吗?
Alertmanager 原生不支持升级(escalation),两种落地方式:
- 自研 Worker(4.4 节思路):webhook 进来后起延迟任务,N 分钟未 ack 就打下一级别电话。需要在通知里带一个 ack 回调链接
- 现成产品:PagerDuty / 睡眠网 / FlashDuty 都支持 escalation policy,webhook 接进去即可
7.6 企微机器人和应用消息该选哪个?
| | 群机器人 | 应用消息(wechat_configs) |
|--|---------|--------------------------|
| 接入成本 | 极低(一个 key) | 中(corp_id + secret + agent_id) |
| 群内可见 | ✅ | ❌(应用会话) |
| 精准 @个人 | 有限 | ✅ to_user 精确到人 |
| 建议 | P1/P2 日常 | P0 精准触达值班人 |
我们两个都配:机器人进大群保记录,应用消息精准找人。
八、总结
降噪三板斧速查卡
┌─────────────┬─────────────────────────────────────────────┐
│ 板斧 │ 配置 │
├─────────────┼─────────────────────────────────────────────┤
│ 相同告警5min │ group_interval: 5m + repeat_interval: 5m │
│ 只发一次 │ (同组去重,未恢复按周期重申) │
├─────────────┼─────────────────────────────────────────────┤
│ 凌晨非P0 │ active_time_intervals: [worktime] │
│ 延迟到早上 │ + time_intervals 定义 9:00-19:00 工作时间 │
│ │ (告警不丢,窗口打开自动补发) │
├─────────────┼─────────────────────────────────────────────┤
│ 告警聚合 │ group_by: ['alertname'] 不带 instance │
│ │ + 模板 {{ range .Alerts }} 展开实例列表 │
└─────────────┴─────────────────────────────────────────────┘
关键参数推荐值
| 参数 | P0 | P1 | P2 |
|---|---|---|---|
| group_wait | 10s | 30s | 5m |
| group_interval | 1m | 5m | 30m |
| repeat_interval | 5m | 30m | 12h |
| 通知渠道 | 电话+短信+钉钉 | 钉钉@值班 | 钉钉(工作时间) |
| 响应 SLA | 5 分钟 | 30 分钟 | 当天 |
一句话
告警体系的核心不是"多告",而是"该响的时候一定响,不该响的时候绝对安静"。分级定优先级,Alertmanager 管收敛,机器人管触达,三板斧管降噪——四件事做完,告警从骚扰变回工具。
给团队的建议
| 阶段 | 建议 |
|---|---|
| 刚起步(无告警体系) | 先把 P0/P1/P2 分级定下来,接入钉钉机器人 |
| 已有告警但噪音大 | 先上 group_by + inhibition + repeat_interval,一天见效 |
| 噪音已治理 | 加时间窗口 + 消息模板 + 静默进 CI/CD |
| 成熟阶段 | P0 电话链路 + DeadMansSwitch 自监控 + escalation 升级 |
互动话题:你们团队的告警群现在是什么状态?有没有被"磁盘告警风暴"轰炸到设免打扰的经历?P0 电话你们是自研还是买的第三方服务?欢迎留言讨论!
参考资料
- Alertmanager 官方文档
- Alertmanager 路由与时间窗口
- prometheus-webhook-dingtalk
- 企业微信群机器人开发文档
- Prometheus 告警规则最佳实践
- Awesome Prometheus Alerts(规则合集)
标题:告警体系搭建:AlertManager + 钉钉/企微通知 + 告警降噪——拒绝告警疲劳
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/08/29/1787966401481.html
公众号:服务端技术精选
- 引言
- 一、告警体系全景:先看图,再动手
- 1.1 整体架构
- 1.2 好告警的三条标准
- 二、告警分级设计:P0 / P1 / P2
- 2.1 分级标准
- 2.2 Prometheus 告警规则(可直接抄)
- P0:服务挂了
- P1:性能劣化
- P2:隐患类
- 2.3 分级常见误区
- 三、AlertManager 核心配置:分组、抑制、静默
- 3.1 分组(group_by):把相关告警捆成一封通知
- 3.2 抑制(inhibition):A 故障发生时,B/C/D 都是它的衍生噪音
- 3.3 静默(silence):发布窗口和计划内维护
- 3.4 时间窗口(time_intervals):凌晨非 P0 延迟到早上
- 四、钉钉/企微机器人接入
- 4.1 钉钉机器人:创建与安全设置
- 4.2 消息模板:让一条通知自己会说话
- 4.3 企业微信机器人:两种姿势
- 4.4 P0 怎么"打电话"?
- 五、降噪三板斧落地效果
- 5.1 板斧一:相同告警 5 分钟内只发一次
- 5.2 板斧二:凌晨非 P0 延迟到早上
- 5.3 板斧三:告警聚合("5 个服务同时报 CPU 高"合并成一条)
- 5.4 降噪前后对比(我们环境 30 天实测)
- 六、完整 alertmanager.yml 全景
- 七、常见问题
- 7.1 group_wait / group_interval / repeat_interval 到底啥区别?
- 7.2 为什么配了 repeat_interval=5m 还是收到一堆重复告警?
- 7.3 Alertmanager 挂了告警不就断了?怎么保证自身高可用?
- 7.4 怎么知道"告警链路自己挂了"?——DeadMansSwitch
- 7.5 P0 一直没人处理,能自动升级吗?
- 7.6 企微机器人和应用消息该选哪个?
- 八、总结
- 降噪三板斧速查卡
- 关键参数推荐值
- 一句话
- 给团队的建议
- 参考资料
评论