Prometheus 接入 Alertmanager 与钉钉机器人,附中文模板和完整踩坑记录
openEuler 22.03 实战:Prometheus 接入 Alertmanager 与钉钉机器人,附中文模板和完整踩坑记录
本文记录一次从已有 Prometheus、node_exporter、Grafana 环境出发,接入 Alertmanager、钉钉机器人,并完善中文通知模板的实际过程。
不重复安装已经正常运行的 Prometheus,不迁移现有部署目录,不重新创建规则目录,所有操作采用 root。文中包含正式配置、验证方法、日常操作,以及本次实际遇到的错误和排查过程。
本文已脱敏:真实 IP、主机名、部署根目录、业务名称及网卡标识已移除或替换为统一示例;Webhook、Token、密钥仅保留占位符。下文命令、配置、规则正则、中文模板和访问链接均已同步替换,不披露脱敏前后的对应关系。root 部署是本次环境的选择,并非最小权限生产部署的推荐方案。
文章目录
- openEuler 22.03 实战:Prometheus 接入 Alertmanager 与钉钉机器人,附中文模板和完整踩坑记录
- 发布说明:本文已进行环境信息脱敏
- 一、部署背景与最终目标
- 二、目录规划与操作约定
- 三、部署 Alertmanager
- 四、浏览器访问不通:定位 firewalld
- 五、接入已有 Prometheus:不重装,只修改并热加载
- 六、理解告警时间参数:不要混淆采集、计算和通知
- 七、正式主机、磁盘与网络规则
- 八、部署 prometheus-webhook-dingtalk 2.1.0
- 九、Alertmanager 正式切换到钉钉接收器
- 十、测试告警与恢复通知
- 十一、配置正式中文钉钉模板
- 十二、本次实际踩坑记录
- 坑 1:备份命令中 date 后少空格
- 坑 2:命令行参数等号后加了空格
- 坑 3:误以为同目录就不需要 ./
- 坑 4:curl -I 返回 405,被误认为服务异常
- 坑 5:本机正常,浏览器一直转圈
- 坑 6:规则目录和规则文件混淆
- 坑 7:以为全局 evaluation_interval 就决定所有规则周期
- 坑 8:指标名字叫 ratio,实际却是百分数
- 坑 9:直接套用 job="node" 导致没有数据
- 坑 10:把本机的两个采集接口当成两台机器
- 坑 11:YAML 缩进使用了 Tab
- 坑 12:编辑器的波浪线和状态信息不是配置内容
- 坑 13:以为检查通过就已经生效
- 坑 14:出现 group is not exist,就直接断定机器人失效
- 坑 15:恢复通知没有恰好五分钟到达
- 坑 16:公开了完整 Webhook
- 十三、为什么本次先不启用通用抑制规则
- 十四、最终验收与日常操作速查
- 十五、后续完善方向与已知边界
- 十六、总结
- 参考入口
发布说明:本文已进行环境信息脱敏
为避免公开真实网络拓扑和环境标识,本文进行了以下处理。所有示例值只用于说明结构,不是原环境的真实地址或名称,也不列出原值映射表。
| 信息类别 | 本文公开写法 | 处理说明 |
|---|---|---|
| 服务器 IP | 192.0.2.10、192.0.2.20、192.0.2.30、192.0.2.40 | 已脱敏 |
| 管理电脑 IP | 192.0.2.100 | 已脱敏,与示例服务器网段保持一致 |
| 主机名 | app-node-01、monitor-node、app-node-02、app-node-03 | 仅为脱敏角色别名,不保留原主机名 |
| 部署根目录 | /opt/monitoring-demo | 已脱敏,程序子目录及软件版本保持技术可读性 |
| 业务名称 | 业务服务 A、监控服务、业务服务 B、业务服务 C | 已泛化,不披露实际业务系统名称 |
| 活动网卡 | eth0 | 示例标识,不代表真实网卡名称 |
| 机器人凭据 | REPLACE_WITH_YOUR_TOKEN 等占位符 | 不包含真实 Webhook Token、SEC 或应用密钥 |
| 截图及终端身份信息 | 不嵌入原始截图,不保留原终端主机名 | 避免泄露群成员、个人水印、组织与内部环境信息 |
使用前必须替换: 192.0.2.0/24 是保留文档地址,不是推荐实际部署网段。真实部署时,必须把地址、主机别名、目录、网卡、防火墙来源、PromQL 正则、模板映射和页面链接一起改为自己的环境;否则可能出现无法绑定地址、指标为空或链接不可达。
127.0.0.1、localhost 和 0.0.0.0 是通用网络语义,不是原环境的唯一身份信息,按原用途保留;标准端口、软件名称/版本及钉钉官方接口域名也保留,便于理解和复现。
一、部署背景与最终目标
1.1 已有环境
四台服务器已经安装 node_exporter,其中 192.0.2.20 同时运行 Prometheus 和 Grafana。
| 组件 | 本次版本或情况 | 用途 |
|---|---|---|
| 操作系统 | openEuler 22.03 虚拟机 | 运行监控组件 |
| Prometheus | 3.8.0 | 指标采集、记录规则、告警规则计算 |
| node_exporter | 1.10.2,二进制解压部署 | 四台机器提供主机指标,监控服务器本机也已安装 |
| Grafana | 5.4.2,通过 RPM 安装 | 已有可视化环境,不作为解压目录放在部署根目录下,也不在本次重新安装 |
| Alertmanager | 0.29.0 | 告警分组、去重、静默、抑制及通知路由 |
| prometheus-webhook-dingtalk | 2.1.0 | 转换 Alertmanager 通知并调用钉钉机器人接口 |
Grafana 5.4.2 版本较旧。本文沿用现有环境,后续应单独规划备份、兼容性验证和升级,不建议在本次告警接入中顺带进行未经验证的大版本升级。
1.2 四台机器与五个采集目标
| 示例主机名(已脱敏) | 示例 IP(已脱敏) | 业务名称(已泛化) | node_exporter 采集地址 |
|---|---|---|---|
| app-node-01 | 192.0.2.10 | 业务服务 A | 192.0.2.10:9100 |
| monitor-node | 192.0.2.20 | 监控服务 | 192.0.2.20:9100 |
| app-node-02 | 192.0.2.30 | 业务服务 B | 192.0.2.30:9100 |
| app-node-03 | 192.0.2.40 | 业务服务 C | 192.0.2.40:9100 |
Prometheus 另外采集自身:
localhost:9090
因此,up 查询看到五个目标,并不是有五台机器,而是:
四台机器的 node_exporter + Prometheus 自身 = 五个采集目标
特别注意:
192.0.2.20:9100 → 本机 CPU、内存、磁盘、网络等主机指标
localhost:9090 → Prometheus 服务自身指标
它们属于同一台机器的不同接口,不能相互替代。主机告警筛选 :9100,并不意味着排除了监控服务器本机。
1.3 采集与计算周期
本环境中:
- 普通机器采集间隔为
30s。 - 部分业务繁忙机器单独设置为
60s。 - Prometheus 全局规则计算间隔为
60s。 - 已有预计算规则组单独配置了
interval: 30s。
规则组自己的 interval 会覆盖全局 evaluation_interval。 因此,这个已有预计算组实际上每 30 秒计算一次,并不是每 60 秒计算一次。本次保留它,不同时调整既有 Grafana 指标计算行为。
1.4 最终链路
四台服务器的 node_exporter
│
▼
Prometheus(192.0.2.20:9090)
采集指标、执行预计算和告警规则
│
▼
Alertmanager(192.0.2.20:9093)
分组、去重、静默、抑制、路由
│
▼
prometheus-webhook-dingtalk(127.0.0.1:8060)
将通用告警转换为钉钉消息
│ HTTPS
▼
钉钉群机器人:告警通知 + 恢复通知
Alertmanager 的通用 Webhook 不应直接填写钉钉官方机器人地址。 两者的消息格式不同,需要适配器转换。
二、目录规划与操作约定
2.1 沿用已有部署布局(目录已脱敏)
Prometheus、node_exporter、Alertmanager 和钉钉适配器使用二进制解压目录;Grafana 5.4.2 已通过 RPM 安装,不属于这些解压目录。 本次不移动已有程序、业务配置和数据。
为公开发布,原部署根目录统一替换为 /opt/monitoring-demo;这只是文档脱敏,不是要求迁移服务器上的实际目录。
以下是监控服务器本机的关键目录结构,省略压缩包、备份文件和部分许可文件:
/opt/monitoring-demo/ # 已脱敏的部署根目录
├── node_exporter-1.10.2.linux-amd64/ # 本机 node_exporter
│ ├── node_exporter
│ ├── LICENSE
│ └── NOTICE
├── prometheus-3.8.0.linux-amd64/
│ ├── prometheus
│ ├── promtool
│ ├── prometheus.yml
│ ├── rules.yml # 预计算与告警共用此文件
│ └── data/
├── alertmanager-0.29.0.linux-amd64/
│ ├── alertmanager
│ ├── amtool
│ ├── alertmanager.yml
│ └── data/
└── prometheus-webhook-dingtalk-2.1.0.linux-amd64/
├── prometheus-webhook-dingtalk
├── config.example.yml
├── config.yml
├── chinese.tmpl
└── contrib/
说明:
- 最终环境中的
rules/目录已经删除,不需要重新创建。 全部预计算和告警规则继续写入现有rules.yml。 - 本机的 node_exporter 也位于部署根目录下,采集目标为本机的
:9100;其他三台机器也各自运行 node_exporter,但目录未在这棵本机目录树中展开。 config.example.yml是适配器自带示例,本次新建config.yml作为实际配置。- systemd 服务定义放在系统标准位置,不属于迁移原有程序或业务配置。
- 下文命令中的程序目录必须与实际解压目录一致,尤其不要把
alertmanager错写为altermanager。
Grafana 的 RPM 安装方式说明
Grafana 5.4.2 是原有 RPM 安装环境,不能把 RPM 安装包的存放位置当成 Grafana 的实际运行目录,也不应在上面的目录树中凭空增加一个 Grafana 解压目录。
RPM 安装的常见配置、程序及数据位置包括:
/etc/grafana/grafana.ini # 常见主配置文件位置
/usr/share/grafana/ # 常见程序资源目录
/var/lib/grafana/ # 常见数据目录
/var/log/grafana/ # 常见日志目录
以上只是常见默认位置,不代表本次已逐项核实;实际位置以 RPM 文件清单、服务启动参数和运行配置为准。可以只读检查:
# 查看已安装的 Grafana 包版本。
rpm -q grafana
# 查看 RPM 安装的文件清单,不执行重新安装。
rpm -ql grafana
# 查看服务启动参数及可能的配置路径覆盖。
systemctl cat grafana-server
如果安装的 RPM 包名不是 grafana,应使用实际包名查询。本次 Alertmanager 和钉钉接入不需要重新安装或迁移 Grafana。
2.2 先确认架构和可执行文件
uname -m
x86_64对应linux-amd64。aarch64对应linux-arm64。
进入相应目录后,可检查版本:
./alertmanager --version
./amtool --version
适配器目录中执行:
./prometheus-webhook-dingtalk --version
./prometheus-webhook-dingtalk --help
如果出现 Exec format error,优先检查架构是否匹配,不要继续修改配置尝试解决。
2.3 每次修改前备份
以 Alertmanager 为例:
cp -a alertmanager.yml alertmanager.yml.bak.$(date +%Y%m%d-%H%M%S)
date 后必须有空格。 这也是本次实际遇到的第一个小坑。
三、部署 Alertmanager
3.1 进入已解压目录
cd /opt/monitoring-demo/alertmanager-0.29.0.linux-amd64
pwd
ls -lh
chmod +x ./alertmanager ./amtool
3.2 先用不外发通知的配置启动
备份:
cp -a alertmanager.yml alertmanager.yml.bak.$(date +%Y%m%d-%H%M%S)
编辑:
vi alertmanager.yml
第一阶段使用以下配置,先验证服务及 Prometheus 到 Alertmanager 的链路:
# ==================== 全局配置 ====================
global:
# 主要用于未提供结束时间的告警。
# Prometheus 通常会携带结束时间,本项不是告警规则的等待时间。
resolve_timeout: 5m
# ==================== 默认告警路由 ====================
route:
# 空接收器,仅在页面展示告警,暂不发送外部通知。
receiver: local-only
# 按告警名称和实例分组。
group_by:
- alertname
- instance
# 新告警组首次通知前等待时间。
group_wait: 30s
# 已通知组出现变化后,后续通知检查的间隔。
group_interval: 5m
# 持续未恢复且没有其他变化时,重复提醒的间隔。
repeat_interval: 1h
receivers:
- name: local-only
不要把这个空接收器误认为“钉钉已经配置好”。它没有外部通知集成。
检查配置,创建数据目录:
./amtool check-config ./alertmanager.yml
mkdir -p ./data
3.3 手动前台启动
./alertmanager --config.file=./alertmanager.yml --storage.path=./data --web.listen-address=192.0.2.20:9093 --cluster.listen-address=
说明:
--cluster.listen-address=故意留空,关闭本次单实例不需要的集群监听。- 前台启动后终端不返回提示符,属于正常运行。
- 不要按
Ctrl+C后再判断服务为什么不可访问;按下后进程已经停止。 - 不需要为单实例额外开放集群端口 9094。
另开 SSH 终端检查:
ss -lntp | grep ':9093'
curl --noproxy '*' -fsS --max-time 5 http://192.0.2.20:9093/-/healthy
curl --noproxy '*' -fsS --max-time 5 http://192.0.2.20:9093/-/ready
浏览器访问:
http://192.0.2.20:9093
当前监听的是内网地址 192.0.2.20,不要因为访问 127.0.0.1:9093 失败就认定服务异常。
3.4 配置 systemd
先停止前台进程,按 Ctrl+C。检查是否已有服务:
systemctl cat alertmanager
如果已存在,先检查和备份;不存在再创建:
vi /etc/systemd/system/alertmanager.service
服务配置:
[Unit]
Description=Prometheus Alertmanager
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
# 本次使用 root,不另外创建账户。
User=root
Group=root
# 保留已有部署目录。
WorkingDirectory=/opt/monitoring-demo/alertmanager-0.29.0.linux-amd64
# 程序、配置和持久化数据均在原目录。
ExecStart=/opt/monitoring-demo/alertmanager-0.29.0.linux-amd64/alertmanager \
--config.file=/opt/monitoring-demo/alertmanager-0.29.0.linux-amd64/alertmanager.yml \
--storage.path=/opt/monitoring-demo/alertmanager-0.29.0.linux-amd64/data \
--web.listen-address=192.0.2.20:9093 \
--cluster.listen-address=
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
启动:
systemctl daemon-reload
systemctl enable --now alertmanager
systemctl status alertmanager --no-pager -l
journalctl -u alertmanager -n 100 --no-pager
预期状态:
Active: active (running)
如果是修改已经运行的服务定义,而不是首次创建,enable --now 不会自动重启旧实例。执行 daemon-reload 后,还需要在确认配置和影响范围后执行 systemctl restart alertmanager,使新的启动参数生效。
四、浏览器访问不通:定位 firewalld
4.1 本次现象
浏览器访问 9093 一直转圈,但服务器检查显示:
LISTEN ... 192.0.2.20:9093 ... alertmanager ...
就绪接口返回:
HTTP/1.1 200 OK
OK
这说明程序已经监听,且服务器本机能够访问。不能再盲目修改程序监听参数,应检查外部访问路径。
4.2 防火墙检查
systemctl is-active firewalld
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all
本次实际规则中开放了:
9090/tcp 3000/tcp 9100/tcp
但没有 9093/tcp,活动接口为 eth0,区域为 public。
4.3 临时放行验证
firewall-cmd --zone=public --add-port=9093/tcp --timeout=10m
放行后,浏览器立即可以访问,确认了本次访问故障与防火墙有关。
注意:这条命令在 10 分钟内会允许所有能到达该服务器的来源访问此端口,仅用于定位,不能当作永久规则。
4.4 推荐永久规则:限制管理来源
以下假设管理电脑 IP 为 192.0.2.100,必须替换为实际来源地址;如有 NAT,应考虑服务器实际看到的来源。
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.0.2.100/32" port port="9093" protocol="tcp" accept'
firewall-cmd --reload
firewall-cmd --zone=public --list-all
如果是已经向所有来源开放了 9093 的环境,还应核查并在确认不影响使用后移除宽泛规则,否则单独新增限制来源规则并不会收紧原来的开放范围。
本次为方便内网访问,也讨论过直接永久开放端口:
firewall-cmd --permanent --zone=public --add-port=9093/tcp
firewall-cmd --reload
两种方案择一。优先使用限制来源方案,不要关闭整个防火墙,不要把未认证的 Alertmanager 暴露到公网。
五、接入已有 Prometheus:不重装,只修改并热加载
5.1 确认实际配置路径
systemctl status prometheus --no-pager -l
systemctl cat prometheus
本次已有服务的关键配置如下,无需重新安装:
[Service]
# 相对配置路径依赖工作目录。
WorkingDirectory=/opt/monitoring-demo/prometheus-3.8.0.linux-amd64
# 已启用生命周期接口,可以 HTTP 热加载。
ExecStart=/opt/monitoring-demo/prometheus-3.8.0.linux-amd64/prometheus --config.file=prometheus.yml --web.enable-lifecycle
Restart=always
RestartSec=5
LimitNOFILE=65535
因此,实际读取的是该目录下的 prometheus.yml。
--web.enable-lifecycle除热加载外,还会启用 HTTP 退出接口。务必限制 9090 的访问范围。
5.2 备份并合并配置
cd /opt/monitoring-demo/prometheus-3.8.0.linux-amd64
cp -a prometheus.yml prometheus.yml.bak.$(date +%Y%m%d-%H%M%S)
vi prometheus.yml
只合并以下相关段落,保留原有四台机器的采集配置,不要整体覆盖。
# 已有 global 中的其他配置继续保留。
global:
# 默认采集间隔,已有 job 可以单独用 60s 覆盖。
scrape_interval: 30s
# 默认记录规则与告警规则计算间隔。
evaluation_interval: 60s
# 原有记录规则和新增告警规则共用同一个文件。
rule_files:
- 'rules.yml'
# 告警发送目标。
alerting:
alertmanagers:
- static_configs:
- targets:
- '192.0.2.20:9093'
不要重复定义 global、rule_files 或 alerting。如果原来加载了其他在用的规则文件,也应保留。
5.3 直接使用现有 rules.yml,不依赖 rules/ 目录
最终环境中的 rules/ 目录已经删除。 本次直接编辑现有 rules.yml,同时存放记录规则和告警规则,不新建目录,也不另外拆分规则文件。
在 Prometheus 目录确认现有文件:
ls -lh ./rules.yml
对应加载配置保持为:
# 只加载本次实际使用的规则文件,不依赖已删除的目录。
rule_files:
- 'rules.yml'
如果之前仅为本次操作增加过 rules/*.yml 等目录加载项,且这些文件已经停用或删除,应移除相应的无效加载项。其他仍在使用的规则文件路径则应保留,不要误删。
一个规则文件完全可以同时包含记录规则和告警规则。后续新增内容都追加到这个文件现有的 groups: 列表中,不添加第二个顶层 groups:。
如果其他已加载文件中仍有同名测试规则,应停用重复项,而不是让同名测试在多个位置持续触发。
5.4 规则检查与热加载
./promtool check rules ./rules.yml
./promtool check config ./prometheus.yml
全部成功后:
curl --noproxy '*' -sS -i --max-time 10 -X POST http://192.0.2.20:9090/-/reload
预期:
HTTP/1.1 200 OK
检查日志:
journalctl -u prometheus --since "5 minutes ago" --no-pager
也可以在 Prometheus 中查询:
prometheus_config_last_reload_successful
返回 1 表示最近一次配置加载成功。本文选用 HTTP 热加载,不需要再重复发送 HUP。
六、理解告警时间参数:不要混淆采集、计算和通知
| 参数 | 所属组件 | 含义 |
|---|---|---|
scrape_interval | Prometheus | 指标采集周期 |
evaluation_interval | Prometheus | 全局规则计算周期 |
规则组 interval | Prometheus | 覆盖本组的计算周期 |
告警规则 for | Prometheus | 条件持续满足后才能进入 Firing 的时长 |
group_wait | Alertmanager | 新告警组首次通知前的等待时间 |
group_interval | Alertmanager | 已通知组后续通知检查间隔 |
repeat_interval | Alertmanager | 持续未恢复告警的重复提醒间隔 |
resolve_timeout | Alertmanager | 主要用于未提供结束时间的告警,不是规则触发等待时间 |
本次使用:
route:
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
这些值不需要为了匹配 30s/60s 采集间隔而全部改为 60s。
特别注意:
for: 2m不保证故障发生两分钟后立即通知。group_interval: 5m不保证从删除测试规则或业务恢复的时刻起,五分钟内收到恢复消息。- 整体时延还包括采集、规则计算对齐、状态更新、告警传递和通知重试。
- 调整为
group_interval: 1m可以让组变化更快进入后续通知检查,但可能增加消息数量,也不是端到端一分钟送达保证。
七、正式主机、磁盘与网络规则
7.1 为什么不使用 job=“node”
本次四台机器的 job 名称不同,不能照搬示例中的 job="node"。
统一按四个 node_exporter 实例筛选:
instance=~"192[.]0[.]2[.](10|20|30|40):9100"
这里 [.] 匹配普通点号,包含监控服务器本机 192.0.2.20:9100,不包含 Prometheus 自身的 localhost:9090。
以后新增机器或修改目标地址,要同步更新筛选条件。规模变大后,应规划稳定的主机分类标签,而不是无限扩展 IP 正则。
7.2 完整规则参考
下面是本次规则的整理版本,可作为最终配置参考。已有环境应先比较再合并,保留其他在用规则,不要未经检查整体覆盖。
groups:
# ==================== 原有 CPU、内存预计算 ====================
- name: node_precompute
# 保留原有 30s,本组覆盖全局 60s。
interval: 30s
rules:
# CPU 百分数,沿用原来的 irate(...[3m]) 口径。
- record: job:cpu_usage:avg
expr: (1 - avg(irate(node_cpu_seconds_total{mode="idle"}[3m])) by (instance, job)) * 100
# 实际输出百分数,不是 0~1,虽然指标名带 ratio。
- record: job:mem_usage:ratio
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# ==================== 主机基础告警 ====================
- name: node_alerts
interval: 60s
rules:
- alert: NodeExporterDown
# 采集失败不一定是整台主机宕机。
expr: |
up{instance=~"192[.]0[.]2[.](10|20|30|40):9100"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "主机监控采集失败:{{ $labels.instance }}"
description: "业务:{{ $labels.app }};任务:{{ $labels.job }}。已连续 2 分钟无法采集,请检查主机、网络及 node_exporter。"
- alert: HostHighCpuUsage
# 复用现有百分数指标,阈值 85 而不是 0.85。
expr: |
job:cpu_usage:avg{instance=~"192[.]0[.]2[.](10|20|30|40):9100"} > 85
for: 5m
labels:
severity: warning
annotations:
summary: "CPU 使用率过高:{{ $labels.instance }}"
description: "CPU 使用率持续 5 分钟超过 85%,当前值 {{ printf \"%.1f\" $value }}%。"
- alert: HostHighMemoryUsage
# 现有 ratio 指标实际已乘以 100,阈值是 90。
expr: |
job:mem_usage:ratio{instance=~"192[.]0[.]2[.](10|20|30|40):9100"} > 90
for: 5m
labels:
severity: warning
annotations:
summary: "内存使用率过高:{{ $labels.instance }}"
description: "内存使用率持续 5 分钟超过 90%,当前值 {{ printf \"%.1f\" $value }}%。"
- alert: HostDiskSpaceLow
# 按普通进程可用空间计算,排除常见虚拟文件系统及只读挂载。
# 排除项应按实际文件系统调整,不要无条件套用。
expr: |
(
100 *
node_filesystem_avail_bytes{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
fstype!~"tmpfs|devtmpfs|overlay|squashfs|nsfs|ramfs",
mountpoint!~"/run($|/.*)|/var/lib/docker($|/.*)|/var/lib/containers($|/.*)"
}
/
node_filesystem_size_bytes{
instance=~"192[.]0[.]2[.](10|20|30|40):9100"
}
< 10
)
and on (job, instance, device, mountpoint)
(
node_filesystem_readonly{
instance=~"192[.]0[.]2[.](10|20|30|40):9100"
} == 0
)
for: 5m
labels:
severity: warning
annotations:
summary: "磁盘可用空间不足:{{ $labels.instance }}"
description: "挂载点 {{ $labels.mountpoint }},设备 {{ $labels.device }},可用空间持续 5 分钟低于 10%,当前剩余 {{ printf \"%.1f\" $value }}%。"
# ==================== 磁盘、网络预计算 ====================
- name: node_io_network_precompute
interval: 60s
rules:
# 每个设备读写总吞吐,单位:字节/秒,不跨设备相加。
- record: node_device:disk_throughput_bytes_per_second:rate5m
expr: |
rate(node_disk_read_bytes_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
+
rate(node_disk_written_bytes_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
# 每个设备读写总操作次数,单位:次/秒。
- record: node_device:disk_iops:rate5m
expr: |
rate(node_disk_reads_completed_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
+
rate(node_disk_writes_completed_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
# 按读写操作次数加权的平均耗时,单位:秒。
# 无操作时可能为 NaN,不代表采集故障。
- record: node_device:disk_latency_seconds:rate5m
expr: |
(
rate(node_disk_read_time_seconds_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
+
rate(node_disk_write_time_seconds_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
)
/
(
rate(node_disk_reads_completed_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
+
rate(node_disk_writes_completed_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
)
# 繁忙时间比例,0.85 约代表 85%。
# 高繁忙率不等同于 SSD、RAID 或虚拟磁盘已经性能饱和。
- record: node_device:disk_utilization:ratio5m
expr: |
rate(node_disk_io_time_seconds_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"loop.*|ram.*|sr.*"
}[5m])
# 网卡接收字节/秒;过滤条件需结合实际业务接口调整。
- record: node_device:network_receive_bytes_per_second:rate5m
expr: |
rate(node_network_receive_bytes_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"lo|veth.*|docker.*|br-.*|virbr.*|cni.*|flannel.*"
}[5m])
# 网卡发送字节/秒;不跨网卡相加,避免网桥、bond 等重复累计。
- record: node_device:network_transmit_bytes_per_second:rate5m
expr: |
rate(node_network_transmit_bytes_total{
instance=~"192[.]0[.]2[.](10|20|30|40):9100",
device!~"lo|veth.*|docker.*|br-.*|virbr.*|cni.*|flannel.*"
}[5m])
# ==================== 磁盘、网络告警 ====================
- name: node_io_network_alerts
interval: 60s
rules:
- alert: HostDiskThroughputHigh
# 示例基线:100 MiB/s,仅为高流量提醒,不等于设备饱和。
expr: node_device:disk_throughput_bytes_per_second:rate5m > 100 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘吞吐量较高:{{ $labels.instance }}"
description: "设备 {{ $labels.device }} 读写合计持续 10 分钟超过 100 MiB/s,当前 {{ printf \"%.0f\" $value }} 字节/秒。请确认是否为正常备份或批处理。"
- alert: HostDiskIopsHigh
# 示例阈值,必须按 HDD、SSD、虚拟盘实际能力调整。
expr: node_device:disk_iops:rate5m > 5000
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘 IOPS 较高:{{ $labels.instance }}"
description: "设备 {{ $labels.device }} 读写总 IOPS 持续 10 分钟超过 5000,当前 {{ printf \"%.1f\" $value }} 次/秒。请结合延迟判断。"
- alert: HostDiskLatencyHigh
# 0.05 秒 = 50 毫秒,并要求 IOPS 大于 1。
expr: |
(node_device:disk_latency_seconds:rate5m > 0.05)
and
(node_device:disk_iops:rate5m > 1)
for: 5m
labels:
severity: warning
annotations:
summary: "磁盘读写延迟过高:{{ $labels.instance }}"
description: "设备 {{ $labels.device }} 平均延迟持续 5 分钟超过 0.05 秒,当前 {{ printf \"%.4f\" $value }} 秒。请检查存储性能及宿主机负载。"
- alert: HostDiskUtilizationHigh
expr: node_device:disk_utilization:ratio5m > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘繁忙率过高:{{ $labels.instance }}"
description: "设备 {{ $labels.device }} 繁忙比例持续 10 分钟超过 0.85,当前 {{ printf \"%.3f\" $value }},1 表示 100%。请结合延迟和业务响应判断。"
- alert: HostNetworkReceiveHigh
# 示例:50 MiB/s,约 419 Mbit/s,不是自动计算的带宽利用率。
expr: node_device:network_receive_bytes_per_second:rate5m > 50 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "网络接收流量较高:{{ $labels.instance }}"
description: "网卡 {{ $labels.device }} 接收持续 10 分钟超过 50 MiB/s,当前 {{ printf \"%.0f\" $value }} 字节/秒。请确认是否为正常传输任务。"
- alert: HostNetworkTransmitHigh
# 收发分别判断,不能简单相加后视为全双工带宽利用率。
expr: node_device:network_transmit_bytes_per_second:rate5m > 50 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "网络发送流量较高:{{ $labels.instance }}"
description: "网卡 {{ $labels.device }} 发送持续 10 分钟超过 50 MiB/s,当前 {{ printf \"%.0f\" $value }} 字节/秒。请确认是否为正常传输任务。"
7.3 规则使用注意事项
-
阈值不是所有硬件的统一标准。 业务 B 服务器和 业务 A 服务器的高流量可能是正常业务。
-
dm-0、sda可能是同一条存储链路的不同层级,不要跨设备求和。本规则仍可能分别告警,确认映射后可进一步筛选。 -
用以下命令确认磁盘关系:
lsblk -o NAME,TYPE,SIZE,MOUNTPOINT -
网络过滤可能需要适配实际 bond、网桥或隧道接口。不要因为指标为空就认为没有网络负载。
-
CPU 预计算的
avg ... by (instance, job)会丢弃app,后面的中文模板通过实例地址映射补齐业务名称。 -
记录规则与告警规则是不同组,刚热加载后可能需要几个计算周期才能完整看到预计算结果。
-
本文使用
rate(...[5m])处理磁盘和网络,显示的是窗口平均速率;不保证与原有 Grafana 面板的窗口、单位完全相同。
7.4 加载后验证数据
up{instance=~"192[.]0[.]2[.](10|20|30|40):9100"}
job:cpu_usage:avg
job:mem_usage:ratio
node_device:disk_throughput_bytes_per_second:rate5m
node_device:disk_iops:rate5m
node_device:disk_latency_seconds:rate5m
node_device:disk_utilization:ratio5m
node_device:network_receive_bytes_per_second:rate5m
node_device:network_transmit_bytes_per_second:rate5m
规则语法正确不代表数据一定存在。必须检查实例、设备、网卡是否符合预期。
八、部署 prometheus-webhook-dingtalk 2.1.0
8.1 确认目录
cd /opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64
ls -lh
chmod +x ./prometheus-webhook-dingtalk
./prometheus-webhook-dingtalk --version
./prometheus-webhook-dingtalk --help
本次目录最初只有示例配置,没有 config.yml,属于正常情况。
8.2 机器人没有 SEC 密钥怎么办
本次使用钉钉开发者平台配置的机器人,实际有可发送消息的 Webhook,没有 SEC 加签密钥。最终通过该 Webhook 成功完成发送。
但不能据此认为所有开发者平台机器人都能直接使用此适配器。需要核对:
- 地址确实是发消息接口,而不是事件回调地址。
- 是否支持
https://oapi.dingtalk.com/robot/send?access_token=...这一类机器人消息格式。 - 如果是有时效的会话 Webhook,需考虑过期,不能直接当作永久通知地址。
- 机器人是否仍在对应群内,应用范围和安全限制是否满足。
没有加签时不填写 secret,不要拿 AppSecret 或 access_token 代替 SEC 密钥。
8.3 创建初始配置
vi config.yml
# ==================== 钉钉机器人目标 ====================
targets:
# 目标名称 ops 对应适配器路径 /dingtalk/ops/send。
ops:
# 将占位符替换为真实 Webhook,不要公开。
url: 'https://oapi.dingtalk.com/robot/send?access_token=REPLACE_WITH_YOUR_TOKEN'
# 本次没有加签,不配置 secret。
# 如果你的机器人启用加签,再按实际配置增加对应 secret。
chmod 600 ./config.yml
8.4 手动启动并检查
./prometheus-webhook-dingtalk --config.file=./config.yml --web.listen-address=127.0.0.1:8060
另开终端:
ss -lntp | grep ':8060'
预期监听 127.0.0.1:8060。这个地址只供同机 Alertmanager 调用,不需要对外开放。
启动正常不等于钉钉已经收到消息,后续仍需通过完整告警链路验证。
8.5 配置 systemd
先停止前台适配器,再创建服务:
vi /etc/systemd/system/prometheus-webhook-dingtalk.service
[Unit]
Description=Prometheus Webhook DingTalk
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64
ExecStart=/opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64/prometheus-webhook-dingtalk \
--config.file=/opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64/config.yml \
--web.listen-address=127.0.0.1:8060
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now prometheus-webhook-dingtalk
systemctl status prometheus-webhook-dingtalk --no-pager -l
journalctl -u prometheus-webhook-dingtalk -n 100 --no-pager
如果是在修改已有且正在运行的适配器服务定义,执行 daemon-reload 后还需执行 systemctl restart prometheus-webhook-dingtalk,不能仅靠 enable --now 应用新的启动参数。
九、Alertmanager 正式切换到钉钉接收器
9.1 修改配置
cd /opt/monitoring-demo/alertmanager-0.29.0.linux-amd64
cp -a alertmanager.yml alertmanager.yml.bak.$(date +%Y%m%d-%H%M%S)
vi alertmanager.yml
本次最终的基础配置为:
# ==================== 全局配置 ====================
global:
resolve_timeout: 5m
# ==================== 告警路由 ====================
route:
# 不再使用空接收器 local-only。
receiver: dingtalk
group_by:
- alertname
- instance
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
# ==================== 钉钉接收器 ====================
receivers:
- name: dingtalk
webhook_configs:
# 这是适配器接口,不是钉钉官方 Webhook。
# ops 必须与适配器的 targets.ops 一致。
- url: 'http://127.0.0.1:8060/dingtalk/ops/send'
# 必须启用,恢复时才向适配器发送通知。
send_resolved: true
# 当前不增加通用抑制规则,避免误抑制。
如果已有其他路由、接收器、静默规划或抑制规则,应比较后合并,不要直接覆盖。
9.2 检查并热加载:本次最关键的一步
./amtool check-config ./alertmanager.yml
检查成功后:
curl --noproxy '*' -sS -i --max-time 10 -X POST http://192.0.2.20:9093/-/reload
注意是 9093,不是 Prometheus 的 9090。
Alertmanager 的重载接口不需要添加 Prometheus 的 --web.enable-lifecycle 参数。
随后打开 Alertmanager 的 Status → Config,确认实际生效内容为:
route:
receiver: dingtalk
并能看到 Webhook 地址和 send_resolved: true。
9.3 本次为什么“文件改了,钉钉却没消息”
实际排查时,磁盘上的配置已经改成 dingtalk,但网页 Status 中仍是:
route:
receiver: local-only
receivers:
- name: local-only
说明运行中的 Alertmanager 还在使用旧配置。
执行 9093/-/reload 后,钉钉立即收到了后续测试告警。这个过程确认了:
修改文件不等于运行配置生效。检查配置语法也不等于已经重新加载。最终应以 Status 中的实际运行配置为准。
如果热加载后仍没有变化,继续检查:
ps -ww -C alertmanager -o pid,args
systemctl cat alertmanager
重点确认 --config.file 和 WorkingDirectory。相对路径指向的是进程工作目录,不一定是当前 SSH 终端目录。
十、测试告警与恢复通知
本节记录本次已经执行过的链路验证。正式环境完成验证后应删除测试组。后续仅更换中文模板时,本次没有再追加模板测试规则。
10.1 在现有规则末尾追加测试组
在现有 groups: 下追加,不要再写第二个 groups::
# 临时测试组,验证结束后删除。
- name: dingtalk_pipeline_test
interval: 60s
rules:
- alert: DingTalkPipelineTest
# 始终返回一个样本,条件一直存在。
expr: vector(1)
for: 1m
labels:
severity: info
instance: "192.0.2.20"
app: "监控告警链路测试"
annotations:
summary: "钉钉告警链路测试"
description: "这是一条人工测试告警,用于验证 Prometheus、Alertmanager、钉钉适配器和群机器人的完整链路。"
检查并加载 Prometheus:
./promtool check rules ./rules.yml
./promtool check config ./prometheus.yml
curl --noproxy '*' -sS -i --max-time 10 -X POST http://192.0.2.20:9090/-/reload
10.2 vector(1) 到底是什么
vector(1)
把标量 1 转为包含一个无标签样本的即时向量,查询结果类似:
{} 1
告警表达式返回样本,就会产生对应的活动告警条件,不是只有数值等于 1 才会告警。
因此:
vector(0)
仍会返回一个样本,也会触发告警,不能用来关闭测试。
正式表达式:
up == 0
默认会过滤掉不等于 0 的样本,所以只保留采集失败的目标。不要无意中改成带 bool 的比较后直接用于同一用途的告警判断。
10.3 验证顺序
- Prometheus Alerts 页面:
Pending → Firing。 - Alertmanager 页面:出现相同告警,接收器为
dingtalk。 - 钉钉群:实际收到告警消息。
仅有接口返回或进程启动日志不能替代第三步。
10.4 删除测试规则并等待恢复
收到告警后,删除整个 dingtalk_pipeline_test 组,保留正式规则。检查并再次热加载 Prometheus。
随后,本次确实收到了恢复消息,但等待时间超过了五分钟。
不能仅凭这一现象确定某个组件异常,也不能把全部延迟都归因于某个参数。若需要精确定位,应比较:
- 删除规则及成功热加载时间。
- Prometheus 活动告警消失时间。
- Alertmanager 收到恢复状态及通知尝试时间。
- 适配器调用钉钉的时间和返回结果。
10.5 未收到恢复通知的排查方法
Prometheus 中查询:
ALERTS{alertname="DingTalkPipelineTest"}
- 仍有
alertstate="firing":告警尚未结束,检查测试规则和热加载。 - 为空:继续检查 Alertmanager 的生效配置和发送日志。
Alertmanager Status 中确认:
send_resolved: true
查看日志:
journalctl -u alertmanager --since "45 minutes ago" --no-pager
journalctl -u prometheus-webhook-dingtalk --since "45 minutes ago" --no-pager
如果 Alertmanager 是前台启动,应查看前台终端日志,而不是只看 systemd 日志。
不要只搜索字面上的 [RESOLVED],模板标题可能还包含数量或其他格式。
十一、配置正式中文钉钉模板
11.1 设计目标
默认模板可以工作,但英文标题和标签堆叠不够直观。正式模板需要:
- 明确区分告警中和已恢复。
- 显示中文等级和中文告警名称。
- 统一四台机器的业务名称。
- 展示磁盘或网卡设备、挂载点。
- 保留摘要和排障说明。
- 不把恢复说明中的旧数值当成恢复时实时值。
- 为缺少
app标签的 CPU 告警提供业务名称映射。
本次仅修改适配器模板及配置,不改变已有告警表达式。
11.2 创建模板
cd /opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64
cp -a config.yml config.yml.bak.$(date +%Y%m%d-%H%M%S)
vi chinese.tmpl
如果同名模板已存在,应先查看、备份,再编辑。
完整模板:
{{/* ==================== 业务名称 ==================== */}}
{{/* 四台已知机器优先统一命名,其他目标使用 app 标签。 */}}
{{ define "cn.business" -}}
{{- if or (eq .Labels.instance "192.0.2.10:9100") (eq .Labels.instance "192.0.2.10") -}}
业务服务 A
{{- else if or (eq .Labels.instance "192.0.2.20:9100") (eq .Labels.instance "192.0.2.20") (eq .Labels.instance "localhost:9090") -}}
监控服务
{{- else if or (eq .Labels.instance "192.0.2.30:9100") (eq .Labels.instance "192.0.2.30") -}}
业务服务 B
{{- else if or (eq .Labels.instance "192.0.2.40:9100") (eq .Labels.instance "192.0.2.40") -}}
业务服务 C
{{- else if .Labels.app -}}
{{ .Labels.app }}
{{- else -}}
未配置业务名称
{{- end -}}
{{- end }}
{{/* ==================== 中文等级 ==================== */}}
{{ define "cn.severity" -}}
{{- if eq .Labels.severity "critical" -}}
严重
{{- else if eq .Labels.severity "warning" -}}
警告
{{- else if eq .Labels.severity "info" -}}
提示
{{- else if .Labels.severity -}}
{{ .Labels.severity }}
{{- else -}}
未分级
{{- end -}}
{{- end }}
{{/* ==================== 中文告警名称 ==================== */}}
{{/* 未列出的新告警回退到原始 alertname。 */}}
{{ define "cn.alertname" -}}
{{- if eq .Labels.alertname "NodeExporterDown" -}}
主机监控采集失败
{{- else if eq .Labels.alertname "HostHighCpuUsage" -}}
CPU 使用率过高
{{- else if eq .Labels.alertname "HostHighMemoryUsage" -}}
内存使用率过高
{{- else if eq .Labels.alertname "HostDiskSpaceLow" -}}
磁盘可用空间不足
{{- else if eq .Labels.alertname "HostDiskThroughputHigh" -}}
磁盘吞吐量较高
{{- else if eq .Labels.alertname "HostDiskIopsHigh" -}}
磁盘 IOPS 较高
{{- else if eq .Labels.alertname "HostDiskLatencyHigh" -}}
磁盘读写延迟过高
{{- else if eq .Labels.alertname "HostDiskUtilizationHigh" -}}
磁盘繁忙率过高
{{- else if eq .Labels.alertname "HostNetworkReceiveHigh" -}}
网络接收流量较高
{{- else if eq .Labels.alertname "HostNetworkTransmitHigh" -}}
网络发送流量较高
{{- else -}}
{{ .Labels.alertname }}
{{- end -}}
{{- end }}
{{/* ==================== 通知标题 ==================== */}}
{{ define "cn.title" -}}
{{- if eq .Status "resolved" -}}
【监控恢复】恢复 {{ len .Alerts.Resolved }} 项
{{- else -}}
【监控告警】异常 {{ len .Alerts.Firing }} 项,恢复 {{ len .Alerts.Resolved }} 项
{{- end -}}
{{- end }}
{{/* ==================== 通知正文 ==================== */}}
{{ define "cn.text" }}
### {{ template "cn.title" . }}
{{ range .Alerts }}
---
{{ if eq .Status "resolved" }}
**状态:** ✅ 已恢复
{{ else }}
**状态:** 🚨 告警中
{{ end }}
**等级:** {{ template "cn.severity" . }}
**业务:** {{ template "cn.business" . }}
**实例:** {{ .Labels.instance }}
**告警:** {{ template "cn.alertname" . }}
{{ if .Labels.device }}
**设备:** {{ .Labels.device }}
{{ end }}
{{ if .Labels.mountpoint }}
**挂载点:** {{ .Labels.mountpoint }}
{{ end }}
{{ if .Annotations.summary }}
**摘要:** {{ .Annotations.summary }}
{{ end }}
{{ if .Annotations.description }}
{{ if eq .Status "resolved" }}
**原告警说明:** {{ .Annotations.description }}
> 说明中的数值不一定是恢复时的实时值,请以监控页面为准。
{{ else }}
**异常说明:** {{ .Annotations.description }}
{{ end }}
{{ end }}
{{/* 保留时间的原始时区偏移,不将 UTC 误标为北京时间。 */}}
**开始时间:** {{ .StartsAt.Format "2006-01-02 15:04:05 Z07:00" }}
{{ if eq .Status "resolved" }}
**恢复时间:** {{ .EndsAt.Format "2006-01-02 15:04:05 Z07:00" }}
{{ end }}
**规则标识:** {{ .Labels.alertname }}
{{ if .Labels.job }}
**采集任务:** {{ .Labels.job }}
{{ end }}
{{ end }}
---
**监控入口(需连接内网):**
[Grafana 仪表盘](http://192.0.2.20:3000) | [Prometheus 告警](http://192.0.2.20:9090/alerts) | [Alertmanager](http://192.0.2.20:9093)
{{ end }}
说明:
Z或+00:00表示 UTC,+08:00表示北京时间偏移。这里使用的 Go 格式Z07:00在 UTC 时通常输出Z,其他时区输出数值偏移;模板没有强制转换时区。- Go 时间格式必须使用参考时间
2006-01-02 15:04:05,不是 Java 风格的yyyy-MM-dd。 - 同一实例以后如果承载多个业务,应改成更准确的业务标签映射,不能一直把 IP 等同于单个服务。
- 恢复消息的描述可能沿用告警时期的值,所以标注为“原告警说明”。
- 手机不在内网时无法访问底部内网链接,属于网络范围限制,不是消息模板错误。
11.3 适配器正式配置
编辑:
vi config.yml
完整结构如下。保留真实 Webhook,不要将占位符覆盖到生产配置中:
# ==================== 钉钉机器人配置 ====================
targets:
ops:
# 替换为真实地址,本文不记录任何实际 Token。
url: 'https://oapi.dingtalk.com/robot/send?access_token=REPLACE_WITH_YOUR_TOKEN'
# 本次机器人没有加签,不增加 secret。
# message 与 url 同级,均缩进 4 个空格。
message:
title: '{{ template "cn.title" . }}'
text: '{{ template "cn.text" . }}'
# templates 与 targets 均为顶层配置,先后顺序不影响解析。
templates:
- '/opt/monitoring-demo/prometheus-webhook-dingtalk-2.1.0.linux-amd64/chinese.tmpl'
{{template ...}} 和 {{ template ... }} 都是可用写法,后者更易读。URL 不加引号也可能可以解析,本次统一使用单引号便于维护。
11.4 应用模板
chmod 600 ./config.yml
ls -lh ./chinese.tmpl
grep -nE 'define "cn\.(title|text)"' ./chinese.tmpl
systemctl restart prometheus-webhook-dingtalk
systemctl status prometheus-webhook-dingtalk --no-pager -l
journalctl -u prometheus-webhook-dingtalk --since "5 minutes ago" --no-pager
只需重启适配器,不需要重新加载 Prometheus 或 Alertmanager。
注意两个边界:
- 服务启动正常,说明配置和模板加载阶段没有报错,不等于实际通知渲染已经验证。
- 重启适配器不会让 Alertmanager 立即重发已有告警,要等待后续正式通知、恢复或重复提醒。
本次在切换正式中文模板后,没有再添加临时模板测试。已完成配置、启动检查,实际排版效果等待下一次正式通知确认。
十二、本次实际踩坑记录
坑 1:备份命令中 date 后少空格
错误:
cp alertmanager.yml alertmanager.yml.bak.$(date+%Y%m%d-%H%M%S)
报错:
-bash: date+%Y%m%d-%H%M%S:未找到命令
正确:
cp -a alertmanager.yml alertmanager.yml.bak.$(date +%Y%m%d-%H%M%S)
命令替换失败后,外层 cp 可能仍执行,生成一个名为 alertmanager.yml.bak. 的文件,末尾带点。
安全处理方法:
# 先确认当前目录及精确文件名。
pwd
ls -lh -- ./alertmanager.yml ./alertmanager.yml.bak.
# 只删除已经确认的误备份文件,交互确认。
rm -i -- ./alertmanager.yml.bak.
# 确认原配置还在。
ls -lh -- ./alertmanager.yml
不要使用 rm -rf,不要用通配符批量删备份。
坑 2:命令行参数等号后加了空格
错误:
./alertmanager --config.file= alertmanager.yml --storage.path= data
报错:
alertmanager: error: unexpected alertmanager.yml, try --help
正确:
./alertmanager --config.file=./alertmanager.yml --storage.path=./data --web.listen-address=192.0.2.20:9093 --cluster.listen-address=
也可以用这个命令支持的空格分隔形式:
./alertmanager --config.file ./alertmanager.yml --storage.path ./data --web.listen-address 192.0.2.20:9093 --cluster.listen-address=
关键是不要写成 --参数= 值,把空值和后面的文件名分开了。
坑 3:误以为同目录就不需要 ./
对于配置路径:
--config.file=alertmanager.yml
--config.file=./alertmanager.yml
都表示相对于进程当前工作目录的配置文件。
但是运行程序时:
./alertmanager
明确表示执行当前目录的程序。直接输入 alertmanager 时,Shell 通常只到 PATH 中查找,不默认查当前目录。
另外,从程序所在目录以外执行绝对程序路径,也不意味着相对配置自动按程序目录解析。工作目录和可执行文件所在目录是两回事。
坑 4:curl -I 返回 405,被误认为服务异常
本次检查首页使用:
curl -I http://192.0.2.20:9093/
返回:
HTTP/1.1 405 Method Not Allowed
Allow: GET, OPTIONS
原因是 -I 发送 HEAD 请求,而该接口不接受 HEAD,不代表 Alertmanager 故障,也不是防火墙拦截。
改为 GET:
curl --noproxy '*' -sS -o /dev/null -w 'HTTP状态码:%{http_code}\n' --connect-timeout 3 --max-time 5 http://192.0.2.20:9093/
或直接用就绪接口:
curl --noproxy '*' -fsS --max-time 5 http://192.0.2.20:9093/-/ready
坑 5:本机正常,浏览器一直转圈
最终原因与 firewalld 未放行 9093 有关。排查顺序应该是:
进程是否存在 → 端口是否监听 → 本机 GET 是否成功 → 防火墙/网络 → 浏览器代理
管理电脑无法使用 Test-NetConnection 也不影响继续定位,不需要卡在这条 Windows 命令上。
坑 6:规则目录和规则文件混淆
最初排查时,目录中曾同时存在 rules/ 目录和 rules.yml 文件;当时执行 cat rules 得到 Is a directory,只是把目录当作文件读取,并不是 Prometheus 故障。
后来已经删除不使用的 rules/ 目录,最终环境只使用 rules.yml,不需要重新创建目录。 保留这段是记录历史踩坑,不是描述当前仍有该目录。
现有文件中可以同时配置记录规则和告警规则。同一个文件只保留一个顶层 groups: 列表,不应为了追加规则再粘贴第二个同名顶层键。
坑 7:以为全局 evaluation_interval 就决定所有规则周期
本次已有:
- name: node_precompute
interval: 30s
它覆盖全局 60s。规则计算更频繁不会自动提高目标采集频率。
坑 8:指标名字叫 ratio,实际却是百分数
现有内存表达式已经乘了 100。为了不影响 Grafana,本次保留名称及表达式,告警使用 > 90,不是 > 0.9。
不能只看指标后缀判断单位,应读表达式、检查查询值。
坑 9:直接套用 job=“node” 导致没有数据
实际四台机器 job 不同。语法正确但选择器不匹配时,可能返回空结果,也不会按预期触发告警。
应先查询 up,核对真实标签,再写规则。
坑 10:把本机的两个采集接口当成两台机器
本机既有 localhost:9090,也有 192.0.2.20:9100。监控主机指标应使用后者;前者用于 Prometheus 自身。
坑 11:YAML 缩进使用了 Tab
实际报错:
Checking './alertmanager.yml' FAILED: yaml: line 40: found character that cannot start any token
复制到聊天后的内容看起来正确,但实际文件中的第 40 行附近存在 Tab。
排查:
nl -ba ./alertmanager.yml | sed -n '30,45p'
sed -n '30,45p' ./alertmanager.yml | cat -T
^I 表示 Tab。
在 vi 中显示隐藏字符:
:set list
修复时优先按层级重新输入有问题的行。也可以先备份,再按已知缩进宽度转换:
:set expandtab
:set tabstop=2 shiftwidth=2
:retab
转换后仍必须检查缩进层级。 原文件若混用了 Tab 和不同空格宽度,机械转换可能改变原本的视觉对齐,不能只执行 :retab 就认为正确。
接收器层级应为:
receivers:
- name: dingtalk
webhook_configs:
- url: 'http://127.0.0.1:8060/dingtalk/ops/send'
send_resolved: true
每次编辑完执行 amtool check-config,检查通过后才应用。
坑 12:编辑器的波浪线和状态信息不是配置内容
vi 底部的:
~
"alertmanager.yml" 51L, 1948B
通常是编辑器显示,不是文件内容。不要把它们复制进实际配置;如果确实误写进文件,需要删除。
坑 13:以为检查通过就已经生效
amtool check-config 只是验证磁盘配置,不能替代热加载。
本次 Alertmanager 页面长期显示 local-only,执行 9093 的 /-/reload 后才成功收到钉钉消息。
这也是整个排查中最关键的经验:最终看运行配置,不只看编辑器。
坑 14:出现 group is not exist,就直接断定机器人失效
过程中出现过 group is not exist 的反馈。但当时没有保留足够的完整接口错误码及对应日志,无法严格确认它来自哪次请求、哪种 Webhook,不能把它写成“本次已证实的单一根因”。
可以确认的是:当时 Alertmanager 实际使用旧的 local-only;热加载后,现有机器人成功收到了测试消息。
正确做法是分层确认:
- Alertmanager 是否实际调用了 dingtalk 接收器。
- 适配器是否收到请求。
- 钉钉接口返回的业务错误码是什么。
- Webhook 是否指向有效群/会话,机器人和权限是否正常。
不要仅凭一句错误文本就删除重建机器人,也不要用没有验证过的猜测替代排查记录。
坑 15:恢复通知没有恰好五分钟到达
本次最终收到了恢复消息,等待超过五分钟。group_interval 不是从故障恢复时刻开始的送达 SLA,还存在前后链路耗时。
如果长期没有恢复,先确认测试告警是否真正结束、生效配置是否 send_resolved: true,再检查发送日志,而不是反复修改正式规则。
坑 16:公开了完整 Webhook
排查中容易直接粘贴完整配置,导致机器人 access_token 暴露。
打码只能保护之后的展示,不能撤销已经泄露的旧 Token。 应按钉钉支持的方式重置或更换 Webhook,使旧地址失效,再更新服务器配置。
本文只保留占位符。发布博客时,终端、群通知、浏览器截图中的 Token、群信息、个人信息和水印都需要检查。
十三、为什么本次先不启用通用抑制规则
最初示例中出现过:
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
这并不是适用于所有主机的通用规则:
- 需要真实存在 critical 和 warning 两级告警。
equal中的标签需要符合实际设计。- 源和目标都缺少某标签时,该标签会被视为相等。
- node_exporter 设备常见标签为
device,不一定叫dev。 - 如果两级告警的 alertname 分别叫不同名字,而
equal要求 alertname 相同,则不会按预期匹配。
以后确实需要“相同告警、相同实例的 critical 抑制 warning”时,可使用匹配器写法,并按资源增加标签:
# 仅为设计示例,本次基础配置不启用。
inhibit_rules:
- source_matchers:
- 'severity="critical"'
target_matchers:
- 'severity="warning"'
equal:
- alertname
- instance
对磁盘还应考虑 mountpoint 或 device,避免一个分区严重异常抑制其他分区的独立问题。
不要让一台机器任意 critical 告警,自动抑制该机器所有 warning。
十四、最终验收与日常操作速查
14.1 服务状态与开机自启
systemctl is-active prometheus alertmanager prometheus-webhook-dingtalk
systemctl is-enabled prometheus alertmanager prometheus-webhook-dingtalk
本次实际检查:三个服务均为 active,三个服务均为 enabled。
enabled 表示已配置开机自启,不等于已经完成一次真实重启后的验收。本次没有为了测试而立即重启服务器,可在维护窗口验证。
14.2 最终验收清单
| 项目 | 本次情况 |
|---|---|
| 四台 node_exporter 指标 | 已采集 |
| Prometheus 记录规则与告警规则 | 已加载并检查数据 |
| Prometheus → Alertmanager | 已通过测试告警验证 |
| Alertmanager → 钉钉 | 已收到告警消息 |
| 恢复通知 | 已收到,等待时间超过五分钟 |
| 三个服务运行 | 均 active |
| 三个服务开机自启 | 均 enabled |
| 正式中文模板配置与启动检查 | 已完成 |
| 中文模板实际通知排版 | 未额外造告警测试,等待后续正式通知确认 |
| 旧 Webhook 泄露后的处理 | 发布前应自行确认已更换且旧地址失效 |
14.3 修改什么,加载哪个组件
| 修改内容 | 检查 | 应用方式 |
|---|---|---|
| Prometheus 配置、记录/告警规则 | promtool check config、promtool check rules | POST 9090 的 /-/reload |
| Alertmanager 路由、接收器、通知间隔 | amtool check-config | POST 9093 的 /-/reload |
| 适配器 Webhook、模板配置、模板文件 | 检查内容与启动/发送日志 | 重启适配器服务 |
| systemd 服务文件 | 检查服务定义 | systemctl daemon-reload,再重启对应服务 |
14.4 常用日志
journalctl -u prometheus --since "15 minutes ago" --no-pager
journalctl -u alertmanager --since "15 minutes ago" --no-pager
journalctl -u prometheus-webhook-dingtalk --since "15 minutes ago" --no-pager
持续观察:
journalctl -u prometheus-webhook-dingtalk -f
14.5 常用监听检查
ss -lntp | grep -E ':(9090|9093|8060)\b'
预期:
- Prometheus:9090,按既有访问策略。
- Alertmanager:192.0.2.20:9093。
- 钉钉适配器:127.0.0.1:8060。
14.6 回退原则
- 修改前备份,记录具体备份文件名。
- 规则或配置检查失败,不加载。
- 中文模板有问题,只恢复适配器配置并重启适配器,不必回退整套监控。
- 不用通配符批量删除备份。
- 不反复启动新进程覆盖问题,应先确认实际进程、工作目录和配置路径。
十五、后续完善方向与已知边界
15.1 优先提高告警质量
建议观察 7~14 天业务基线,覆盖备份和 业务 A 高峰,再调整阈值。不要把“高流量”直接等同于“故障”。
15.2 增加有实际价值的告警
- inode 不足。
- 磁盘空间预计耗尽。
- 业务文件系统意外只读。
- 业务 A 所依赖服务和启动文件可用性。
- 业务 C 实际服务及通信能力。
- 最近一次备份成功时间、任务退出结果及恢复演练。
- Prometheus 规则计算失败、配置加载失败和通知失败。
可按需要使用 Blackbox Exporter、systemd collector 和 textfile collector。TCP 可连接不等于业务完整可用,也不能替代 UDP 服务验证。
15.3 监控服务器单点风险
Prometheus、Alertmanager、适配器同在 192.0.2.20:
该服务器整机宕机 → 告警计算和通知链路一起停止
不能靠本机对自身 localhost:9090 的采集,可靠发现本机整体宕机并发送通知。
后续应在独立机器或外部系统增加探测,且失败通知路径不能再依赖这台服务器。必要时再规划高可用。
15.4 安全与备份
- 9090、9093、3000 限制可信管理来源。
- 9100 尽量只允许 Prometheus 访问。
- 8060 保持回环监听。
- 后续在维护窗口评估专用账户和最小权限。
- 保护 Webhook、备份配置和自定义模板。
- 规划 Prometheus 数据保留时间、容量和可靠备份方法,不把随意复制运行中数据目录当作已经验证可恢复的备份。
- 保持系统时间同步,避免日志和通知时间对不齐。
十六、总结
这次接入的核心,并不是把几个配置文件粘贴进去,而是逐层验证:
程序启动 → 端口监听 → 本机 HTTP → 外部访问
↓
规则正确 → 规则实际加载 → 告警进入 Firing
↓
Alertmanager 实际路由 → 适配器请求 → 钉钉实际收信
↓
告警结束 → 恢复状态传递 → 钉钉恢复通知
最值得记住的经验有四条:
- 文件修改、语法检查、运行生效是三件不同的事。
- 同一台机器可以有多个采集目标,主机指标和服务自身指标不能混淆。
- 告警时间不是单一参数,恢复通知也不是固定五分钟内送达。
- 配置、日志、截图发布前必须脱敏,凭据泄露后要更换,而不只是打码。
至此,已有 Prometheus 环境完成了 Alertmanager、钉钉告警与恢复通知接入,并配置了可维护的中文通知模板。告警及恢复链路已经实际验证;正式中文模板的实际通知排版,按本次操作范围留待后续正式通知确认。
参考入口
以下为后续核对配置、版本行为和升级说明的官方/项目入口:
- Prometheus 告警规则:https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Prometheus 配置:https://prometheus.io/docs/prometheus/latest/configuration/configuration/
- Alertmanager 配置:https://prometheus.io/docs/alerting/latest/configuration/
- Alertmanager 管理 API:https://prometheus.io/docs/alerting/latest/management_api/
- 钉钉适配器项目:https://github.com/timonwong/prometheus-webhook-dingtalk
- 钉钉适配器 v2.1.0:https://github.com/timonwong/prometheus-webhook-dingtalk/tree/v2.1.0
本文正文已完成环境信息脱敏。发布 CSDN 前,可将 Markdown 源文粘贴到编辑器,预览目录、代码块和表格;如果另外补充截图,仍须单独遮盖真实 IP、主机名、目录、业务名称、Token、群成员、个人水印和组织信息。本文未嵌入原始截图。
建议标签: Prometheus、Alertmanager、钉钉、openEuler、Linux、运维监控。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)