第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_STATUSPROBLEM / OKPROBLEM
ZABBIX_ALERT_TRIGGERID触发器ID12345

一个简单的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接入方式个人评级
Prometheusalertname, status, labels, annotations原生webhook_configs★★★★★ 最标准
ZabbixSUBJECT, MESSAGE, SEVERITY, STATUS脚本转Webhook★★★☆☆ 需额外开发
ELK(ElastAlert)name, severity, match_body插件WebhookAlert★★★★☆ 灵活但字段杂

你看,Prometheus的告警结构天然就是结构化JSON,Zabbix还得靠脚本去拼,ELK则需要在告警规则里人工定义字段。我的建议是:统一后端的Schema,然后为每个系统写一个「适配器」——说白了就是一段小脚本或中间层,把源字段映射成目标字段。

2.6 Webhook统一接入架构

来看下我画的架构图,它解释了各监控系统如何通过Webhook接入统一告警中心:

Webhook统一接入架构 Prometheus Zabbix ELK Webhook统一接收层 ( /prometheus /zabbix /elastalert ) 字段映射 → 统一Schema → 告警路由 每个系统对应独立端点,通过适配器转为统一格式

架构很直白:每个监控系统都有独立的Webhook端点,收到告警后立马做字段映射,转成统一Schema,再进入路由引擎。这样做的好处是——你加一个新系统时,只需要写一个几十行的Python适配器,不用动现有的路由逻辑。

核心要点:Webhook统一接入不是一股脑把所有原始JSON丢到后端,而是先做一次「格式清洗」。清洗内容包括:字段重命名、级别映射(比如Zabbix的4级数字转成critical)、时间格式化、去重ID生成。我曾经在一个项目里偷懒没做清洗,结果后端处理程序在解析不同系统的告警时频繁报KeyError,修了三天。所以,别图省事。

好了,到这一步你已经掌握了Prometheus、Zabbix、ELK三种主流监控系统的告警输出格式和Webhook接入方法。下一步就该考虑告警到了统一中心之后怎么去重、怎么分配处置人员了——那是后面章节的事情。