概述

凌晨三点,手机震动。你从被窝爬起来,打开电脑,SSH 上去,发现某个服务 CPU 飙升。Kill 进程,重启服务,12 分钟搞定——但你彻底清醒了。四点半又来一条告警:磁盘使用率超 85%。又爬起来,du -sh 定位,删掉过期日志,15 分钟。

这是无数运维工程师的日常。监控做了,告警配了,脚本也写了——但最后一步还是人在跑。而且偏偏在凌晨。

根据 Google SRE Book 的数据,一个典型的 SRE 团队每天接收 50-100 条告警,其中 80% 是噪音,超过 60% 的告警是重复处理过的已知问题。

告警自动化的目标不是消灭告警,而是把人的判断和操作转化为系统的自动响应。我将从告警降噪、分级路由、Runbook 自动化、自愈平台架构、AI 辅助治理五个维度,详细梳理如何构建告警自动化处理体系。

告警现状:为什么需要自动化

告警风暴的根源

告警风暴通常不是监控配置不足,而是配置泛滥的产物。以下是生产环境中最常见的告警问题模式:

问题模式典型表现根因
告警泛滥每天 100+ 条告警,80% 无需人工介入静态阈值过敏感,缺少聚合和去重
告警疲劳工程师忽略告警通知,真正故障被淹没信号噪声比太低,缺少优先级分级
重复告警同一问题触发多条告警,不同监控视角缺少告警关联和聚合机制
响应延迟从告警到人工处理平均 15-30 分钟缺少自动化响应,依赖人工介入
重复劳动超过 60% 的告警处理流程完全相同没有将已知操作沉淀为自动化 Runbook

告警生命周期的五个阶段

一个成熟的告警自动化系统应该覆盖告警的完整生命周期:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  1. 产生     │────▶│  2. 降噪     │────▶│  3. 路由     │────▶│  4. 处理     │────▶│  5. 复盘     │
│  Detection  │     │  Dedup      │     │  Route     │     │  Remediate │     │  Review    │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
     │                    │                   │                   │                   │
  监控系统          去重/聚合/抑制      分级/分发/升级     自动Runbook/人工     归档/改进/度量
  1. 产生:监控系统(Prometheus、Zabbix、Datadog 等)检测到异常,产生原始告警
  2. 降噪:去重、聚合、抑制,将告警风暴压缩为可管理的事件
  3. 路由:按严重程度、服务归属、值班表,将告警分发到正确的处理通道
  4. 处理:自动执行 Runbook 或人工介入修复问题
  5. 复盘:记录处理过程、度量效果、持续改进告警规则和自动化脚本

告警降噪:从噪声到信号

告警去重与聚合

Alertmanager 是 Prometheus 生态中最常用的告警管理组件,它提供了强大的告警降噪能力。

分组(Grouping)

将相关告警合并为一个通知,避免告警风暴:

# alertmanager.yml — 分组配置
route:
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s        # 首次告警后等待 30s,收集同组告警
  group_interval: 5m     # 同组告警的发送间隔
  repeat_interval: 4h    # 未解决的告警重复通知间隔
  receiver: 'default'

receivers:
  - name: 'default'
    webhook_configs:
      - url: 'http://alert-router:8080/alert'

分组策略的关键在于选择正确的 group_by 维度。以下是不同场景的分组建议:

场景group_by 配置效果
多实例服务故障['alertname', 'cluster', 'service']同一服务的多实例告警合并
基础设施级故障['alertname', 'cluster']同一集群的基础设施告警合并
单节点故障['alertname', 'instance']同一节点的多维度告警合并
全局性故障['alertname']所有同名告警合并为一条

抑制(Inhibition)

当高级别告警触发时,自动抑制低级别告警:

# alertmanager.yml — 抑制规则
inhibit_rules:
  # 当节点宕机时,抑制该节点上的所有服务告警
  - source_match:
      alertname: 'NodeDown'
    target_match_re:
      alertname: '.*(Service|Pod|Container).*Down|CrashLoopBackOff'
    equal: ['instance']

  # 当集群不可用时,抑制该集群的所有告警
  - source_match:
      severity: 'critical'
      alertname: 'ClusterUnavailable'
    target_match_re:
      severity: 'warning|info'
    equal: ['cluster']

  # 当数据库主节点切换时,抑制从库同步延迟告警
  - source_match:
      alertname: 'MySQLMasterSwitch'
    target_match:
      alertname: 'MySQLReplicationLag'
    equal: ['cluster', 'service']

告警指纹去重

对于跨多个监控系统的告警,需要在告警路由层实现基于指纹的去重:

#!/usr/bin/env python3
"""告警指纹去重引擎 — 基于告警特征生成唯一指纹,实现跨系统去重"""

import hashlib
import time
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class Alert:
    """告警数据结构"""
    alertname: str
    severity: str
    service: str
    instance: str
    cluster: str
    message: str
    labels: dict = field(default_factory=dict)
    fingerprint: str = ""
    first_seen: float = 0
    last_seen: float = 0
    count: int = 0

    def compute_fingerprint(self) -> str:
        """计算告警指纹(基于关键字段,忽略时间戳和动态值)"""
        # 只用结构性字段生成指纹,忽略 message 中的动态数值
        key_fields = f"{self.alertname}:{self.service}:{self.instance}:{self.cluster}:{self.severity}"
        return hashlib.md5(key_fields.encode()).hexdigest()[:16]

class AlertDeduplicator:
    """告警去重器"""

    def __init__(self, dedup_window: int = 300):
        """
        Args:
            dedup_window: 去重窗口(秒),同一指纹在此窗口内只保留一条
        """
        self.dedup_window = dedup_window
        self.alert_store: dict[str, Alert] = {}  # fingerprint -> Alert

    def process(self, alert: Alert) -> Optional[Alert]:
        """
        处理告警,返回需要发送的告警(去重后)
        返回 None 表示该告警是重复的,已被抑制
        """
        alert.fingerprint = alert.compute_fingerprint()
        now = time.time()

        if alert.fingerprint in self.alert_store:
            existing = self.alert_store[alert.fingerprint]
            existing.last_seen = now
            existing.count += 1

            # 在去重窗口内,抑制重复告警
            if now - existing.first_seen < self.dedup_window:
                return None  # 抑制
            else:
                # 超过去重窗口,作为新告警处理
                existing.first_seen = now
                existing.count = 1
                return existing
        else:
            alert.first_seen = now
            alert.last_seen = now
            alert.count = 1
            self.alert_store[alert.fingerprint] = alert
            return alert

    def get_active_alerts(self) -> list[Alert]:
        """获取当前活跃的告警列表"""
        now = time.time()
        return [
            a for a in self.alert_store.values()
            if now - a.last_seen < self.dedup_window * 2
        ]

    def cleanup(self, max_age: int = 3600):
        """清理过期的告警记录"""
        now = time.time()
        expired = [
            fp for fp, alert in self.alert_store.items()
            if now - alert.last_seen > max_age
        ]
        for fp in expired:
            del self.alert_store[fp]

if __name__ == "__main__":
    dedup = AlertDeduplicator(dedup_window=300)

    # 模拟告警风暴
    alerts = [
        Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 92%"),
        Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 95%"),  # 重复
        Alert("HighCPU", "warning", "api-gw", "10.0.1.1", "prod", "CPU 98%"),  # 重复
        Alert("HighMemory", "critical", "api-gw", "10.0.1.1", "prod", "内存 95%"),
        Alert("DiskFull", "critical", "db", "10.0.2.1", "prod", "磁盘 85%"),
    ]

    for alert in alerts:
        result = dedup.process(alert)
        if result:
            print(f"[发送] {alert.alertname} | {alert.service} | {alert.instance} | 指纹={alert.fingerprint}")
        else:
            print(f"[抑制] {alert.alertname} | {alert.service} | {alert.instance} | 重复告警")

    print(f"\n活跃告警数: {len(dedup.get_active_alerts())}")

静默管理

在维护窗口期间,临时静默特定告警:

# alertmanager.yml — 静默规则(也可通过 API 动态创建)
# 通过 API 创建静默
# curl -X POST http://alertmanager:9093/api/v2/silences \
#   -H "Content-Type: application/json" \
#   -d '{
#     "matchers": [
#       {"name": "service", "value": "payment-service", "isRegex": false}
#     ],
#     "startsAt": "2026-07-11T02:00:00+08:00",
#     "endsAt": "2026-07-11T04:00:00+08:00",
#     "createdBy": "ops-team",
#     "comment": "数据库维护窗口"
#   }'

自动化的静默管理脚本:

#!/bin/bash
# schedule-silence.sh — 自动创建告警静默(用于维护窗口)

ALERTMANAGER="http://alertmanager:9093"
SERVICE="${1:-payment-service}"
DURATION="${2:-120}"  # 分钟

START_TIME=$(date -u -d "+1 minute" '+%Y-%m-%dT%H:%M:%S.000Z')
END_TIME=$(date -u -d "+${DURATION} minutes" '+%Y-%m-%dT%H:%M:%S.000Z')

curl -s -X POST "${ALERTMANAGER}/api/v2/silences" \
  -H "Content-Type: application/json" \
  -d "{
    \"matchers\": [
      {\"name\": \"service\", \"value\": \"${SERVICE}\", \"isRegex\": false}
    ],
    \"startsAt\": \"${START_TIME}\",
    \"endsAt\": \"${END_TIME}\",
    \"createdBy\": \"automation\",
    \"comment\": \"自动静默: ${SERVICE} 维护 ${DURATION}分钟\"
  }"

echo "已为 ${SERVICE} 创建 ${DURATION} 分钟静默窗口"

多维度告警关联

当多个监控系统(Prometheus、ELK、APM)同时产生告警时,需要将它们关联为同一个事件:

#!/usr/bin/env python3
"""多维度告警关联引擎 — 将不同来源的告警关联为同一事件"""

import time
from dataclasses import dataclass, field
from typing import Optional
from collections import defaultdict

@dataclass
class AlertEvent:
    """关联后的告警事件"""
    event_id: str
    service: str
    cluster: str
    severity: str  # 取最高级别
    alerts: list = field(default_factory=list)
    first_seen: float = 0
    last_seen: float = 0
    status: str = "firing"  # firing / resolved

    def add_alert(self, alert: dict):
        self.alerts.append(alert)
        self.last_seen = time.time()
        severity_order = {"info": 0, "warning": 1, "critical": 2, "fatal": 3}
        if severity_order.get(alert.get("severity", "info"), 0) > \
           severity_order.get(self.severity, 0):
            self.severity = alert["severity"]

class AlertCorrelator:
    """告警关联器 — 基于服务、集群、时间窗口关联告警"""

    CORRELATION_WINDOW = 300  # 5分钟窗口

    def __init__(self):
        self.events: dict[str, AlertEvent] = {}  # correlation_key -> AlertEvent

    def _correlation_key(self, alert: dict) -> str:
        """生成关联键"""
        service = alert.get("labels", {}).get("service", "unknown")
        cluster = alert.get("labels", {}).get("cluster", "unknown")
        return f"{service}:{cluster}"

    def process(self, alert: dict) -> Optional[AlertEvent]:
        """处理告警,返回关联后的事件"""
        key = self._correlation_key(alert)
        now = time.time()

        if key in self.events:
            event = self.events[key]
            # 检查是否在关联窗口内
            if now - event.last_seen <= self.CORRELATION_WINDOW:
                event.add_alert(alert)
                return event
            else:
                # 窗口过期,创建新事件
                event.status = "resolved"

        # 创建新事件
        event_id = f"EVT-{int(now)}-{key.replace(':', '-')}"
        event = AlertEvent(
            event_id=event_id,
            service=alert.get("labels", {}).get("service", "unknown"),
            cluster=alert.get("labels", {}).get("cluster", "unknown"),
            severity=alert.get("severity", "info"),
            first_seen=now,
            last_seen=now,
        )
        event.add_alert(alert)
        self.events[key] = event
        return event

if __name__ == "__main__":
    correlator = AlertCorrelator()

    # 模拟多源告警
    raw_alerts = [
        {"alertname": "HighCPU", "severity": "warning",
         "labels": {"service": "api-gw", "cluster": "prod"}},
        {"alertname": "HighLatency", "severity": "critical",
         "labels": {"service": "api-gw", "cluster": "prod"}},  # 关联
        {"alertname": "ErrorRateHigh", "severity": "critical",
         "labels": {"service": "api-gw", "cluster": "prod"}},  # 关联
        {"alertname": "DiskFull", "severity": "critical",
         "labels": {"service": "db", "cluster": "prod"}},       # 不同服务
    ]

    for alert in raw_alerts:
        event = correlator.process(alert)
        print(f"告警: {alert['alertname']:20s} → 事件: {event.event_id} | "
              f"严重度: {event.severity:8s} | 关联告警数: {len(event.alerts)}")

告警分级与路由

告警分级标准

建立统一的告警分级标准,是自动化路由的前提:

级别定义响应时间示例处理方式
P0 - Critical核心服务不可用立即(< 1分钟)生产数据库宕机、API 全不可用电话+短信+IM,全员响应
P1 - High核心服务降级5 分钟内API 错误率 > 5%、P99 延迟翻倍短信+IM,值班工程师处理
P2 - Medium非核心服务异常30 分钟内测试环境服务异常、磁盘 > 80%IM 通知,工作时间处理
P3 - Low预警信息工作时间磁盘 > 70%、证书 30 天过期邮件/IM,创建工单跟踪

告警路由规则设计

#!/usr/bin/env python3
"""告警路由引擎 — 基于规则的告警分级与分发"""

import re
import json
from dataclasses import dataclass, field
from typing import Optional
from enum import Enum

class Severity(Enum):
    P0 = "critical"
    P1 = "high"
    P2 = "medium"
    P3 = "low"

@dataclass
class RouteRule:
    """路由规则"""
    name: str
    match_labels: dict           # 匹配的标签
    match_severity: list[str]    # 匹配的严重度
    receivers: list[str]         # 接收者列表
    escalate_after: int = 0     # 未响应后升级时间(秒)
    escalate_to: list[str] = field(default_factory=list)  # 升级接收者
    auto_remediation: str = ""   # 关联的自动修复 Runbook

class AlertRouter:
    """告警路由器"""

    def __init__(self):
        self.rules: list[RouteRule] = []
        self.default_receivers = ["on-call-team"]

    def add_rule(self, rule: RouteRule):
        self.rules.append(rule)

    def route(self, alert: dict) -> dict:
        """路由告警到正确的接收者和处理流程"""
        severity = alert.get("severity", "info")
        labels = alert.get("labels", {})

        for rule in self.rules:
            if self._match(alert, rule):
                return {
                    "alert": alert,
                    "severity": severity,
                    "receivers": rule.receivers,
                    "escalate_after": rule.escalate_after,
                    "escalate_to": rule.escalate_to,
                    "auto_remediation": rule.auto_remediation,
                    "routed_at": alert.get("startsAt", ""),
                }

        return {
            "alert": alert,
            "severity": severity,
            "receivers": self.default_receivers,
            "escalate_after": 0,
            "escalate_to": [],
            "auto_remediation": "",
            "routed_at": alert.get("startsAt", ""),
        }

    def _match(self, alert: dict, rule: RouteRule) -> bool:
        """检查告警是否匹配路由规则"""
        severity = alert.get("severity", "info")
        labels = alert.get("labels", {})

        if rule.match_severity and severity not in rule.match_severity:
            return False

        for key, value in rule.match_labels.items():
            if key not in labels:
                return False
            if isinstance(value, str) and value.startswith("~"):
                pattern = value[1:]
                if not re.match(pattern, str(labels.get(key, ""))):
                    return False
            elif str(labels.get(key, "")) != str(value):
                return False

        return True

if __name__ == "__main__":
    router = AlertRouter()

    # 定义路由规则
    router.add_rule(RouteRule(
        name="数据库 P0",
        match_labels={"service": "mysql", "cluster": "prod"},
        match_severity=[Severity.P0.value],
        receivers=["on-call-dba", "on-call-sre", "tech-lead"],
        escalate_after=300,  # 5 分钟未响应升级
        escalate_to=["cto", "vp-engineering"],
        auto_remediation="runbook:mysql-failover",
    ))

    router.add_rule(RouteRule(
        name="API 服务 P1",
        match_labels={"service": "~api-.*", "cluster": "prod"},
        match_severity=[Severity.P1.value, Severity.P0.value],
        receivers=["on-call-sre"],
        escalate_after=600,
        escalate_to=["tech-lead"],
        auto_remediation="runbook:api-restart",
    ))

    router.add_rule(RouteRule(
        name="磁盘空间预警",
        match_labels={"alertname": "DiskSpaceWarning"},
        match_severity=[Severity.P2.value, Severity.P3.value],
        receivers=["ops-team-im"],
        escalate_after=0,
        auto_remediation="runbook:disk-cleanup",
    ))

    # 测试路由
    test_alerts = [
        {"alertname": "MySQLDown", "severity": "critical",
         "labels": {"service": "mysql", "cluster": "prod"},
         "startsAt": "2026-07-11T02:25:59+08:00"},
        {"alertname": "HighErrorRate", "severity": "high",
         "labels": {"service": "api-gateway", "cluster": "prod"},
         "startsAt": "2026-07-11T02:25:59+08:00"},
        {"alertname": "DiskSpaceWarning", "severity": "medium",
         "labels": {"service": "web", "cluster": "prod", "instance": "10.0.1.5"},
         "startsAt": "2026-07-11T02:25:59+08:00"},
    ]

    for alert in test_alerts:
        result = router.route(alert)
        print(f"\n告警: {alert['alertname']} ({alert['severity']})")
        print(f"  接收者: {result['receivers']}")
        print(f"  自动修复: {result['auto_remediation']}")
        print(f"  升级策略: {result['escalate_after']}s → {result['escalate_to']}")

升级机制

当告警在指定时间内未被响应时,自动升级到更高级别:

#!/usr/bin/env python3
"""告警升级管理器 — 跟踪告警响应状态,超时自动升级"""

import time
import threading
from dataclasses import dataclass, field

@dataclass
class EscalationPolicy:
    """升级策略"""
    initial_receivers: list[str]
    escalate_after: int           # 秒
    escalate_to: list[str]        # 升级后的接收者
    max_escalations: int = 3      # 最大升级次数
    auto_resolve_on_action: bool = True  # 有人响应后停止升级

@dataclass
class AlertEscalation:
    """告警升级跟踪"""
    alert_id: str
    policy: EscalationPolicy
    created_at: float
    acknowledged: bool = False
    current_level: int = 0
    escalation_history: list = field(default_factory=list)

    def acknowledge(self, user: str):
        self.acknowledged = True
        self.escalation_history.append({
            "action": "acknowledged",
            "user": user,
            "timestamp": time.time(),
        })

    def escalate(self) -> list[str]:
        """执行升级,返回新的接收者列表"""
        if self.acknowledged:
            return []

        self.current_level += 1
        if self.current_level > self.policy.max_escalations:
            return ["final-escalation", "cto"]

        receivers = self.policy.escalate_to[:self.current_level]
        self.escalation_history.append({
            "action": "escalated",
            "level": self.current_level,
            "receivers": receivers,
            "timestamp": time.time(),
        })
        return receivers

class EscalationManager:
    """告警升级管理器"""

    def __init__(self):
        self.tracked: dict[str, AlertEscalation] = {}
        self._timer: threading.Timer = None

    def track(self, alert_id: str, policy: EscalationPolicy):
        """开始跟踪告警的升级"""
        escalation = AlertEscalation(
            alert_id=alert_id,
            policy=policy,
            created_at=time.time(),
        )
        self.tracked[alert_id] = escalation

        # 设置升级定时器
        timer = threading.Timer(
            policy.escalate_after,
            self._check_escalation,
            args=[alert_id]
        )
        timer.daemon = True
        timer.start()

    def _check_escalation(self, alert_id: str):
        """检查是否需要升级"""
        if alert_id not in self.tracked:
            return

        escalation = self.tracked[alert_id]
        if escalation.acknowledged:
            return

        new_receivers = escalation.escalate()
        if new_receivers:
            print(f"[升级] 告警 {alert_id} → 级别 {escalation.current_level}")
            print(f"  新接收者: {new_receivers}")
            # 这里触发实际的通知发送

    def acknowledge(self, alert_id: str, user: str):
        """确认告警"""
        if alert_id in self.tracked:
            self.tracked[alert_id].acknowledge(user)
            print(f"[确认] 告警 {alert_id} 已被 {user} 确认")

if __name__ == "__main__":
    manager = EscalationManager()

    policy = EscalationPolicy(
        initial_receivers=["on-call-sre"],
        escalate_after=5,  # 5秒后升级(演示用,实际应为 300-600 秒)
        escalate_to=["tech-lead", "vp-engineering", "cto"],
        max_escalations=3,
    )

    manager.track("EVT-001", policy)
    print("告警已跟踪,等待响应...")
    print("(未确认,5秒后自动升级)")

    time.sleep(8)  # 等待升级触发

    # 模拟确认
    # manager.acknowledge("EVT-001", "张三")

Runbook 自动化

Runbook 注册表设计

Runbook 是告警自动化的核心——将已知的故障处理流程编码为可自动执行的脚本:

#!/usr/bin/env python3
"""Runbook 注册表 — 管理和执行自动化修复脚本"""

import subprocess
import logging
import time
import json
import os
from dataclasses import dataclass, field
from typing import Optional, Callable
from enum import Enum

logger = logging.getLogger(__name__)

class RunbookStatus(Enum):
    SUCCESS = "success"
    FAILED = "failed"
    PARTIAL = "partial"
    SKIPPED = "skipped"

class RunbookRisk(Enum):
    SAFE = "safe"           # 安全操作,可自动执行
    CAUTION = "caution"     # 需谨慎,建议人工确认后执行
    DANGEROUS = "dangerous" # 高风险,必须人工确认

@dataclass
class RunbookResult:
    """Runbook 执行结果"""
    runbook_name: str
    status: RunbookStatus
    message: str
    execution_time: float
    output: str = ""
    actions_taken: list = field(default_factory=list)

@dataclass
class Runbook:
    """Runbook 定义"""
    name: str
    description: str
    risk_level: RunbookRisk
    match_conditions: dict       # 匹配的告警条件
    handler: Callable            # 处理函数
    max_retries: int = 1        # 最大重试次数
    timeout: int = 60           # 超时时间(秒)
    cooldown: int = 300         # 冷却时间(秒),同一 Runbook 的执行间隔
    last_executed: float = 0    # 上次执行时间

    def matches(self, alert: dict) -> bool:
        """检查告警是否匹配此 Runbook"""
        labels = alert.get("labels", {})
        for key, value in self.match_conditions.items():
            if key not in labels:
                return False
            if str(labels[key]) != str(value):
                return False
        return True

    def can_execute(self) -> bool:
        """检查是否可以执行(冷却时间检查)"""
        if self.last_executed == 0:
            return True
        return time.time() - self.last_executed >= self.cooldown

    def execute(self, alert: dict) -> RunbookResult:
        """执行 Runbook"""
        if not self.can_execute():
            return RunbookResult(
                runbook_name=self.name,
                status=RunbookStatus.SKIPPED,
                message=f"Runbook 处于冷却期({self.cooldown}s)",
                execution_time=0,
            )

        self.last_executed = time.time()
        start = time.time()

        for attempt in range(self.max_retries + 1):
            try:
                result = self.handler(alert)
                elapsed = time.time() - start

                if result.get("success", False):
                    return RunbookResult(
                        runbook_name=self.name,
                        status=RunbookStatus.SUCCESS,
                        message=result.get("message", "执行成功"),
                        execution_time=elapsed,
                        output=result.get("output", ""),
                        actions_taken=result.get("actions", []),
                    )
                else:
                    if attempt < self.max_retries:
                        logger.warning(f"Runbook {self.name}{attempt+1} 次执行失败,重试中...")
                        time.sleep(2 ** attempt)  # 指数退避
                    else:
                        return RunbookResult(
                            runbook_name=self.name,
                            status=RunbookStatus.FAILED,
                            message=result.get("message", "执行失败"),
                            execution_time=elapsed,
                            output=result.get("output", ""),
                            actions_taken=result.get("actions", []),
                        )
            except Exception as e:
                elapsed = time.time() - start
                logger.error(f"Runbook {self.name} 执行异常: {e}")
                if attempt >= self.max_retries:
                    return RunbookResult(
                        runbook_name=self.name,
                        status=RunbookStatus.FAILED,
                        message=f"执行异常: {str(e)}",
                        execution_time=elapsed,
                    )
                time.sleep(2 ** attempt)

        return RunbookResult(
            runbook_name=self.name,
            status=RunbookStatus.FAILED,
            message="重试次数耗尽",
            execution_time=time.time() - start,
        )

class RunbookRegistry:
    """Runbook 注册表"""

    def __init__(self):
        self.runbooks: list[Runbook] = []
        self.execution_history: list[dict] = []

    def register(self, runbook: Runbook):
        """注册 Runbook"""
        self.runbooks.append(runbook)
        logger.info(f"已注册 Runbook: {runbook.name} (风险: {runbook.risk_level.value})")

    def find_and_execute(self, alert: dict, auto_execute: bool = True) -> Optional[RunbookResult]:
        """查找匹配的 Runbook 并执行"""
        for runbook in self.runbooks:
            if runbook.matches(alert):
                if not auto_execute and runbook.risk_level != RunbookRisk.SAFE:
                    logger.info(f"Runbook {runbook.name} 风险等级 {runbook.risk_level.value},需人工确认")
                    return RunbookResult(
                        runbook_name=runbook.name,
                        status=RunbookStatus.SKIPPED,
                        message=f"风险等级 {runbook.risk_level.value},等待人工确认",
                        execution_time=0,
                    )

                logger.info(f"执行 Runbook: {runbook.name}")
                result = runbook.execute(alert)

                # 记录执行历史
                self.execution_history.append({
                    "timestamp": time.time(),
                    "runbook": runbook.name,
                    "alert": alert.get("alertname", ""),
                    "status": result.status.value,
                    "message": result.message,
                    "execution_time": result.execution_time,
                    "actions": result.actions_taken,
                })

                return result

        return None

# ====== 六大自动修复场景 ======

def handle_high_cpu(alert: dict) -> dict:
    """场景1:CPU 使用率过高 — 重启进程或扩容"""
    instance = alert.get("labels", {}).get("instance", "")
    service = alert.get("labels", {}).get("service", "")

    actions = []

    # 步骤1:采集诊断信息
    actions.append(f"采集 {instance} 的 top 进程信息")
    top_output = subprocess.run(
        ["ssh", instance, "top", "-b", "-n", "1"],
        capture_output=True, text=True, timeout=10
    ).stdout

    # 步骤2:识别高 CPU 进程
    actions.append("分析高 CPU 进程")
    if "runaway_worker" in top_output:
        # 步骤3:重启服务
        actions.append(f"重启服务 {service} on {instance}")
        subprocess.run(
            ["ssh", instance, "systemctl", "restart", service],
            timeout=30
        )
        return {"success": True, "message": f"已重启 {service} on {instance}", "actions": actions}

    return {"success": False, "message": "未识别到异常进程,需人工排查", "actions": actions}

def handle_disk_full(alert: dict) -> dict:
    """场景2:磁盘空间不足 — 清理过期日志和临时文件"""
    instance = alert.get("labels", {}).get("instance", "")
    partition = alert.get("labels", {}).get("partition", "/")

    actions = []

    # 查找大文件
    actions.append(f"扫描 {instance}:{partition} 的大文件")
    du_output = subprocess.run(
        ["ssh", instance, "du", "-sh", f"{partition}/*"],
        capture_output=True, text=True, timeout=30
    ).stdout

    # 清理策略
    cleanup_dirs = ["/var/log", "/tmp", "/var/cache/apt/archives"]
    total_cleaned = 0

    for dir_path in cleanup_dirs:
        if dir_path.startswith(partition):
            actions.append(f"清理 {dir_path} 中 7 天前的文件")
            result = subprocess.run(
                ["ssh", instance, "find", dir_path, "-type", "f", "-mtime", "+7", "-delete"],
                capture_output=True, text=True, timeout=60
            )
            actions.append(f"已清理 {dir_path}")

    # 清理 Docker 无用镜像
    actions.append("清理 Docker 无用镜像和容器")
    subprocess.run(
        ["ssh", instance, "docker", "system", "prune", "-f"],
        capture_output=True, text=True, timeout=60
    )

    # 验证磁盘空间
    df_output = subprocess.run(
        ["ssh", instance, "df", "-h", partition],
        capture_output=True, text=True, timeout=10
    ).stdout

    return {
        "success": True,
        "message": f"磁盘清理完成\n{df_output}",
        "actions": actions,
    }

def handle_memory_oom(alert: dict) -> dict:
    """场景3:内存 OOM — 重启 OOM 进程并调整限制"""
    instance = alert.get("labels", {}).get("instance", "")
    service = alert.get("labels", {}).get("service", "")

    actions = []

    # 检查 OOM 记录
    actions.append("检查 dmesg 中的 OOM 记录")
    oom_log = subprocess.run(
        ["ssh", instance, "dmesg", "-T", "|", "grep", "-i", "oom"],
        capture_output=True, text=True, timeout=10, shell=True
    ).stdout

    if "Out of memory" in oom_log or "Killed process" in oom_log:
        actions.append(f"检测到 OOM Kill,重启服务 {service}")
        subprocess.run(["ssh", instance, "systemctl", "restart", service], timeout=30)
        actions.append("增加 cgroup 内存限制")
        return {"success": True, "message": f"已处理 OOM 并重启 {service}", "actions": actions}

    return {"success": False, "message": "未检测到 OOM Kill 记录", "actions": actions}

def handle_cert_expiry(alert: dict) -> dict:
    """场景4:证书即将过期 — 自动续期"""
    instance = alert.get("labels", {}).get("instance", "")
    domain = alert.get("labels", {}).get("domain", "")

    actions = [f"为 {domain} 执行证书续期"]

    # 使用 certbot 续期
    result = subprocess.run(
        ["ssh", instance, "certbot", "renew", "--quiet", "--deploy-hook",
         "systemctl reload nginx"],
        capture_output=True, text=True, timeout=120
    )

    if result.returncode == 0:
        actions.append("证书续期成功,已重新加载 nginx")
        return {"success": True, "message": f"证书 {domain} 已自动续期", "actions": actions}
    else:
        return {"success": False, "message": f"证书续期失败: {result.stderr}", "actions": actions}

def handle_service_down(alert: dict) -> dict:
    """场景5:服务不可用 — 尝试重启并检查健康"""
    instance = alert.get("labels", {}).get("instance", "")
    service = alert.get("labels", {}).get("service", "")

    actions = []

    # 尝试重启
    actions.append(f"重启服务 {service} on {instance}")
    subprocess.run(["ssh", instance, "systemctl", "restart", service], timeout=30)

    # 等待并检查健康
    time.sleep(10)
    health_check = subprocess.run(
        ["ssh", instance, "systemctl", "is-active", service],
        capture_output=True, text=True, timeout=10
    )

    if health_check.stdout.strip() == "active":
        actions.append("服务已恢复")
        return {"success": True, "message": f"{service} 已重启并恢复", "actions": actions}
    else:
        actions.append("服务重启后仍未恢复")
        return {"success": False, "message": f"{service} 重启后仍未恢复,需人工介入",
                "actions": actions}

def handle_disk_io_high(alert: dict) -> dict:
    """场景6:磁盘 IO 过高 — 识别并限制 IO"""
    instance = alert.get("labels", {}).get("instance", "")

    actions = []

    # 识别高 IO 进程
    actions.append(f"识别 {instance} 上的高 IO 进程")
    iotop_output = subprocess.run(
        ["ssh", instance, "iotop", "-b", "-n", "1", "-o"],
        capture_output=True, text=True, timeout=10
    ).stdout

    # 使用 cgroup v2 限制 IO
    actions.append("对高 IO 进程应用 cgroup 限制")
    # 这里简化处理,实际场景需要解析 iotop 输出获取 PID

    return {"success": True, "message": "已识别并限制高 IO 进程", "actions": actions}

if __name__ == "__main__":
    registry = RunbookRegistry()

    # 注册 Runbook
    registry.register(Runbook(
        name="auto-cpu-restart",
        description="CPU 过高时自动重启服务",
        risk_level=RunbookRisk.CAUTION,
        match_conditions={"alertname": "HighCPU"},
        handler=handle_high_cpu,
        max_retries=1,
        timeout=60,
        cooldown=300,
    ))

    registry.register(Runbook(
        name="auto-disk-cleanup",
        description="磁盘空间不足时自动清理",
        risk_level=RunbookRisk.SAFE,
        match_conditions={"alertname": "DiskSpaceWarning"},
        handler=handle_disk_full,
        max_retries=1,
        timeout=120,
        cooldown=600,
    ))

    registry.register(Runbook(
        name="auto-oom-restart",
        description="OOM 时自动重启并调整内存限制",
        risk_level=RunbookRisk.CAUTION,
        match_conditions={"alertname": "OOMKilled"},
        handler=handle_memory_oom,
        max_retries=1,
        timeout=30,
        cooldown=300,
    ))

    registry.register(Runbook(
        name="auto-cert-renew",
        description="证书即将过期时自动续期",
        risk_level=RunbookRisk.SAFE,
        match_conditions={"alertname": "CertExpiringSoon"},
        handler=handle_cert_expiry,
        max_retries=1,
        timeout=120,
        cooldown=3600,
    ))

    registry.register(Runbook(
        name="auto-service-restart",
        description="服务不可用时自动重启",
        risk_level=RunbookRisk.CAUTION,
        match_conditions={"alertname": "ServiceDown"},
        handler=handle_service_down,
        max_retries=2,
        timeout=30,
        cooldown=300,
    ))

    registry.register(Runbook(
        name="auto-io-throttle",
        description="IO 过高时自动限制",
        risk_level=RunbookRisk.SAFE,
        match_conditions={"alertname": "HighDiskIO"},
        handler=handle_disk_io_high,
        max_retries=1,
        timeout=30,
        cooldown=300,
    ))

    # 模拟告警触发 Runbook
    test_alert = {
        "alertname": "DiskSpaceWarning",
        "severity": "medium",
        "labels": {
            "instance": "10.0.1.5",
            "service": "web-server",
            "partition": "/",
        },
    }

    print(f"收到告警: {test_alert['alertname']}")
    result = registry.find_and_execute(test_alert, auto_execute=True)
    if result:
        print(f"Runbook: {result.runbook_name}")
        print(f"状态: {result.status.value}")
        print(f"消息: {result.message}")
        print(f"执行时间: {result.execution_time:.2f}s")
        print(f"执行动作: {result.actions_taken}")

安全机制:白名单与限流

自动修复必须是安全的。以下安全机制确保自动化不会造成更大故障:

#!/usr/bin/env python3
"""自动化安全防护层 — 白名单、限流、熔断"""

import time
from collections import defaultdict
from dataclasses import dataclass, field
from threading import Lock

@dataclass
class CircuitBreaker:
    """熔断器 — 连续失败时熔断自动修复"""
    failure_threshold: int = 3      # 连续失败阈值
    recovery_timeout: int = 600     # 熔断恢复时间(秒)
    failure_count: int = 0
    last_failure_time: float = 0
    state: str = "closed"           # closed / open / half-open
    lock: Lock = field(default_factory=Lock)

    def can_execute(self) -> bool:
        with self.lock:
            if self.state == "open":
                # 检查是否可以进入 half-open
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = "half-open"
                    return True
                return False
            return True

    def record_success(self):
        with self.lock:
            self.failure_count = 0
            self.state = "closed"

    def record_failure(self):
        with self.lock:
            self.failure_count += 1
            self.last_failure_time = time.time()
            if self.failure_count >= self.failure_threshold:
                self.state = "open"
                print(f"[熔断] 连续失败 {self.failure_count} 次,熔断 {self.recovery_timeout}s")

class RateLimiter:
    """限流器 — 限制同一 Runbook 的执行频率"""

    def __init__(self):
        self.executions: dict[str, list[float]] = defaultdict(list)
        self.lock = Lock()

    def can_execute(self, runbook_name: str, max_per_hour: int = 5) -> bool:
        """检查是否允许执行(每小时最多 max_per_hour 次)"""
        with self.lock:
            now = time.time()
            # 清理1小时前的记录
            self.executions[runbook_name] = [
                t for t in self.executions[runbook_name]
                if now - t < 3600
            ]
            if len(self.executions[runbook_name]) >= max_per_hour:
                return False
            self.executions[runbook_name].append(now)
            return True

class SafeExecutor:
    """安全执行器 — 集成白名单、限流、熔断"""

    def __init__(self):
        self.circuit_breakers: dict[str, CircuitBreaker] = defaultdict(CircuitBreaker)
        self.rate_limiter = RateLimiter()
        # 白名单:只允许对特定服务执行自动修复
        self.service_whitelist = {
            "nginx", "redis", "web-server", "api-gateway",
            "log-collector", "metric-exporter",
        }
        # 黑名单:永远不自动修复的服务
        self.service_blacklist = {
            "mysql-master", "postgresql-primary", "etcd",
            "consul", "zookeeper",
        }

    def can_execute(self, runbook_name: str, alert: dict) -> tuple[bool, str]:
        """检查是否可以安全执行"""
        labels = alert.get("labels", {})
        service = labels.get("service", "")

        # 检查黑名单
        if service in self.service_blacklist:
            return False, f"服务 {service} 在黑名单中,禁止自动修复"

        # 检查白名单
        if service and service not in self.service_whitelist:
            return False, f"服务 {service} 不在白名单中,需人工确认"

        # 检查熔断器
        cb = self.circuit_breakers[runbook_name]
        if not cb.can_execute():
            return False, f"Runbook {runbook_name} 已熔断,等待恢复"

        # 检查限流
        if not self.rate_limiter.can_execute(runbook_name, max_per_hour=5):
            return False, f"Runbook {runbook_name} 已达每小时执行上限"

        return True, "允许执行"

    def record_result(self, runbook_name: str, success: bool):
        """记录执行结果"""
        cb = self.circuit_breakers[runbook_name]
        if success:
            cb.record_success()
        else:
            cb.record_failure()

if __name__ == "__main__":
    executor = SafeExecutor()

    # 测试白名单检查
    alerts = [
        {"alertname": "HighCPU", "labels": {"service": "nginx"}},
        {"alertname": "HighCPU", "labels": {"service": "mysql-master"}},
        {"alertname": "HighCPU", "labels": {"service": "unknown-service"}},
    ]

    for alert in alerts:
        can_run, reason = executor.can_execute("auto-cpu-restart", alert)
        status = "允许" if can_run else "拒绝"
        print(f"[{status}] {alert['labels']['service']:20s} | {reason}")

自愈平台架构设计

整体架构

┌──────────────────────────────────────────────────────────────────────────┐
│                          自愈平台架构                                      │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │ Prometheus   │  │ ELK Stack   │  │ APM (Jaeger)│  │ Zabbix      │    │
│  │ (Metrics)    │  │ (Logs)      │  │ (Traces)    │  │ (Infra)     │    │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘    │
│         │                 │                 │                 │            │
│         ▼                 ▼                 ▼                 ▼            │
│  ┌──────────────────────────────────────────────────────────────────┐    │
│  │                    告警接入层 (Alert Ingestion)                    │    │
│  │         Webhook Receiver | API Gateway | Message Queue            │    │
│  └──────────────────────────────┬───────────────────────────────────┘    │
│                                 │                                        │
│                                 ▼                                        │
│  ┌──────────────────────────────────────────────────────────────────┐    │
│  │                    告警处理层 (Alert Processing)                   │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐    │    │
│  │  │ 去重引擎  │  │ 关联引擎  │  │ 抑制引擎  │  │ 静默管理     │    │    │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────────┘    │    │
│  └──────────────────────────────┬───────────────────────────────────┘    │
│                                 │                                        │
│                                 ▼                                        │
│  ┌──────────────────────────────────────────────────────────────────┐    │
│  │                    路由决策层 (Routing & Dispatch)                  │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐    │    │
│  │  │ 分级引擎  │  │ 路由规则  │  │ 升级管理  │  │ 通知分发     │    │    │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────────┘    │    │
│  └──────────────────────────────┬───────────────────────────────────┘    │
│                                 │                                        │
│                  ┌──────────────┴──────────────┐                        │
│                  ▼                              ▼                        │
│  ┌──────────────────────┐     ┌──────────────────────────────────┐     │
│  │  自动修复层            │     │  人工通知层                       │     │
│  │  ┌──────────────────┐ │     │  ┌──────────────────────────┐   │     │
│  │  │ Runbook Registry │ │     │  │ PagerDuty / 飞书 / 短信   │   │     │
│  │  │ (匹配+执行)       │ │     │  └──────────────────────────┘   │     │
│  │  └────────┬─────────┘ │     └──────────────────────────────────┘     │
│  │           │           │                                              │
│  │  ┌────────▼─────────┐ │                                              │
│  │  │ Safe Executor    │ │  (白名单+限流+熔断)                           │
│  │  └────────┬─────────┘ │                                              │
│  │           │           │                                              │
│  │  ┌────────▼─────────┐ │                                              │
│  │  │ Action Executor  │ │  (SSH/API/K8s/Cloud)                        │
│  │  └────────┬─────────┘ │                                              │
│  └───────────┼──────────┘                                              │
│              │                                                           │
│              ▼                                                           │
│  ┌──────────────────────────────────────────────────────────────────┐    │
│  │                    验证与反馈层 (Verify & Feedback)                 │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐    │    │
│  │  │ 健康检查  │  │ 效果验证  │  │ 失败回滚  │  │ 事件归档     │    │    │
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────────┘    │    │
│  └──────────────────────────────────────────────────────────────────┘    │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

核心服务实现

以下是自愈平台的核心服务骨架,用 Go 语言实现(适合运维团队部署):

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"
)

// Alert 告警结构
type Alert struct {
	ID        string            `json:"id"`
	AlertName string            `json:"alertname"`
	Severity  string            `json:"severity"`
	Labels    map[string]string `json:"labels"`
	Message   string            `json:"message"`
	StartsAt  time.Time         `json:"startsAt"`
}

// RemediationAction 修复动作
type RemediationAction struct {
	Name       string                 `json:"name"`
	Type       string                 `json:"type"` // ssh, api, k8s, cloud
	Params     map[string]interface{} `json:"params"`
	Result     string                 `json:"result"`
	ExecutedAt time.Time              `json:"executedAt"`
}

// RemediationResult 修复结果
type RemediationResult struct {
	AlertID    string              `json:"alertId"`
	Success    bool                `json:"success"`
	Message    string              `json:"message"`
	Actions    []RemediationAction `json:"actions"`
	Duration   float64             `json:"duration"`
	Timestamp  time.Time           `json:"timestamp"`
}

// AutoRemediationPlatform 自愈平台核心
type AutoRemediationPlatform struct {
	mu             sync.RWMutex
	deduplicator   *Deduplicator
	correlator     *Correlator
	router         *Router
	runbookReg     *RunbookRegistry
	safeExecutor   *SafeExecutor
	verifier       *HealthVerifier
	eventStore     []RemediationResult
}

// HandleAlert 处理告警的完整流程
func (p *AutoRemediationPlatform) HandleAlert(alert Alert) (*RemediationResult, error) {
	start := time.Now()

	// 1. 去重检查
	if p.deduplicator.IsDuplicate(alert) {
		return &RemediationResult{
			AlertID:   alert.ID,
			Success:   true,
			Message:   "告警已去重,无需处理",
			Timestamp: time.Now(),
		}, nil
	}

	// 2. 关联检查
	event := p.correlator.Correlate(alert)

	// 3. 路由决策
	route := p.router.Route(alert)

	// 4. 尝试自动修复
	var result *RemediationResult
	if route.AutoRemediation != "" {
		canExecute, reason := p.safeExecutor.Check(route.AutoRemediation, alert)
		if canExecute {
			result = p.runbookReg.Execute(route.AutoRemediation, alert)
			p.safeExecutor.RecordResult(route.AutoRemediation, result.Success)

			// 5. 验证修复效果
			if result.Success {
				verified := p.verifier.Verify(alert)
				if !verified {
					result.Success = false
					result.Message = "修复执行成功但验证未通过,需人工检查"
				}
			}
		} else {
			log.Printf("[安全拦截] Runbook %s 被拦截: %s", route.AutoRemediation, reason)
		}
	}

	// 6. 如果自动修复失败或未配置,发送人工通知
	if result == nil || !result.Success {
		p.notifyHumans(route, alert)
	}

	// 7. 归档
	result.Duration = time.Since(start).Seconds()
	result.Timestamp = time.Now()
	p.storeEvent(*result)

	return result, nil
}

func (p *AutoRemediationPlatform) notifyHumans(route *RouteResult, alert Alert) {
	log.Printf("[人工通知] 告警 %s → %v (升级: %ds → %v)",
		alert.AlertName, route.Receivers, route.EscalateAfter, route.EscalateTo)
}

func (p *AutoRemediationPlatform) storeEvent(result RemediationResult) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.eventStore = append(p.eventStore, result)
}

// WebhookHandler Alertmanager webhook 接收器
func (p *AutoRemediationPlatform) WebhookHandler(w http.ResponseWriter, r *http.Request) {
	var payload struct {
		Alerts []Alert `json:"alerts"`
	}

	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	for _, alert := range payload.Alerts {
		go func(a Alert) {
			result, err := p.HandleAlert(a)
			if err != nil {
				log.Printf("[错误] 处理告警 %s 失败: %v", a.AlertName, err)
			} else {
				log.Printf("[完成] 告警 %s → %s (%.2fs)",
					a.AlertName, result.Message, result.Duration)
			}
		}(alert)
	}

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}

func main() {
	platform := &AutoRemediationPlatform{
		deduplicator: NewDeduplicator(300),
		correlator:   NewCorrelator(),
		router:       NewRouter(),
		runbookReg:   NewRunbookRegistry(),
		safeExecutor: NewSafeExecutor(),
		verifier:     NewHealthVerifier(),
	}

	http.HandleFunc("/api/v1/alerts", platform.WebhookHandler)

	// 健康检查
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, "OK")
	})

	// 统计接口
	http.HandleFunc("/api/v1/stats", func(w http.ResponseWriter, r *http.Request) {
		platform.mu.RLock()
		defer platform.mu.RUnlock()

		total := len(platform.eventStore)
		success := 0
		for _, e := range platform.eventStore {
			if e.Success {
				success++
			}
		}

		json.NewEncoder(w).Encode(map[string]interface{}{
			"total_events":  total,
			"success_count": success,
			"success_rate":  func() float64 {
				if total == 0 {
					return 0
				}
				return float64(success) / float64(total) * 100
			}(),
		})
	})

	log.Println("自愈平台启动,监听 :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

// 以下是各组件的简化实现(实际项目中应有完整实现)

type Deduplicator struct{ window int }
type Correlator struct{}
type Router struct{}
type RouteResult struct {
	Receivers        []string
	EscalateAfter    int
	EscalateTo       []string
	AutoRemediation  string
}
type RunbookRegistry struct{}
type SafeExecutor struct{}
type HealthVerifier struct{}

func NewDeduplicator(w int) *Deduplicator { return &Deduplicator{window: w} }
func (d *Deduplicator) IsDuplicate(a Alert) bool { return false }
func NewCorrelator() *Correlator { return &Correlator{} }
func (c *Correlator) Correlate(a Alert) interface{} { return nil }
func NewRouter() *Router { return &Router{} }
func (r *Router) Route(a Alert) *RouteResult {
	return &RouteResult{Receivers: []string{"on-call"}, AutoRemediation: ""}
}
func NewRunbookRegistry() *RunbookRegistry { return &RunbookRegistry{} }
func (r *RunbookRegistry) Execute(name string, a Alert) *RemediationResult {
	return &RemediationResult{Success: false, Message: "no runbook matched"}
}
func NewSafeExecutor() *SafeExecutor { return &SafeExecutor{} }
func (s *SafeExecutor) Check(name string, a Alert) (bool, string) { return false, "safe check" }
func (s *SafeExecutor) RecordResult(name string, success bool) {}
func NewHealthVerifier() *HealthVerifier { return &HealthVerifier{} }
func (h *HealthVerifier) Verify(a Alert) bool { return true }

Docker 一键部署

# docker-compose.yml — 自愈平台部署
version: '3.8'

services:
  remediation-platform:
    build: .
    container_name: auto-remediation
    ports:
      - "8080:8080"
    volumes:
      - ./config:/app/config
      - ./runbooks:/app/runbooks
      - ./logs:/app/logs
    environment:
      - LOG_LEVEL=info
      - ALERTMANAGER_URL=http://alertmanager:9093
      - PROMETHEUS_URL=http://prometheus:9090
      - GRAFANA_URL=http://grafana:3000
    networks:
      - monitoring
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  alertmanager:
    image: prom/alertmanager:latest
    container_name: alertmanager
    ports:
      - "9093:9093"
    volumes:
      - ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    networks:
      - monitoring
    restart: unless-stopped

networks:
  monitoring:
    driver: bridge

AI 辅助告警治理

从规则到智能

传统的告警自动化基于静态规则(if-then),而 AI 辅助告警治理可以引入动态学习和自适应能力:

能力维度规则驱动AI 辅助
异常检测静态阈值动态基线 + 异常检测算法
告警分类人工定义规则自动分类 + 语义理解
根因分析预定义关联规则因果推理 + 拓扑分析
修复建议固定 Runbook基于上下文生成修复策略
告警优化人工调参自动调优 + 反馈学习

基于 LLM 的告警诊断

#!/usr/bin/env python3
"""AI 告警诊断引擎 — 利用 LLM 理解告警上下文并生成修复建议"""

import json
import requests
from dataclasses import dataclass

@dataclass
class DiagnosticResult:
    """诊断结果"""
    root_cause: str          # 根因分析
    severity_assessment: str # 严重度评估
    impact_analysis: str    # 影响面分析
    remediation_plan: str    # 修复建议
    confidence: float        # 置信度

class AlertDiagnosticEngine:
    """告警诊断引擎"""

    def __init__(self, llm_api_url: str, llm_api_key: str):
        self.llm_api_url = llm_api_url
        self.llm_api_key = llm_api_key

    def diagnose(self, alert: dict, context: dict) -> DiagnosticResult:
        """
        告警诊断
        Args:
            alert: 告警信息
            context: 上下文信息(监控指标、日志、拓扑等)
        """
        prompt = self._build_prompt(alert, context)

        # 调用 LLM 进行诊断
        response = self._call_llm(prompt)

        return self._parse_response(response)

    def _build_prompt(self, alert: dict, context: dict) -> str:
        """构建诊断 prompt"""
        return f"""你是一位资深 SRE 工程师,请分析以下告警并给出诊断。

## 告警信息
- 名称: {alert.get('alertname', '')}
- 严重度: {alert.get('severity', '')}
- 服务: {alert.get('labels', {}).get('service', '')}
- 实例: {alert.get('labels', {}).get('instance', '')}
- 消息: {alert.get('message', '')}

## 上下文信息
- 相关指标:
{json.dumps(context.get('metrics', {}), indent=2, ensure_ascii=False)}
- 相关日志(最近5分钟):
{context.get('recent_logs', '无')}
- 服务拓扑:
{context.get('topology', '无')}
- 最近变更:
{context.get('recent_changes', '无')}

## 请输出
1. 根因分析:最可能的故障原因
2. 严重度评估:基于影响面重新评估
3. 影响面分析:哪些服务和用户可能受影响
4. 修复建议:具体的修复步骤(优先自动化的方案)

请以 JSON 格式输出:
{{"root_cause": "...", "severity_assessment": "...", "impact_analysis": "...", "remediation_plan": "...", "confidence": 0.0-1.0}}
"""

    def _call_llm(self, prompt: str) -> str:
        """调用 LLM API"""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.llm_api_key}",
        }
        payload = {
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.3,  # 低温度,确保输出稳定
            "max_tokens": 2000,
        }
        response = requests.post(
            self.llm_api_url,
            headers=headers,
            json=payload,
            timeout=30
        )
        response.raise_for_status()
        return response.json()["choices"][0]["message"]["content"]

    def _parse_response(self, response: str) -> DiagnosticResult:
        """解析 LLM 响应"""
        try:
            # 尝试提取 JSON
            start = response.find("{")
            end = response.rfind("}") + 1
            if start >= 0 and end > start:
                data = json.loads(response[start:end])
                return DiagnosticResult(
                    root_cause=data.get("root_cause", ""),
                    severity_assessment=data.get("severity_assessment", ""),
                    impact_analysis=data.get("impact_analysis", ""),
                    remediation_plan=data.get("remediation_plan", ""),
                    confidence=data.get("confidence", 0.5),
                )
        except (json.JSONDecodeError, KeyError) as e:
            pass

        # 降级:返回原始文本
        return DiagnosticResult(
            root_cause=response[:500],
            severity_assessment="unknown",
            impact_analysis="unknown",
            remediation_plan="需人工分析",
            confidence=0.3,
        )

if __name__ == "__main__":
    engine = AlertDiagnosticEngine(
        llm_api_url="https://api.example.com/v1/chat/completions",
        llm_api_key="your-api-key"
    )

    alert = {
        "alertname": "HighErrorRate",
        "severity": "critical",
        "labels": {"service": "payment-api", "instance": "10.0.1.5:8080"},
        "message": "错误率 15.2%,持续 3 分钟",
    }

    context = {
        "metrics": {
            "error_rate": 0.152,
            "p99_latency_ms": 3500,
            "qps": 1200,
            "cpu_usage": 0.89,
            "memory_usage": 0.92,
        },
        "recent_logs": "[ERROR] 2026-07-11 02:20:01 connection refused: db-pool exhausted",
        "topology": "payment-api → redis-cluster → mysql-primary",
        "recent_changes": "2小时前部署了 payment-api v2.3.1",
    }

    result = engine.diagnose(alert, context)
    print(f"根因: {result.root_cause}")
    print(f"严重度: {result.severity_assessment}")
    print(f"影响: {result.impact_analysis}")
    print(f"修复建议: {result.remediation_plan}")
    print(f"置信度: {result.confidence}")

告警反馈闭环

AI 诊断的准确性依赖持续学习。以下是一个完整的告警反馈闭环:

#!/usr/bin/env python3
"""告警反馈闭环 — 持续学习改进告警质量"""

import time
import json
from dataclasses import dataclass, field, asdict
from pathlib import Path

@dataclass
class AlertFeedback:
    """告警反馈记录"""
    alert_id: str
    alert_name: str
    severity: str
    was_actionable: bool        # 告警是否需要人工处理
    was_auto_resolved: bool    # 是否被自动修复解决
    false_positive: bool       # 是否误报
    resolution_time: float     # 解决耗时(分钟)
    root_cause: str            # 根因
    action_taken: str          # 实际处理操作
    operator: str              # 处理人
    feedback: str              # 附加反馈
    timestamp: float = field(default_factory=time.time)

class FeedbackLoop:
    """告警反馈闭环管理器"""

    def __init__(self, storage_path: str = "/data/feedback"):
        self.storage_path = Path(storage_path)
        self.storage_path.mkdir(parents=True, exist_ok=True)
        self.feedback_store: list[AlertFeedback] = []
        self._load()

    def record(self, feedback: AlertFeedback):
        """记录反馈"""
        self.feedback_store.append(feedback)
        self._save(feedback)
        self._analyze_patterns()

    def _save(self, feedback: AlertFeedback):
        """持久化反馈记录"""
        filepath = self.storage_path / f"{feedback.alert_id}.json"
        filepath.write_text(json.dumps(asdict(feedback), indent=2, ensure_ascii=False))

    def _load(self):
        """加载历史反馈"""
        for filepath in self.storage_path.glob("*.json"):
            data = json.loads(filepath.read_text())
            self.feedback_store.append(AlertFeedback(**data))

    def _analyze_patterns(self):
        """分析反馈模式,输出改进建议"""
        if len(self.feedback_store) < 10:
            return

        total = len(self.feedback_store)
        false_positives = sum(1 for f in self.feedback_store if f.false_positive)
        auto_resolved = sum(1 for f in self.feedback_store if f.was_auto_resolved)

        fp_rate = false_positives / total * 100
        auto_rate = auto_resolved / total * 100

        print(f"\n=== 告警质量报告 ===")
        print(f"总告警数: {total}")
        print(f"误报率: {fp_rate:.1f}%")
        print(f"自动修复率: {auto_rate:.1f}%")
        print(f"平均解决时间: {sum(f.resolution_time for f in self.feedback_store)/total:.1f} 分钟")

        # 按告警类型统计误报
        by_name = {}
        for f in self.feedback_store:
            if f.alert_name not in by_name:
                by_name[f.alert_name] = {"total": 0, "fp": 0}
            by_name[f.alert_name]["total"] += 1
            if f.false_positive:
                by_name[f.alert_name]["fp"] += 1

        print(f"\n=== 高误报告警 TOP 5 ===")
        sorted_alerts = sorted(
            by_name.items(),
            key=lambda x: x[1]["fp"] / max(x[1]["total"], 1),
            reverse=True
        )[:5]
        for name, stats in sorted_alerts:
            rate = stats["fp"] / stats["total"] * 100
            print(f"  {name:30s} | 误报率 {rate:.0f}% ({stats['fp']}/{stats['total']})")

        # 改进建议
        if fp_rate > 20:
            print("\n[建议] 误报率过高,建议调整告警阈值或增加前置条件")
        if auto_rate < 30:
            print("[建议] 自动修复率低,建议为高频告警增加 Runbook")

效果度量

关键指标

指标定义目标值度量方法
平均告警数每天产生的告警总量< 20 条/天告警系统统计
有效告警率需要人工处理的告警占比> 70%人工标注
误报率不需要处理的告警占比< 10%人工标注
自动修复率被自动 Runbook 解决的告警占比> 50%自动统计
平均响应时间(MTTA)从告警到开始处理的时间< 5 分钟系统记录
平均修复时间(MTTR)从告警到问题解决的时间< 15 分钟系统记录
告警疲劳指数工程师忽略告警的比例< 5%人工调研

度量看板

#!/usr/bin/env python3
"""告警自动化效果度量看板"""

import time
import json
from dataclasses import dataclass, field
from collections import defaultdict

@dataclass
class AlertMetrics:
    """告警度量数据"""
    total_alerts: int = 0
    actionable_alerts: int = 0       # 需要人工处理的
    false_positives: int = 0         # 误报
    auto_resolved: int = 0            # 自动修复
    escalated: int = 0               # 升级到人工
    mtta_seconds: float = 0          # 平均响应时间
    mttr_seconds: float = 0          # 平均修复时间
    by_severity: dict = field(default_factory=lambda: defaultdict(int))
    by_service: dict = field(default_factory=lambda: defaultdict(int))
    by_runbook: dict = field(default_factory=lambda: {"executed": 0, "success": 0})

    def to_dict(self) -> dict:
        return {
            "total_alerts": self.total_alerts,
            "actionable_alerts": self.actionable_alerts,
            "false_positive_rate": f"{self.false_positives/max(self.total_alerts,1)*100:.1f}%",
            "auto_resolution_rate": f"{self.auto_resolved/max(self.total_alerts,1)*100:.1f}%",
            "mtta": f"{self.mtta_seconds:.0f}s",
            "mttr": f"{self.mttr_seconds:.0f}s",
            "by_severity": dict(self.by_severity),
            "by_service": dict(self.by_service),
            "runbook_stats": dict(self.by_runbook),
        }

if __name__ == "__main__":
    metrics = AlertMetrics()

    # 模拟数据
    metrics.total_alerts = 156
    metrics.actionable_alerts = 98
    metrics.false_positives = 12
    metrics.auto_resolved = 58
    metrics.escalated = 40
    metrics.mtta_seconds = 45
    metrics.mttr_seconds = 480
    metrics.by_severity = {"critical": 8, "high": 25, "medium": 68, "low": 55}
    metrics.by_service = {"api-gw": 35, "db": 12, "web": 45, "cache": 28, "other": 36}
    metrics.by_runbook = {"executed": 58, "success": 45}

    print("=== 告警自动化效果看板 ===")
    print(json.dumps(metrics.to_dict(), indent=2, ensure_ascii=False))

    print(f"\n=== 核心指标 ===")
    print(f"告警总量:       {metrics.total_alerts} 条/天")
    print(f"有效告警率:     {metrics.actionable_alerts/metrics.total_alerts*100:.1f}%")
    print(f"误报率:         {metrics.false_positives/metrics.total_alerts*100:.1f}%")
    print(f"自动修复率:     {metrics.auto_resolved/metrics.total_alerts*100:.1f}%")
    print(f"Runbook成功率:  {metrics.by_runbook['success']/max(metrics.by_runbook['executed'],1)*100:.1f}%")
    print(f"平均响应时间:   {metrics.mtta_seconds:.0f}s")
    print(f"平均修复时间:   {metrics.mttr_seconds/60:.1f} 分钟")

总结

告警自动化不是一蹴而就的工程,而是一个从"噪声到信号、从信号到行动、从行动到自愈"的渐进式建设过程。

核心建设路径:

  1. 告警降噪是基础:通过 Alertmanager 的分组、抑制和静默,配合自定义的指纹去重和多维度关联,将告警风暴压缩为可管理的事件。目标是让工程师每天看到的告警从 100+ 条降到 20 条以内,有效告警率超过 70%。

  2. 分级路由是骨架:建立统一的 P0-P3 分级标准,配合路由规则和升级机制,确保每条告警在正确的时间到达正确的人。关键是在自动修复和人工介入之间找到平衡——安全操作自动执行,高风险操作人工确认。

  3. Runbook 自动化是核心:将已知的故障处理流程编码为可自动执行的 Runbook,覆盖 CPU 过高、磁盘满、OOM、服务不可用、证书过期、IO 过高等高频场景。配合白名单、限流和熔断机制确保安全。

  4. 自愈平台是载体:构建统一的告警接入、处理、路由、修复、验证平台,将分散的自动化能力整合为完整的自愈链路。关键设计是"修复后验证"——不仅执行修复,还要验证修复效果,失败时自动回滚并升级人工。

  5. AI 辅助是加速器:在规则驱动的基础上引入 AI 能力,利用 LLM 进行告警诊断、根因分析和修复建议生成。但 AI 不是替代规则,而是在规则无法覆盖的场景下提供智能辅助,并通过反馈闭环持续学习。

最终目标是构建一个比最优秀工程师更快响应的可靠性系统——在故障影响用户之前自动检测、分析并修复,让 SRE 从"救火队"变为"可靠性工程师"。