openEuler 22.03 实战:Prometheus 接入 Alertmanager 与钉钉机器人,附中文模板和完整踩坑记录

本文记录一次从已有 Prometheus、node_exporter、Grafana 环境出发,接入 Alertmanager、钉钉机器人,并完善中文通知模板的实际过程。

不重复安装已经正常运行的 Prometheus,不迁移现有部署目录,不重新创建规则目录,所有操作采用 root。文中包含正式配置、验证方法、日常操作,以及本次实际遇到的错误和排查过程。

本文已脱敏:真实 IP、主机名、部署根目录、业务名称及网卡标识已移除或替换为统一示例;Webhook、Token、密钥仅保留占位符。下文命令、配置、规则正则、中文模板和访问链接均已同步替换,不披露脱敏前后的对应关系。root 部署是本次环境的选择,并非最小权限生产部署的推荐方案。

文章目录

发布说明:本文已进行环境信息脱敏

为避免公开真实网络拓扑和环境标识,本文进行了以下处理。所有示例值只用于说明结构,不是原环境的真实地址或名称,也不列出原值映射表。

信息类别本文公开写法处理说明
服务器 IP192.0.2.10、192.0.2.20、192.0.2.30、192.0.2.40已脱敏
管理电脑 IP192.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 虚拟机运行监控组件
Prometheus3.8.0指标采集、记录规则、告警规则计算
node_exporter1.10.2,二进制解压部署四台机器提供主机指标,监控服务器本机也已安装
Grafana5.4.2,通过 RPM 安装已有可视化环境,不作为解压目录放在部署根目录下,也不在本次重新安装
Alertmanager0.29.0告警分组、去重、静默、抑制及通知路由
prometheus-webhook-dingtalk2.1.0转换 Alertmanager 通知并调用钉钉机器人接口

Grafana 5.4.2 版本较旧。本文沿用现有环境,后续应单独规划备份、兼容性验证和升级,不建议在本次告警接入中顺带进行未经验证的大版本升级。

1.2 四台机器与五个采集目标

示例主机名(已脱敏)示例 IP(已脱敏)业务名称(已泛化)node_exporter 采集地址
app-node-01192.0.2.10业务服务 A192.0.2.10:9100
monitor-node192.0.2.20监控服务192.0.2.20:9100
app-node-02192.0.2.30业务服务 B192.0.2.30:9100
app-node-03192.0.2.40业务服务 C192.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_intervalPrometheus指标采集周期
evaluation_intervalPrometheus全局规则计算周期
规则组 intervalPrometheus覆盖本组的计算周期
告警规则 forPrometheus条件持续满足后才能进入 Firing 的时长
group_waitAlertmanager新告警组首次通知前的等待时间
group_intervalAlertmanager已通知组后续通知检查间隔
repeat_intervalAlertmanager持续未恢复告警的重复提醒间隔
resolve_timeoutAlertmanager主要用于未提供结束时间的告警,不是规则触发等待时间

本次使用:

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 规则使用注意事项

  1. 阈值不是所有硬件的统一标准。 业务 B 服务器和 业务 A 服务器的高流量可能是正常业务。

  2. dm-0、sda 可能是同一条存储链路的不同层级,不要跨设备求和。本规则仍可能分别告警,确认映射后可进一步筛选。

  3. 用以下命令确认磁盘关系:

    lsblk -o NAME,TYPE,SIZE,MOUNTPOINT
    
  4. 网络过滤可能需要适配实际 bond、网桥或隧道接口。不要因为指标为空就认为没有网络负载。

  5. CPU 预计算的 avg ... by (instance, job) 会丢弃 app,后面的中文模板通过实例地址映射补齐业务名称。

  6. 记录规则与告警规则是不同组,刚热加载后可能需要几个计算周期才能完整看到预计算结果。

  7. 本文使用 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 验证顺序

  1. Prometheus Alerts 页面:Pending → Firing。
  2. Alertmanager 页面:出现相同告警,接收器为 dingtalk。
  3. 钉钉群:实际收到告警消息。

仅有接口返回或进程启动日志不能替代第三步。

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。

注意两个边界:

  1. 服务启动正常,说明配置和模板加载阶段没有报错,不等于实际通知渲染已经验证。
  2. 重启适配器不会让 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;热加载后,现有机器人成功收到了测试消息。

正确做法是分层确认:

  1. Alertmanager 是否实际调用了 dingtalk 接收器。
  2. 适配器是否收到请求。
  3. 钉钉接口返回的业务错误码是什么。
  4. 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 rulesPOST 9090 的 /-/reload
Alertmanager 路由、接收器、通知间隔amtool check-configPOST 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 实际路由 → 适配器请求 → 钉钉实际收信
    ↓
告警结束 → 恢复状态传递 → 钉钉恢复通知

最值得记住的经验有四条:

  1. 文件修改、语法检查、运行生效是三件不同的事。
  2. 同一台机器可以有多个采集目标,主机指标和服务自身指标不能混淆。
  3. 告警时间不是单一参数,恢复通知也不是固定五分钟内送达。
  4. 配置、日志、截图发布前必须脱敏,凭据泄露后要更换,而不只是打码。

至此,已有 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、运维监控。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐