首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MTTR 从 45 分钟降到 5 分钟:我们是怎么用 AI 实现故障自动修复闭环的

MTTR 从 45 分钟降到 5 分钟:我们是怎么用 AI 实现故障自动修复闭环的

作者头像
行者全栈架构师
发布2026-07-15 15:13:26
发布2026-07-15 15:13:26
1420
举报

凌晨 2 点,告警响了——OOM 杀死。你从床上爬起来,SSH 登录、看日志、重启 Pod、等恢复。整套动作重复了三个月,一模一样的问题,一模一样的操作。能不能让机器自己来?

01 80% 的故障都是重复模式,为什么还要等人来修?

我负责的订单中心集群有 30+ 微服务,每次故障处理流程是这样的:

步骤

操作

耗时

1. 告警通知

飞书/短信接收

2 分钟

2. 登录排查

SSH 进服务器看日志

8 分钟

3. 定位根因

多日志源关联分析

15 分钟

4. 执行修复

重启/扩容/回滚

10 分钟

5. 验证恢复

确认指标恢复正常

10 分钟

总计

45 分钟

最抓狂的是什么?80% 的故障都是重复模式——OOM 重启、连接池耗尽、磁盘满——修复动作完全一样,却每次都要人工走一遍。

运维最宝贵的资源是人的注意力,而不是人的手。把"手"的工作交给机器,让人的注意力聚焦在真正的异常上——这就是故障自愈的核心理念。

图:自愈链路可视化,从 Prometheus 告警到 K8s 执行、验证恢复的端到端闭环,本事件总耗时 3m 24s

02 自愈架构:告警进来,修复出去,中间不需要人

核心思路很简单:把运维经验和决策逻辑变成可编程的 Runbook,让 AI 分类器决定走自动还是走人工。

自愈 vs 人工修复,差距在哪?

维度

人工修复

AI 自愈

响应速度

2-5 分钟(人看到通知)

< 10 秒(自动触发)

修复执行

手动输入命令

Runbook 自动化

风险控制

人工判断

分级审批 + 回滚保护

一致性

因人而异

标准化流程

可审计

口头汇报

完整操作日志

03 Runbook 自动化引擎:把运维经验写成可执行的 YAML

为什么要画 Runbook 执行流程图?Runbook 的核心价值在于"步骤编排 + 条件分支 + 自动回滚",纯文字和代码很难直观体现分支决策逻辑。流程图能让读者一眼看清"匹配 → 采集 → 诊断 → 修复 → 回滚"的完整链路,也方便团队评审 Runbook 设计是否合理。

Runbook 配置结构

为什么用 YAML 而不是硬编码?运维修复策略需要频繁调整,YAML 配置让非开发人员也能维护 Runbook。

代码语言:javascript
复制
# runbook-oom-restart.yaml
apiVersion: selfhealing/v1
kind: Runbook
metadata:
  name: oom-auto-restart
  labels:
    alertType: OOMKilled
    severity: P2
    riskLevel: low
spec:
  trigger:
    alertName: OOMKilled
    matchLabels:
      app: "{{ .Labels.app }}"
  steps:
    - name: 检查最近重启次数
      action: check-restart-count
      params:
        namespace: "{{ .Labels.namespace }}"
        pod: "{{ .Labels.pod }}"
        windowMinutes: 10
        maxRestarts: 3

    - name: 记录当前内存配置
      action: get-resource-limits
      params:
        namespace: "{{ .Labels.namespace }}"
        deployment: "{{ .Labels.deployment }}"

    - name: 提升内存限制
      action: patch-deployment
      params:
        memoryLimit: "2Gi"  # 原 1Gi → 2Gi
        memoryRequest: "1Gi"
      rollback:
        memoryLimit: "1Gi"
        memoryRequest: "512Mi"

    - name: 验证服务恢复
      action: health-check
      params:
        endpoint: "http://{{ .Labels.app }}:8080/actuator/health"
        timeoutSeconds: 60
        expectedStatus: 200

Runbook 执行引擎实现

为什么用 Python 而不是 Shell 脚本?Python 可以更好地处理条件分支、异常捕获和回滚逻辑,Shell 在复杂流程控制上力不从心。

代码语言:javascript
复制
#!/usr/bin/env python3
"""
Runbook 执行引擎
支持步骤编排、条件分支、自动回滚
"""
import subprocess
import json
import logging
from datetime import datetime

logger = logging.getLogger("runbook-engine")

class RunbookExecutor:
    def __init__(self, runbook: dict, context: dict):
        self.runbook = runbook
        self.context = context
        self.executed_steps = []  # 记录已执行步骤,用于回滚
        self.start_time = datetime.now()

    def execute(self) -> dict:
        """执行 Runbook 全部步骤"""
        steps = self.runbook["spec"]["steps"]
        for step in steps:
            try:
                result = self._execute_step(step)
                self.executed_steps.append({
                    "step": step["name"],
                    "status": "success",
                    "result": result,
                    "rollback": step.get("rollback")
                })
                logger.info(f"步骤 [{step['name']}] 执行成功")
            except Exception as e:
                logger.error(f"步骤 [{step['name']}] 执行失败: {e}")
                self._rollback()
                return {"status": "failed", "error": str(e)}

        return {
            "status": "success",
            "steps_completed": len(self.executed_steps),
            "duration_sec": (datetime.now() - self.start_time).seconds
        }

    def _execute_step(self, step: dict) -> dict:
        """执行单个步骤"""
        action = step["action"]
        params = step.get("params", {})

        if action == "check-restart-count":
            return self._check_restart_count(params)
        elif action == "patch-deployment":
            return self._patch_deployment(params)
        elif action == "health-check":
            return self._health_check(params)
        else:
            raise ValueError(f"未知 action: {action}")

    def _check_restart_count(self, params: dict) -> dict:
        """检查 Pod 最近重启次数"""
        ns = params["namespace"]
        pod = params["pod"]
        window = params["windowMinutes"]
        max_count = params["maxRestarts"]

        cmd = f"kubectl get pod {pod} -n {ns} -o json"
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        pod_info = json.loads(result.stdout)
        restarts = pod_info["status"]["containerStatuses"][0]["restartCount"]

        if restarts >= max_count:
            raise Exception(
                f"重启次数 {restarts} 超过阈值 {max_count},停止自愈,转人工"
            )
        return {"restartCount": restarts, "allowAutoFix": True}

    def _patch_deployment(self, params: dict) -> dict:
        """修改 Deployment 资源配置"""
        # 实际生产中会调用 K8s API
        logger.info(f"修改 Deployment: {params}")
        return {"patched": True}

    def _health_check(self, params: dict) -> dict:
        """服务健康检查"""
        import requests
        try:
            resp = requests.get(
                params["endpoint"],
                timeout=params.get("timeoutSeconds", 30)
            )
            return {"status": resp.status_code, "healthy": resp.status_code == 200}
        except requests.RequestException as e:
            raise Exception(f"健康检查失败: {e}")

    def _rollback(self):
        """回滚已执行的步骤"""
        logger.warning("开始回滚...")
        for step_info in reversed(self.executed_steps):
            rollback = step_info.get("rollback")
            if rollback:
                logger.info(f"回滚步骤 [{step_info['step']}]: {rollback}")
                # 执行回滚逻辑
        logger.info("回滚完成")

04 人机协同安全机制:不是所有故障都该自动修

分级审批策略

安全红线必须守住:

风险等级

修复类型

审批方式

示例

低风险

单 Pod 重启

全自动

OOM 重启、健康检查失败

中风险

资源调整/扩容

飞书审批

内存限制提升、HPA 扩容

高风险

配置变更/回滚

人工确认

环境变量修改、版本回滚

禁止自愈

数据操作/删除

仅告警

数据库 DDL、数据清理

回滚保护

为什么每次自愈都要准备回滚方案?自动修复可能引入新问题,回滚是确保安全的兜底机制。

代码语言:javascript
复制
"""回滚保护装饰器"""
from functools import wraps

def with_rollback(rollback_func):
    """为自愈步骤添加回滚保护"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                result = func(*args, **kwargs)
                # 记录回滚上下文
                rollback_ctx = {
                    "step": func.__name__,
                    "timestamp": datetime.now().isoformat(),
                    "params": kwargs
                }
                # 保存回滚信息到 Redis,TTL 1 小时
                redis.setex(
                    f"rollback:{func.__name__}",
                    3600,
                    json.dumps(rollback_ctx)
                )
                return result
            except Exception as e:
                logger.error(f"步骤 {func.__name__} 失败,触发回滚: {e}")
                rollback_func(**kwargs)
                raise
        return wrapper
    return decorator

MTTR 对比数据

故障类型

人工 MTTR

自愈 MTTR

提升幅度

OOM 重启

20 分钟

2 分钟

⬇️ 90%

连接池耗尽

35 分钟

4 分钟

⬇️ 89%

磁盘满

25 分钟

3 分钟

⬇️ 88%

配置错误

60 分钟

8 分钟

⬇️ 87%

加权平均

45 分钟

5 分钟

⬇️ 89%

05 四个踩坑实录,每一个都是真金白银换来的教训

坑 1:自愈在级联故障中火上浇油

现象:数据库主库宕机,自愈系统自动重启主库,但主库启动后数据不一致,导致更大范围故障。

原因:自愈逻辑没有考虑数据库主从关系的特殊性,盲目重启破坏了数据一致性。

解决:为数据库组件设置"禁止自愈"标签,仅触发告警通知 DBA 人工处理。

提醒:有状态服务(数据库、消息队列)的自愈要极度谨慎,推荐仅告警不修复。

坑 2:Runbook 参数模板渲染失败

现象:Runbook 的 {{ .Labels.app }} 在部分告警中为空,导致 K8s API 调用报错。

原因:某些告警的 Label 不包含 app 字段,模板渲染后变成了空字符串。

解决:为所有模板变量设置默认值,并在执行前校验必填参数:

代码语言:javascript
复制
# 执行前校验模板参数
REQUIRED_PARAMS = ["app", "namespace", "deployment"]

def validate_context(context: dict) -> bool:
    missing = [p for p in REQUIRED_PARAMS if not context.get(p)]
    if missing:
        raise ValueError(f"缺少必填参数: {missing}")
    return True

提醒:模板参数校验是自愈系统的基本防线,缺失参数宁可跳过也不能乱执行。

坑 3:自愈循环——修复触发新告警

现象:自愈提升内存限制后,Pod 重启触发 Ready 告警,又触发新一轮自愈,反复循环。

原因:自愈系统没有"冷却期"机制,修复动作产生的预期告警被当作新故障处理。

解决:自愈执行后设置 5 分钟静默窗口,期间同类告警不触发自愈:

代码语言:javascript
复制
def should_auto_heal(alert: dict) -> bool:
    """判断是否应触发自愈"""
    key = f"silence:{alert['name']}:{alert['labels']['app']}"
    if redis.exists(key):
        logger.info(f"告警 {alert['name']} 在静默期,跳过自愈")
        return False
    # 设置 5 分钟静默
    redis.setex(key, 300, "healing_in_progress")
    return True

提醒:自愈必须有冷却机制,否则就是"自动故障制造机"。

坑 4:回滚方案过期导致二次故障

现象:自愈失败触发回滚,但回滚方案使用的是旧配置,导致服务用了过期参数。

原因:回滚上下文是从自愈前快照的,但自愈过程中有其他人工变更,回滚覆盖了人工修改。

解决:回滚前先 diff 当前配置与回滚配置,有冲突时转人工决策而非强制回滚。

提醒:回滚不是"时间倒流",要考虑并发变更的情况。

图:自愈事件日志,24h 内 28 次自愈,成功率 92.9%,平均耗时 3m 24s,节省人工工时约 14h

06 自愈系统监控与自动化部署

自愈系统自身也需要监控——谁来看护看护者?

监控项

采集方式

更新间隔

告警阈值

严重级别

自愈执行成功率

Runbook 日志统计

60s

< 90%

P1

Runbook 执行超时

日志监控

60s

> 300s

P2

自愈回滚触发率

日志统计

3600s

> 5%

P1

人机审批超时未处理

审批系统状态

60s

> 300s

P0

自愈风暴检测

频次统计

60s

> 5次/10min 自动熔断

P0

代码语言:javascript
复制
# Crontab 配置:自愈 Runbook 定时健康检查
0 * * * * /opt/scripts/self_healing_health.sh  # 每小时检查自愈服务状态
0 3 * * * /opt/scripts/runbook_backup.sh       # 每天凌晨备份 Runbook
*/5 * * * * /opt/scripts/check_healing_storm.sh # 每5分钟检查自愈风暴

07 总结:谁适合上自愈,谁不适合?

故障自愈的核心价值:

  • MTTR:从 45 分钟 → 5 分钟(⬇️ 89%)
  • 重复故障处理:从人工 45 分钟 → 自动 2 分钟
  • 修复一致性:从因人而异 → 标准化 Runbook

适合谁用:高频重复故障(OOM、连接池、磁盘满)、告警量大、值班人力紧张的运维团队

不适合谁用:有状态服务(数据库主从)、数据变更操作、业务逻辑类故障——这些场景,人比机器更靠得住

💬 你的团队有故障自愈实践吗?MTTR 大概多少?评论区聊聊~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 行者架构谈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01 80% 的故障都是重复模式,为什么还要等人来修?
  • 02 自愈架构:告警进来,修复出去,中间不需要人
    • 自愈 vs 人工修复,差距在哪?
  • 03 Runbook 自动化引擎:把运维经验写成可执行的 YAML
    • Runbook 配置结构
    • Runbook 执行引擎实现
  • 04 人机协同安全机制:不是所有故障都该自动修
    • 分级审批策略
    • 回滚保护
    • MTTR 对比数据
  • 05 四个踩坑实录,每一个都是真金白银换来的教训
    • 坑 1:自愈在级联故障中火上浇油
    • 坑 2:Runbook 参数模板渲染失败
    • 坑 3:自愈循环——修复触发新告警
    • 坑 4:回滚方案过期导致二次故障
  • 06 自愈系统监控与自动化部署
  • 07 总结:谁适合上自愈,谁不适合?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档