第2章 告警源接入:常见监控系统的告警输出格式与Webhook对接
说实话,告警源接入是整个自动化告警处置流程的起点。如果这一步没做好,后面的路由、降噪、通知全是空中楼阁。我个人习惯是把「接入」拆成两件事:一是搞清楚监控系统吐出来的告警长什么样,二是把告警通过Webhook送到统一处理中心。
你会问:为什么不直接用监控系统自带的通知?嗯,当你有三五套监控系统、上百个业务模块的时候,每套系统各自发邮件、发钉钉,你就等着被告警洪流冲垮吧。标准化接入,才是正经事。
2.1 为什么要标准化告警源接入?
我在项目中遇到过最典型的案例:Prometheus告警里带着alertname和labels,Zabbix告警用的是triggerid和severity数字,ELK那边的ElastAlert直接发JSON里塞了query_key。三套系统的字段名完全不同,每次写路由规则都得翻三份文档。说白了,不统一就等于给自己挖坑。
标准化的核心目标就三个:
- 字段统一:把不同系统的告警字段映射到统一Schema(比如标题、级别、来源、时间、描述)。
- 传输协议统一:全部走HTTP POST + JSON 的 Webhook,拒绝任何SMTP、SNMP Trap、自定义TCP协议。
- 幂等重试:Webhook接收端必须支持去重,保证同一条告警不会触发两次处置。
避坑指南:我曾经遇到过Zabbix的Webhook脚本里忘了转义中文字符,结果告警标题变成了乱码。后来我在接入脚本里强制json.dumps(ensure_ascii=False),再也没出现过。细节决定成败。
2.2 Prometheus Alertmanager
Prometheus本身不做告警发送,它把告警推到Alertmanager。Alertmanager支持webhook_configs,你只需要配一个url和send_resolved开关。
先看看Alertmanager默认的告警JSON长啥样:
{
"version": "4",
"groupKey": "{}:{alertname=\"CPUUsageHigh\"}",
"status": "firing",
"receiver": "webhook-handler",
"groupLabels": {"alertname": "CPUUsageHigh"},
"commonLabels": {
"alertname": "CPUUsageHigh",
"severity": "critical",
"instance": "web-01"
},
"commonAnnotations": {
"summary": "CPU usage exceeds 90%",
"description": "Instance web-01 CPU at 95% for 5 minutes"
},
"alerts": [
{
"status": "firing",
"labels": {"alertname": "CPUUsageHigh", "severity": "critical"},
"annotations": {"summary": "CPU usage exceeds 90%"},
"startsAt": "2025-01-15T10:00:00Z",
"endsAt": "0001-01-01T00:00:00Z"
}
]
}
重点字段:status(firing/resolved)、commonLabels(公共标签)、alerts[].annotations(告警描述)。
配Webhook时,Alertmanager的alertmanager.yml里加一段:
receivers:
- name: 'webhook-handler'
webhook_configs:
- url: 'http://your-alert-center:8080/prometheus'
send_resolved: true
注意send_resolved: true,这样恢复告警也能收到,方便做「告警关闭」的自动化动作。
2.3 Zabbix
Zabbix没有原生Webhook,但你可以用「动作(Action)」配合「脚本」来实现。我建议用Python写一个简单的Webhook发送脚本,放到Zabbix Server的AlertScriptsPath目录下。
Zabbix传给脚本的环境变量大概有这些:
| 环境变量 | 含义 | 示例值 |
|---|---|---|
ZABBIX_ALERT_SUBJECT | 告警标题(宏解析后) | CPU High on web-01 |
ZABBIX_ALERT_MESSAGE | 告警消息正文 | CPU usage: 95% |
ZABBIX_ALERT_SEVERITY | 告警级别(数字) | 4 |
ZABBIX_ALERT_STATUS | PROBLEM / OK | PROBLEM |
ZABBIX_ALERT_TRIGGERID | 触发器ID | 12345 |
一个简单的Python发送脚本示例:
#!/usr/bin/env python3
import os, requests, json
severity_map = {4: "critical", 3: "warning", 2: "info"}
status = os.environ.get("ZABBIX_ALERT_STATUS", "PROBLEM")
payload = {
"source": "zabbix",
"status": status == "PROBLEM" and "firing" or "resolved",
"severity": severity_map.get(int(os.environ.get("ZABBIX_ALERT_SEVERITY", 2)), "unknown"),
"title": os.environ.get("ZABBIX_ALERT_SUBJECT", ""),
"description": os.environ.get("ZABBIX_ALERT_MESSAGE", ""),
"trigger_id": os.environ.get("ZABBIX_ALERT_TRIGGERID", ""),
"time": os.environ.get("ZABBIX_ALERT_TIMESTAMP", "")
}
requests.post("http://your-alert-center:8080/zabbix", json=payload, timeout=5)
个人经验:Zabbix的宏变量解析结果里可能包含HTML标签或特殊字符,我习惯在脚本里做一次strip()和unquote(),保证JSON不报错。
2.4 ELK (Elastic Stack)
ELK做告警,常见方案有三种:ElastAlert、Watcher(X-Pack)、以及自写Logstash过滤。我常用ElastAlert,配置简单,而且输出格式可控。
ElastAlert的Webhook输出配置示例:
alert:
- "elastalert.alerts.webhook.WebhookAlert"
webhook:
url: "http://your-alert-center:8080/elastalert"
webhook_timeout: 10
webhook_body: |
{
"source": "elastalert",
"status": "firing",
"severity": "{{ severity }}",
"title": "{{ name }}",
"description": "{{ match_body.message }}",
"rule_name": "{{ rule_name }}",
"match_time": "{{ @timestamp }}"
}
ElastAlert默认的告警JSON里字段很多,我建议在webhook_body里手动映射成统一格式,别偷懒用match_source全量转发,毕竟你的后端处理程序可不想解析ElastAlert那一大坨嵌套结构。
2.5 常见告警格式对比
| 监控系统 | 核心字段 | Webhook接入方式 | 个人评级 |
|---|---|---|---|
| Prometheus | alertname, status, labels, annotations | 原生webhook_configs | ★★★★★ 最标准 |
| Zabbix | SUBJECT, MESSAGE, SEVERITY, STATUS | 脚本转Webhook | ★★★☆☆ 需额外开发 |
| ELK(ElastAlert) | name, severity, match_body | 插件WebhookAlert | ★★★★☆ 灵活但字段杂 |
你看,Prometheus的告警结构天然就是结构化JSON,Zabbix还得靠脚本去拼,ELK则需要在告警规则里人工定义字段。我的建议是:统一后端的Schema,然后为每个系统写一个「适配器」——说白了就是一段小脚本或中间层,把源字段映射成目标字段。
2.6 Webhook统一接入架构
来看下我画的架构图,它解释了各监控系统如何通过Webhook接入统一告警中心:
架构很直白:每个监控系统都有独立的Webhook端点,收到告警后立马做字段映射,转成统一Schema,再进入路由引擎。这样做的好处是——你加一个新系统时,只需要写一个几十行的Python适配器,不用动现有的路由逻辑。
核心要点:Webhook统一接入不是一股脑把所有原始JSON丢到后端,而是先做一次「格式清洗」。清洗内容包括:字段重命名、级别映射(比如Zabbix的4级数字转成critical)、时间格式化、去重ID生成。我曾经在一个项目里偷懒没做清洗,结果后端处理程序在解析不同系统的告警时频繁报KeyError,修了三天。所以,别图省事。
好了,到这一步你已经掌握了Prometheus、Zabbix、ELK三种主流监控系统的告警输出格式和Webhook接入方法。下一步就该考虑告警到了统一中心之后怎么去重、怎么分配处置人员了——那是后面章节的事情。