文章

应急预案与故障演练

应急预案与故障演练

核心目标:在故障真正发生之前,通过预案体系和实战演练,让团队具备”肌肉记忆”级的应急响应能力,做到”平时多流汗,战时少出血”。

概述

应急预案(Incident Response Plan)是预定义的故障处理流程和角色分工体系;故障演练(Incident Drill/Game Day)是通过模拟真实故障场景,验证预案有效性、锻炼团队响应能力的实践方法。两者构成”准备 → 验证 → 改进”的闭环。

flowchart TB
    subgraph 预案体系
        A[故障场景库] --> B[应急预案模板]
        B --> C[角色分工矩阵]
        C --> D[升级流程]
        D --> E[沟通模板]
    end

    subgraph 演练体系
        F[演练计划] --> G[场景设计]
        G --> H[执行演练]
        H --> I[观察记录]
        I --> J[复盘改进]
    end

    E --> F
    J --> A

应急预案体系

1. 故障场景库

按故障域分类建立场景库,每个场景定义触发条件、影响范围、预案编号。

故障域场景严重度预案编号
网络DNS 解析故障SEV2IR-NET-001
网络核心交换机故障SEV1IR-NET-002
计算K8s 控制面不可用SEV1IR-K8S-001
计算节点池大规模离线SEV1IR-K8S-002
存储PV 不可挂载SEV2IR-STO-001
存储数据库主节点宕机SEV1IR-DB-001
应用核心API 5xx率飙升SEV1IR-APP-001
应用消息队列积压SEV2IR-MQ-001
安全DDoS 攻击SEV1IR-SEC-001
安全数据泄露SEV0IR-SEC-002
第三方云厂商Region故障SEV1IR-CLOUD-001
变更发布导致线上故障SEV1IR-CHG-001

2. 应急预案模板

# 应急预案: IR-DB-001 — 数据库主节点宕机

## 基本信息
- **预案编号**: IR-DB-001
- **故障级别**: SEV1
- **故障域**: 存储/数据库
- **影响范围**: 全部依赖该DB的服务
- **RTO目标**: 5分钟
- **RPO目标**: 0(零数据丢失)

## 触发条件(任一满足)
1. MySQL 主节点健康检查连续失败 3 次
2. VIP 漂移失败
3. 应用层报 "Connection refused" 错误率 > 50%

## 角色分工
| 角色 | 负责人 | 职责 | 联系方式 |
|------|--------|------|---------|
| IC (指挥) | 值班SRE | 统筹协调、决策 | 电话/钉钉 |
| DBA | DBA值班 | 执行故障切换 | 电话/钉钉 |
| Dev | 服务Owner | 应用层降级/重试 | 电话/钉钉 |
| Comms | 技术支持 | 内/外部沟通 | 电话/钉钉 |

## 响应流程
### T+0: 检测确认
- [ ] 确认告警有效性(排除误报)
- [ ] 初始定级(SEV1)
- [ ] 拉起应急群

### T+1min: 初步响应
- [ ] IC 到岗,接管指挥权
- [ ] DBA 确认主节点状态
- [ ] 判断是否需要故障切换

### T+2min: 执行故障切换
- [ ] 确认备节点数据同步状态(Seconds_Behind_Master < 5)
- [ ] 执行提升备节点为主节点
- [ ] 验证新主节点读写正常
- [ ] 更新 VIP 指向

### T+5min: 服务恢复
- [ ] 应用层连接恢复
- [ ] 核心指标回归正常
- [ ] 取消告警

### T+10min: 稳定确认
- [ ] 持续观察5分钟无异常
- [ ] 解散应急群
- [ ] 通知相关方

## 升级路径
1. T+2min 未恢复 → 升级到 SRE 主管
2. T+5min 未恢复 → 升级到技术总监
3. T+10min 未恢复 → 启动灾备切换流程

## 沟通模板
### 内部通报
> 【SEV1 故障】数据库主节点宕机
> 时间: 2026-08-03 10:30:00
> 影响: 全站核心交易不可用
> 当前状态: 正在执行故障切换
> 负责人: @IC
> 下次更新: 10分钟内

### 外部通报(如需)
> 服务异常通知
> 我们检测到部分服务出现异常,技术团队正在紧急处理。
> 预计恢复时间: XX:XX
> 对您造成的不便深表歉意。

## 回退方案
- 故障切换后旧主节点不可重新提升为从节点(binlog可能不一致)
- 需要从新主节点重建从节点(耗时约30分钟)

3. 角色分工矩阵(RACI)

flowchart LR
    IC[Incident Commander<br/>故障指挥官] -->|指挥| OPS[Ops Lead<br/>运维执行]
    IC -->|协调| DEV[Dev Lead<br/>开发支持]
    IC -->|通报| COMMS[Comms Lead<br/>沟通协调]
    IC -->|升级| MGMT[Management<br/>管理层]

    OPS -->|执行动作| SYS[系统/基础设施]
    DEV -->|代码层处理| APP[应用/服务]
    COMMS -->|对内| INTERNAL[内部团队]
    COMMS -->|对外| EXTERNAL[客户/用户]
角色职责RACI关键能力要求
IC (Incident Commander)统筹指挥、决策、资源协调R/A冷静、决策力、全局视野
Ops Lead执行基础设施层操作RK8s/网络/DB 操作能力
Dev Lead应用层诊断和修复R代码熟悉、快速定位
Comms Lead对内外沟通R简洁清晰、时间管理
Scribe记录时间线和决策R速记、结构化能力
Management资源支持和升级决策C/I业务影响判断

故障演练体系

1. 演练分级

级别名称范围参与方频率风险
L1桌面推演纸面/讨论SRE团队每月
L2红蓝对抗-测试环境测试环境SRE+Dev每季度
L3红蓝对抗-预发环境预发环境SRE+Dev+DBA每季度
L4Game Day-生产环境生产环境(受限)全员每半年中高
L5混沌工程自动注入生产环境自动化持续可控

2. 红蓝对抗演练

flowchart TB
    subgraph 红队(攻击方)
        R1[设计故障场景] --> R2[注入故障]
        R2 --> R3[观察蓝队响应]
        R3 --> R4[增加复杂度]
    end

    subgraph 蓝队(防守方)
        B1[接收告警] --> B2[响应判断]
        B2 --> B3[执行恢复]
        B3 --> B4[验证恢复]
    end

    subgraph 裁判组
        J1[记录时间线] --> J2[评估响应质量]
        J2 --> J3[记录问题]
    end

    R2 --> B1
    B4 --> R3
    R2 -.-> J1
    B3 -.-> J1

红蓝对抗流程模板:

"""红蓝对抗演练管理器"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import time


class DrillPhase(Enum):
    PLANNING = "planning"
    BRIEFING = "briefing"        # 演练前对齐
    EXECUTION = "execution"
    OBSERVATION = "observation"
    RECOVERY = "recovery"
    DEBRIEF = "debrief"          # 复盘


class FaultType(Enum):
    POD_KILL = "pod_kill"
    NETWORK_DELAY = "network_delay"
    NETWORK_PARTITION = "network_partition"
    CPU_STRESS = "cpu_stress"
    MEM_STRESS = "mem_stress"
    DISK_FILL = "disk_fill"
    DNS_ERROR = "dns_error"
    DB_FAILover = "db_failover"
    DEPENDENCY_DOWN = "dependency_down"


@dataclass
class FaultScenario:
    """故障场景定义"""
    scenario_id: str
    name: str
    fault_type: FaultType
    target: str                    # 目标资源
    params: dict                   # 故障参数
    expected_impact: str           # 预期影响
    expected_mttr: int             # 预期MTTR(秒)
    success_criteria: str          # 成功标准
    safety_guardrails: list[str]   # 安全护栏


@dataclass
class TimelineEntry:
    timestamp: float
    actor: str                     # red / blue / judge
    event: str
    detail: str = ""


@dataclass
class DrillResult:
    drill_name: str
    scenarios: list[FaultScenario]
    timeline: list[TimelineEntry] = field(default_factory=list)
    actual_mttr: int = 0
    success: bool = False
    issues_found: list[str] = field(default_factory=list)
    action_items: list[str] = field(default_factory=list)
    participants: dict[str, list[str]] = field(default_factory=dict)


class RedBlueDrill:
    """红蓝对抗演练管理器"""

    def __init__(self, name: str, scenarios: list[FaultScenario]):
        self.name = name
        self.scenarios = scenarios
        self.result = DrillResult(drill_name=name, scenarios=scenarios)
        self.current_phase = DrillPhase.PLANNING
        self.drill_start_time = 0

    def start(self):
        """开始演练"""
        self.current_phase = DrillPhase.EXECUTION
        self.drill_start_time = time.time()
        self._log("judge", "演练开始", f"共 {len(self.scenarios)} 个场景")

    def inject_fault(self, scenario: FaultScenario):
        """红队注入故障"""
        self.current_phase = DrillPhase.EXECUTION
        self._log("red", f"注入故障: {scenario.name}",
                  f"目标: {scenario.target}, 类型: {scenario.fault_type.value}")
        # 实际执行 ChaosMesh / kubectl 命令
        # self._execute_chaos(scenario)

    def record_blue_response(self, event: str, detail: str = ""):
        """记录蓝队响应"""
        self._log("blue", event, detail)

    def record_recovery(self, scenario: FaultScenario):
        """记录恢复"""
        self.current_phase = DrillPhase.RECOVERY
        actual_mttr = int(time.time() - self.drill_start_time)
        self.result.actual_mttr = actual_mttr
        self.result.success = actual_mttr <= scenario.expected_mttr
        self._log("blue", f"恢复完成: {scenario.name}",
                  f"实际MTTR: {actual_mttr}s, 预期: {scenario.expected_mttr}s")

    def add_issue(self, issue: str):
        """记录发现的问题"""
        self.result.issues_found.append(issue)
        self._log("judge", f"发现问题: {issue}")

    def add_action_item(self, item: str):
        """记录改进项"""
        self.result.action_items.append(item)

    def finish(self) -> DrillResult:
        """结束演练"""
        self.current_phase = DrillPhase.DEBRIEF
        self._log("judge", "演练结束",
                  f"MTTR: {self.result.actual_mttr}s, "
                  f"问题数: {len(self.result.issues_found)}")
        return self.result

    def _log(self, actor: str, event: str, detail: str = ""):
        entry = TimelineEntry(
            timestamp=time.time(),
            actor=actor,
            event=event,
            detail=detail,
        )
        self.result.timeline.append(entry)


# ====== 预定义演练场景 ======
drill_scenarios = [
    FaultScenario(
        scenario_id="DRILL-001",
        name="核心API Pod被随机杀死",
        fault_type=FaultType.POD_KILL,
        target="deployment/core-api",
        params={"kill_ratio": 0.3},
        expected_impact="30%实例下线,短暂5xx",
        expected_mttr=60,
        success_criteria="HPA自动扩容恢复,5xx率<1%持续3分钟",
        safety_guardrails=["min_available=70%", "max_kill=30%"],
    ),
    FaultScenario(
        scenario_id="DRILL-002",
        name="数据库主节点故障",
        fault_type=FaultType.DB_FAILover,
        target="mysql-primary",
        params={},
        expected_impact="写入中断,读取可能受影响",
        expected_mttr=300,
        success_criteria="故障切换完成,读写恢复",
        safety_guardrails=["验证备节点同步状态", "回滚方案就绪"],
    ),
    FaultScenario(
        scenario_id="DRILL-003",
        name="网络延迟注入",
        fault_type=FaultType.NETWORK_DELAY,
        target="namespace/payment",
        params={"latency_ms": 500, "duration": "5m"},
        expected_impact="支付链路P99延迟飙升",
        expected_mttr=120,
        success_criteria="降级策略生效,超时熔断触发",
        safety_guardrails=["仅影响payment namespace", "5分钟自动恢复"],
    ),
]

3. Game Day 组织流程

gantt
    title Game Day 全流程时间线(以1天为例)
    dateFormat HH:mm
    axisFormat %H:%M

    section 准备阶段
    场景设计          :a1, 09:00, 30m
    风险评估          :a2, 09:30, 20m
    安全护栏配置      :a3, 09:50, 20m
    对齐说明会        :a4, 10:10, 20m

    section 执行阶段
    场景1: PodKill   :b1, 10:30, 30m
    场景2: 网络故障   :b2, 11:00, 30m
    场景3: DB故障切换 :b3, 11:30, 45m
    午休              :break, 12:15, 45m
    场景4: 级联故障   :b4, 13:00, 60m
    场景5: 安全事件   :b5, 14:00, 45m

    section 复盘阶段
    数据收集          :c1, 14:45, 15m
    全员复盘          :c2, 15:00, 60m
    Action Items      :c3, 16:00, 30m
    报告输出          :c4, 16:30, 30m

Game Day Checklist:

阶段检查项负责人状态
准备场景设计完成并评审红队队长
准备安全护栏配置并验证SRE
准备回滚方案确认蓝队队长
准备监控大盘就绪监控负责人
准备参与人员通知到位PM
准备变更冻结窗口已通知Comms
执行演练前对齐会完成IC
执行每个场景执行记录裁判组
执行安全护栏有效SRE
执行紧急中止机制就绪IC
复盘时间线还原Scribe
复盘MTTR/MTTD统计数据分析
复盘问题清单整理裁判组
复盘Action Items分配IC
复盘改进跟踪计划PM

4. 混沌工程自动注入

混沌工程与故障注入 结合,将部分低风险演练自动化。

# ChaosMesh 定期自动注入实验
apiVersion: chaos-mesh.org/v1alpha1
kind: Schedule
metadata:
  name: weekly-pod-kill-drill
  namespace: chaos-testing
spec:
  schedule: "0 10 * * 1"          # 每周一10点
  historyLimit: 5
  concurrencyPolicy: Forbid
  type: Workflow
  workflow:
    templates:
    - name: automated-drill
      templateType: Serial
      deadline: 300s
      task:
      - name: inject-pod-kill
        templateType: PodChaos
        deadline: 60s
        podchaos:
          selector:
            namespaces: [staging]
            labelSelectors:
              app: non-critical-api
          mode: fixed-percent
          value: "20"
          action: pod-kill
          gracePeriod: 0

演练效果度量

关键指标

指标含义目标测量方法
MTTD(演练)演练中检测时间<1min故障注入→首次告警
MTTA(演练)演练中响应时间<2min告警→IC接管
MTTR(演练)演练中恢复时间<场景目标故障注入→恢复确认
预案覆盖率有预案的故障占比>80%有预案场景/总场景
预案有效率预案指导成功恢复占比>90%预案有效/预案使用
发现问题数每次演练发现的新问题记录统计
Action Items 闭环率演练改进项完成率>80%已完成/总改进项

演练成熟度模型

级别特征典型表现
L1无演练全靠线上真实故障”练兵”
L2桌面推演定期讨论故障场景,无实操
L3测试环境演练在非生产环境模拟故障
L4预发环境红蓝对抗定期红蓝对抗,有度量
L5生产环境Game Day生产环境受控演练+混沌工程持续注入

常见坑点

坑点现象解决方案
演练变事故演练引发真实故障安全护栏+紧急中止机制
只练不练演练后不改进Action Items 闭环跟踪
场景单一只练简单场景渐进复杂度,引入级联故障
知己知彼蓝队提前知道场景红队保密,随机时间注入
过度依赖预案遇到预案外故障无所适从培养通用故障处理能力
演练疲劳频率过高团队抵触合理频率+轮换参与

关联知识

参考资源

  • 《SRE: Google 运维解密》— 第18章 “Slack” + Game Day 实践
  • 《Hands-On Chaos Engineering” — 故障演练工程化
  • ChaosMesh 文档 — Automated Chaos Experiments
  • Netflix Chaos Monkey — 生产环境混沌工程先驱
  • AWS Well-Architected Framework — Disaster Recovery & Game Days

学习时间

约 4-6 小时(含组织一次完整的桌面推演)

状态

  • 理解应急预案体系的组成部分
  • 能编写标准化的应急预案模板
  • 掌握红蓝对抗演练的流程
  • 能设计 Game Day 全流程
  • 理解演练效果度量指标
  • 实际组织一次红蓝对抗演练
  • 搭建混沌工程自动注入流水线