
凌晨 3 点,MySQL 主库宕机,告警触发——但 40 分钟后运维才收到通知。不是因为告警没发出来,而是推送通道自己先挂了。
2026 年 6 月的一个周三凌晨,MySQL 主库宕机。
Alertmanager 在 03:15:00 精准触发了 P0 告警,通过 Webhook 推送到 Hermes Agent。然而运维团队等了整整 40 分钟都没有收到钉钉通知,直到用户投诉才发现服务已经挂了。
指标 | 预期 | 实际 |
|---|---|---|
告警触发时间 | 03:15:00 | ✅ 正常触发 |
Webhook 推送时间 | 03:15:02 | ❌ 从未到达 Hermes |
钉群收到通知 | 03:15:05 | 🚨 晚了 40 分钟 |
故障持续 | — | 40 分钟(无人响应) |
Step 1:检查 Alertmanager 日志
journalctl -u alertmanager --since "03:10" --until "03:20" | grep -i "webhook\|error"
输出:
Mar 15 03:15:01 alertmanager[12345]: level=error msg="Notify for webhook failed"
receiver=hermes-agent err="Post \"http://hermes-agent:8080/api/v1/alerts\":
dial tcp 10.0.1.50:8080: connect: connection refused"
Step 2:检查 Hermes Agent
systemctl status hermes-agent
● hermes-agent.service - Hermes AI Agent
Loaded: loaded (/etc/systemd/system/hermes-agent.service; enabled)
Active: inactive (dead) since Tue 2026-03-14 22:30:00 CST
# ⚠️ 上周扩容操作后忘记重启了!
Step 3:有监控告警通道的健康检查吗?
grep -r "webhook\|health.*check" /etc/prometheus/alerts/
# 输出为空 ❌

根因 1:Hermes Agent 进程被意外停止。上周 K8s 节点扩容,运维同事停掉了非 K8s 管理的服务,忘了重启 Hermes。
根因 2:没有告警通道健康检查。Alertmanager → Hermes Agent → 钉钉这条链路是否正常,没有任何监控。
根因 3:没有配置降级通知。钉钉不通时,没有自动切到邮件或短信兜底。
三个理由加在一起,就是"必出事"。
# Prometheus 监控告警通道健康
groups:
- name: alert_channel_health
rules:
- alert: HermesAgentDown
expr: up{job="hermes-agent"} == 0
for: 1m
labels: { severity: P1 }
annotations:
summary: "Hermes Agent 不可达"
description: "告警推送通道中断,钉钉将收不到通知"
- alert: WebhookDeliveryFailed
expr: rate(alertmanager_notification_failed_total{integration="webhook"}[5m]) > 0
for: 2m
labels: { severity: P0 }
annotations:
summary: "Webhook 推送失败"
# 配置降级:钉钉不通时走邮件
receivers:
- name: hermes-agent
webhook_configs:
- url: "http://hermes-agent:8080/api/v1/alerts"
send_resolved: true
email_configs:
- to: "ops@example.com"
修复后,4 个月内零漏推。
教训:告警通道本身也要被监控。监控系统的"最后一公里"往往最脆弱。
回到根本问题——为什么非要搞钉钉推送?
我之前一直用 Alertmanager 发邮件告警,痛点一堆:
痛点 | 具体表现 |
|---|---|
邮件延迟 | 平均 3-5 分钟才到手机 |
信息过载 | 200+ 封告警邮件,P0 夹在营销邮件中间 |
无交互 | 告警邮件只能看,不能认领或静默 |
移动不友好 | 手机上看监控图和日志体验极差 |
有一次凌晨 P0 故障,告警邮件被手机自动归到"推广"文件夹,我早上 8 点才看到。

维度 | 邮件告警 | 钉钉告警 |
|---|---|---|
推送延迟 | 3-5 分钟 | < 3 秒 |
移动体验 | 差(排版不适配) | 好(卡片原生适配) |
可交互 | 否 | 是(认领/静默/跳转) |
群协作 | 转发麻烦 | 群内 @相关人 |
配置复杂度 | 低(SMTP) | 低(Webhook) |
结论:不是邮件不能用,而是钉钉把"发出去通知"升级成了"让团队能立即响应"。
打开钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义
Hermes 告警机器人为什么选加签?运维机器 IP 可能变化(如 K8s Pod 重建),加签用密钥验证更稳定。
获取的信息:
Webhook: https://oapi.dingtalk.com/robot/send?access_token=abc123def456
Secret: SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# hermes-agent.yml - 钉钉通知渠道配置
notifiers:
- name: dingtalk-ops
type: dingtalk
enabled: true
config:
webhook: "https://oapi.dingtalk.com/robot/send?access_token=abc123def456"
secret: "SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg_type: "actionCard"
# 消息模板
templates:
critical: |
## 🔴 P0 告警 - {{ .Labels.alertname }}
**服务**: {{ .Labels.job }}
**环境**: {{ .Labels.env }}
**实例**: {{ .Labels.instance }}
**详情**: {{ .Annotations.description }}
**触发时间**: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
👉 请立即响应!
warning: |
## 🟡 P2 告警 - {{ .Labels.alertname }}
**服务**: {{ .Labels.job }}
**详情**: {{ .Annotations.description }}
**触发时间**: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
# hermes-agent.yml - 告警路由配置
routes:
- name: ops-critical
match:
severity: critical
notifier: dingtalk-ops
template: critical
# 互动按钮
actions:
- title: "✅ 认领告警"
action: "acknowledge"
- title: "🔇 静默 1 小时"
action: "silence"
params:
duration: "1h"
- title: "📊 查看 Dashboard"
action: "link"
url: "https://grafana.internal/d/{{ .Labels.job }}"
- name: ops-warning
match:
severity: warning
notifier: dingtalk-ops
template: warning
actions:
- title: "✅ 认领告警"
action: "acknowledge"
- title: "📊 查看 Dashboard"
action: "link"
url: "https://grafana.internal/d/{{ .Labels.job }}"
5 分钟,3 步,钉钉告警推送跑通。
用 actionCard 而不是普通 markdown——因为支持按钮交互,运维可以在钉钉里直接认领告警或静默。
import hmac
import hashlib
import base64
import time
import requests
import urllib.parse
def sign_request(secret: str) -> tuple[str, str]:
"""计算钉钉加签"""
timestamp = str(round(time.time() * 1000))
string_to_sign = f"{timestamp}\n{secret}"
hmac_code = hmac.new(
secret.encode("utf-8"),
string_to_sign.encode("utf-8"),
digestmod=hashlib.sha256
).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
return timestamp, sign
def send_action_card(
webhook: str, secret: str, title: str,
text: str, buttons: list[dict]
) -> dict:
timestamp, sign = sign_request(secret)
url = f"{webhook}×tamp={timestamp}&sign={sign}"
payload = {
"msgtype": "actionCard",
"actionCard": {
"title": title,
"text": text,
"btnOrientation": "1",
"btns": buttons,
}
}
resp = requests.post(
url, json=payload,
headers={"Content-Type": "application/json"}
)
return resp.json()
# 发送 P0 告警
result = send_action_card(
webhook="https://oapi.dingtalk.com/robot/send?access_token=abc123def456",
secret="SECxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
title="🔴 P0 - MySQLDown",
text="""## 🔴 P0 告警 - MySQLDown
**服务**: order-db
**环境**: production
**实例**: 10.0.1.5:3306
**详情**: MySQL 主库连接失败
**触发时间**: 2026-07-01 03:25:01
> 请立即响应!""",
buttons=[
{"title": "✅ 认领告警", "actionURL": "https://hermes.internal/api/ack/mysql-down-abc123"},
{"title": "🔇 静默 1h", "actionURL": "https://hermes.internal/api/silence?alert=mysql-down&duration=1h"},
{"title": "📊 Dashboard", "actionURL": "https://grafana.internal/d/order-db"},
]
)
在钉钉群里看到的效果:
┌──────────────────────────────────┐
│ 🔴 P0 告警 - MySQLDown │
│ │
│ 服务: order-db │
│ 环境: production │
│ 实例: 10.0.1.5:3306 │
│ 详情: MySQL 主库连接失败 │
│ 时间: 2026-07-01 03:25:01 │
│ │
│ ┌───── ✅ 认领告警 ──────────┐ │
│ ├───── 🔇 静默 1h ───────────┤ │
│ └───── 📊 Dashboard ──────────┘ │
└──────────────────────────────────┘

# alertmanager.yml
route:
receiver: hermes-agent
group_wait: 15s
group_interval: 3m
repeat_interval: 10m
group_by: [ 'alertname', 'job', 'severity' ]
receivers:
- name: hermes-agent
webhook_configs:
- url: "http://hermes-agent:8080/api/v1/alerts"
send_resolved: true
http_config:
headers:
Authorization: "Bearer hermes-token-xxx"
监控项 | 采集方式 | 告警阈值 | 级别 |
|---|---|---|---|
Hermes Agent 可用性 | Prometheus up | == 0 持续 30s | P1 |
Webhook 推送成功率 | Alertmanager 指标 | < 99% | P0 |
钉钉 API 响应时间 | Hermes 内部指标 | P99 > 2s | P2 |
告警端到端延迟 | 端到端拨测 | > 10s | P1 |
钉钉 Token 有效性 | 定时拨测 | 401 响应 | P0 |
降级通道可用性 | 定时拨测邮件/短信 | 不可用 | P1 |
#!/bin/bash
# alert_channel_health.sh - 告警推送通道健康检查
# 1. 检查 Hermes Agent 进程
if ! systemctl is-active --quiet hermes-agent; then
echo "[ALERT] Hermes Agent 进程停止,尝试重启..."
systemctl restart hermes-agent
sleep 5
if ! systemctl is-active --quiet hermes-agent; then
curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{"msgtype":"text","text":{"content":"🚨 Hermes Agent 重启失败!"}}'
fi
fi
# 2. 拨测钉钉 Webhook
python3 /opt/scripts/dingtalk_probe.py \
--webhook "https://oapi.dingtalk.com/robot/send?access_token=TOKEN" \
--secret "SECRET" --timeout 5 \
|| echo "[ALERT] 钉钉推送链路故障"
部署后,告警通道再出问题,第一时间告警你——而不是等你发现了再追责。

维度 | 手动 Python 脚本 | Hermes Agent 配置 |
|---|---|---|
开发量 | 100+ 行代码 | 50 行 YAML |
加签逻辑 | 自己实现 | 内置 |
模板渲染 | 手拼字符串 | Go Template |
告警恢复通知 | 需额外处理 | send_resolved: true |
按钮交互 | 需自建 HTTP 服务 | 内置 |
维护成本 | 高 | 低(只改 YAML) |
现象:发送消息后返回 {"errcode":310000,"errmsg":"sign not match"}。
原因:加签计算时 timestamp 和 secret 的拼接格式有误,或 timestamp 与服务器时间差超过 1 小时。
解决:
# 检查服务器时间是否准确
ntpdate -q ntp.aliyun.com
# 如果偏差大,同步时间
sudo ntpdate ntp.aliyun.com
加签的 timestamp 必须是当前毫秒时间戳,不能用缓存值。
现象:服务恢复了,钉钉收到的消息还是红色 P0 告警格式,差点把值班同事吓一跳。
原因:模板没有区分 firing 和 resolved。
解决:
templates:
critical: |
{{- if eq .Status "firing" -}}
## 🔴 P0 告警 - {{ .Labels.alertname }}
**详情**: {{ .Annotations.description }}
**触发时间**: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{- else -}}
## 🟢 已恢复 - {{ .Labels.alertname }}
**持续时间**: {{ .EndsAt.Sub .StartsAt }}
**恢复时间**: {{ .EndsAt.Format "2006-01-02 15:04:05" }}
{{- end -}}
告警恢复通知比触发通知更重要——它告诉值班人员"可以安心了"。
现象:Alertmanager 配了 http://localhost:8080,但 Hermes Agent 在另一个 Pod 里,推送失败。
原因:K8s 中每个 Pod 独立网络命名空间,localhost 指的是自己 Pod。
解决:
# 用 K8s Service DNS,不用 localhost
receivers:
- name: hermes-agent
webhook_configs:
- url: "http://hermes-agent.ops-monitoring.svc.cluster.local:8080/api/v1/alerts"
send_resolved: true
K8s 环境中服务间通信一律用 Service DNS。
Hermes Agent + 钉钉告警推送的核心价值:
适合谁用:团队用钉钉办公、需要移动端快速响应告警、希望告警可交互(认领/静默)
不适合谁用:不用钉钉(飞书/企微有替代方案)、告警量 < 5 条/天(邮件足够)
💬 你们团队用什么渠道接收告警?钉钉、飞书、企微、还是邮件?评论区聊聊你的选择~选钉钉的人最多我猜,但我可能猜错了 😄
⭐️ 觉得有用?点个「在看」和「转发」,让更多运维兄弟 5 分钟搞定告警推送~