第三章 告警标准化:设计统一告警模型

标准化是基础。但没有统一模型,标准化就是空谈。

我见过不少团队,告警字段随心定,严重等级全凭感觉。结果呢?告警池乱成一锅粥,自动化处置根本推不下去。说白了,你得先定义清楚「告警长什么样」,才能让机器看懂、让流程跑通。

3.1 核心字段定义:告警的「身份证」

统一告警模型,第一件事就是把字段固定下来。我习惯把字段分成两类:必填字段和选填字段。必填字段缺一不可,否则告警直接丢弃。

下面是我的推荐清单,你可以在自己系统里照着建:

核心字段清单

  • 告警ID:全局唯一标识。我通常用UUID或雪花ID,别用自增数字——如果你要跨系统合并,重复ID会让你哭。
  • 告警标题:一句话概括问题。例如「CPU使用率超过90% on host-001」。
  • 告警内容:详细描述,包含上下文和关键指标。
  • 告警来源:哪个监控系统产生的?Prometheus、Zabbix、还是ELK?
  • 资源标识:哪个IP、主机名、容器ID或服务名出了问题。
  • 严重等级:下文专门展开。
  • 事件时间:问题实际发生的时间。
  • 接收时间:告警系统收到的时间。
  • 标签集合:可扩展的KV键值对,用于路由、过滤和分组。

嗯,这里要注意——告警内容别写太啰嗦。我在项目中遇到过有人把整页日志贴进去,结果下游处理时解析超时。控制在200字以内,关键信息前置。

来看看实际JSON长什么样:

{
  "alert_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "磁盘使用率超过80% on db-01",
  "content": "主机 db-01 (10.0.1.10) 根分区使用率85.3%,已持续15分钟",
  "source": "Prometheus",
  "resource_id": "host:db-01",
  "severity": "critical",
  "event_time": "2025-03-15T14:32:00+08:00",
  "receive_time": "2025-03-15T14:32:05+08:00",
  "labels": {
    "env": "production",
    "team": "dba",
    "region": "cn-beijing"
  }
}

字段定了,团队要达成一致。我建议把这份定义当作「告警契约」,所有产生告警的系统都必须遵守。不遵守的告警,直接进黑名单。

3.2 严重等级:不要只分「严重」和「不严重」

很多团队把告警等级分为P0、P1、P2、P3、P4。但光有等级还不够——你得明确每个等级对应的响应时效和处置动作。否则大家还是按自己理解来。

我个人习惯用这个标准:

等级 定义 举例 响应时效
P0 (紧急) 核心服务完全不可用,影响大量用户 支付网关挂了,数据库主库宕机 5分钟内有人处理
P1 (严重) 核心功能降级,或局部服务不可用 API响应延迟>5秒,Redis集群部分节点掉线 15分钟内有人处理
P2 (一般) 非核心功能受损,或核心功能有潜在风险 磁盘使用率超过80%,次要服务报错率上升 1小时内处理
P3 (警告) 指标有异常,但暂时不影响业务 CPU负载短期峰值,证书即将过期 24小时内处理
P4 (通知) 信息类,仅记录 日常备份完成,版本发布成功 无需立即响应

我曾经踩过的坑: 某次把所有告警都设为P1,想着「宁可严不可松」。结果值班同学被频繁告警炸到麻木,真正P0的核心宕机反而没人第一时间响应。所以,等级必须清晰、差异化,否则就是噪音。

为什么我在模型里特别强调「响应时效」?因为自动化处置需要它。举个例子,P0告警来了,自动拉群、打电话、启动runbook;P3告警来了,自动创建Jira工单就行。等级定义不清,自动化逻辑就没法写。

3.3 时间戳:千万别只用「当前时间」

很多人写告警模型,时间戳只写一个「当前时间」。这是错的。你想想看——监控系统从采集到传输再到入库,中间可能有延迟。如果你只用接收时间,那问题追溯就会出偏差。

我推荐的方案是三级时间戳体系:

  • 事件时间 (event_time):指标实际被采集的时间。这是你分析问题时序的依据。
  • 采集时间 (collect_time):代理或系统采集到数据的时刻。通常和事件时间很接近,但有些被动采集(比如SNMP)会有差异。
  • 入库时间 (receive_time):告警系统实际写入存储的时间。用于监控系统自身的延迟检测。

我的建议: 所有时间戳统一使用ISO 8601格式(带时区)。我见过有人用Unix秒数、有人用毫秒、还有人用字符串 - 每个系统的解析器都要单独适配。统一格式能省下你大量调试时间。

举个例子,时间戳转换的逻辑我通常会放在告警接收的入口处,统一清洗:

def normalize_timestamp(raw_time):
    """
    统一转成带时区的ISO 8601字符串
    """
    if isinstance(raw_time, (int, float)):
        # 假设是秒级Unix时间戳
        dt = datetime.fromtimestamp(raw_time, tz=timezone.utc)
        return dt.isoformat()
    # 其他格式的兜底逻辑
    return raw_time  # 如果入参已经是标准格式,直接返回

3.4 标签:告警的「分类和路由钥匙」

字段定义了、等级划定了、时间戳统一了。但真正让告警能被灵活分组、路由和过滤的,是标签 (Labels)。

说白了,标签就是一组Key-Value的元数据。它可以表达任何维度:环境(env)、机房(region)、业务线(service)、团队(team)、甚至告警来源子类(alert_type)。

我过去做的一个经典标签策略是这样的:

标签Key 说明 示例值
env 部署环境 production, staging, dev
region 地域/机房 cn-beijing, us-east-1
service 所属服务或业务线 payment, user, order
team 负责团队 dba, sre, backend
alert_type 告警类别 cpu, memory, latency, error_rate
runbook 处置手册链接 https://wiki/internal/runbook/cpu

为什么要这样设计?因为在你做自动化处置时,标签就是「条件判断」的依据。比如:

  • 如果 env = production 且 severity = critical,自动创建P0工单并@值班人。
  • 如果 service = payment 且 alert_type = latency,自动触发扩容runbook。
  • 如果 team = dba,自动路由到DBA的告警群。

我团队有一个原则:任何告警模型,标签集的扩展性要大于字段集。因为字段是固定的Schema,改起来要动表结构;标签是KV对,随时可以追加。新增一个标签,不需要改数据库。

补充建议: 标签的值尽量标准化。比如环境统一写成「production」而非「pro」、「prod」、「prd」。我曾经因为命名不规范,导致告警路由规则写了30行条件匹配,改了半年才收敛。

好了,现在我们把上面所有内容串成一张图,看看统一告警模型长什么样:

统一告警模型结构 字段结构 • 告警ID (唯一标识) • 告警标题 • 告警内容 (关键信息前置) • 告警来源 (Prometheus 等) • 资源标识 (IP/主机名/服务) 等级映射 • P0: 5分钟内响应 • P1: 15分钟内响应 • P2: 1小时内处理 • P3: 24小时内处理 • P4: 仅通知 时间戳体系 • 事件时间 (event_time) • 采集时间 (collect_time) • 入库时间 (receive_time) • 统一 ISO 8601 格式 标签体系 (Labels) • env: production / staging / dev • region: cn-beijing / us-east-1 • service: payment / user / dba • alert_type: cpu / memory / latency • runbook: 手册链接 • team: dba / sre / backend 所有字段与标签组合成统一Schema 支撑路由、过滤、处置自动化

看图就清楚了。字段结构负责「是什么」,等级映射决定「有多急」,时间戳体系记录「什么时候」,标签体系控制「怎么分、怎么转」。四者缺一不可。

我在落地时,通常会把这个模型放到公司的告警网关层。所有上游监控系统(Prometheus、Zabbix、CloudWatch)产生的告警,都必须经过这个网关「翻译」成统一模型,再进入下游处置引擎。这样,后端处理代码就只需要跟这一种模型打交道,复用率极高。

小提示: 如果你们团队已经有一套现成的告警模型,别急着推倒重来。先在网关层做一层模型转换,逐步迁移。我见过一个团队花三个月「重构告警模型」,结果业务方等不及,自己又绕开模型发了告警。