分布式 SRE 与混沌工程:SLI/SLO、错误预算与故障注入实战

SRE 运维体系从 0 到 1:SLI/SLO/错误预算设计、混沌工程实验框架(Chaos Mesh/Litmus/Chaos Monkey),以及生产级故障演练与复盘体系。

在现代分布式系统中,服务数量多、链路长、依赖复杂,传统 “救火式” 运维已经无法满足业务对稳定性的要求。SRE(Site Reliability Engineering,站点可靠性工程)和混沌工程(Chaos Engineering)作为两种互补的实践方法论,分别从 主动防御主动进攻 两个维度构建系统的韧性。本文将从 SRE 文化、SLI/SLO/SLA 体系、错误预算、监控告警、混沌工程原理到 Chaos Mesh / Litmus / Chaos Monkey 三大工具实战,完整覆盖生产级可靠性建设路径。

一、SRE 文化与核心原则

1.1 SRE 是什么

SRE 由 Google 在 2003 年提出,核心理念是:用软件工程的方法解决运维问题。SRE 团队不是传统意义上的 “运维人员”,而是兼具开发能力的可靠性工程师。他们的工作围绕以下几个核心原则展开:

  • 拥抱故障(Embrace Failure):承认系统会出故障,关键不是零故障,而是快速恢复和降低影响
  • 自动化优先(Automate Everything):任何人工操作超过两次就应该自动化
  • 错误预算(Error Budget):允许系统在一定范围内出错,用预算驱动发布节奏
  • 监控一切(Monitor Everything):没有监控的系统就是黑盒,无法做可靠决策
  • 简化复杂度(Reduce Toil):消除重复性、无差异化的人工劳动

1.2 开发与 SRE 的关系

在传统组织中,开发和运维往往是对立的:开发希望快速发布新功能,运维希望系统稳定少变更。SRE 通过 错误预算 机制将两者统一:

// 错误预算协调函数示例
// 当错误预算充足时,允许加快发布速度
// 当错误预算耗尽时,暂停非紧急发布
func ShouldAllowRelease(errorBudgetRemaining float64) bool {
    // 错误预算低于 20% 时冻结发布
    if errorBudgetRemaining < 0.2 {
        return false
    }
    // 错误预算低于 50% 时要求增加 Canary 比例
    if errorBudgetRemaining < 0.5 {
        return true // 但会触发额外审批流程
    }
    return true
}

// 获取当前错误预算状态
func GetErrorBudgetStatus(slo float64, actualAvailability float64, windowDays int) *BudgetStatus {
    // 计算当前可用性
    budgetUsed := 1.0 - (actualAvailability / slo)
    return &BudgetStatus{
        SLI:            slo,
        Actual:         actualAvailability,
        BudgetUsed:     budgetUsed,
        BudgetRemaining: 1.0 - budgetUsed,
        WindowDays:     windowDays,
    }
}

type BudgetStatus struct {
    SLI             float64
    Actual          float64
    BudgetUsed      float64
    BudgetRemaining float64
    WindowDays      int
}

这种机制让开发团队在追求速度的同时必须承担稳定性责任,而 SRE 则通过工程手段提供工具、平台和规范,而非简单做 “守门员”。

二、SLI / SLO / SLA 定义与计算

2.1 三者的区别

术语全称定义示例
SLIService Level Indicator服务等级指标,用于衡量服务健康度的具体指标请求延迟 P99 < 200ms
SLOService Level Objective服务等级目标,SLI 在特定时间窗口内应达到的目标值一个月内 99.9% 的请求延迟 < 200ms
SLAService Level Agreement服务等级协议,面向客户的法律承诺,违约需赔偿可用性低于 99.9% 时赔付代金券

关键关系:SLI 是测量值,SLO 是内部目标,SLA 是外部承诺。通常 SLA 会比 SLO 宽松,留出缓冲空间。

2.2 SLI 的选择方法

选择 SLI 时应遵循 用户旅程 原则,关注用户真正在意的体验指标:

  1. 可用性:服务是否可响应(请求成功率)
  2. 延迟:响应速度(P50 / P95 / P99)
  3. 吞吐量:处理容量(QPS / 每分钟请求数)
  4. 错误率:失败请求占比(HTTP 5xx 比例)

Google SRE Book 推荐的 四大黄金信号(Four Golden Signals) 就是基于此:

  • 延迟(Latency):服务处理请求所需时间
  • 流量(Traffic):系统承载的请求量
  • 错误(Errors):请求失败的比例
  • 饱和度(Saturation):资源使用接近满载的程度
# Prometheus 记录规则:四大黄金信号 SLI
# 保存到 prometheus-rules/golden-signals.yml
groups:
  - name: golden_signals
    rules:
      # 1. 可用性 SLI:请求成功率
      - record: sli:availability:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{status=~"2..|3.."}[5m]))
          /
          sum(rate(http_requests_total[5m]))

      # 2. 延迟 SLI:P99 延迟
      - record: sli:latency:p99_rate5m
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
          )

      # 3. 错误率 SLI
      - record: sli:error:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum(rate(http_requests_total[5m]))

      # 4. 饱和度 SLI:CPU 使用率
      - record: sli:saturation:cpu_rate5m
        expr: |
          avg(irate(node_cpu_seconds_total{mode="idle"}[5m]))

      # 5. 流量 SLI:每秒请求数
      - record: sli:traffic:qps_rate5m
        expr: |
          sum(rate(http_requests_total[5m]))

2.3 SLO 的具体计算

设定 SLO 需要明确三个要素:指标定义、目标值、时间窗口

#!/usr/bin/env python3
# slo_calculator.py — SLO 计算与错误预算推导工具

class SLOCalculator:
    def __init__(self, target: float, window_days: int):
        """
        初始化 SLO 计算器
        :param target: SLO 目标值,如 0.999 表示 99.9%
        :param window_days: 计算窗口,如 30 天
        """
        self.target = target
        self.window_days = window_days
        # 月度总分钟数
        self.total_minutes = window_days * 24 * 60

    def calculate_error_budget_minutes(self) -> float:
        """计算月度允许的停机分钟数(错误预算)"""
        error_rate = 1.0 - self.target
        return self.total_minutes * error_rate

    def calculate_error_budget_requests(self, total_requests: int) -> int:
        """基于总请求数计算允许的错误请求数"""
        error_rate = 1.0 - self.target
        return int(total_requests * error_rate)

    def status(self, actual_availability: float) -> dict:
        """检查 SLO 状态"""
        budget_remaining = self.target - (1.0 - actual_availability)
        return {
            "slo_target": f"{self.target*100:.3f}%",
            "actual": f"{actual_availability*100:.3f}%",
            "budget_remaining_pct": f"{budget_remaining*100:.3f}%",
            "is_met": actual_availability >= self.target
        }

# 使用示例
if __name__ == "__main__":
    # 99.9% SLO,30 天窗口
    calc = SLOCalculator(target=0.999, window_days=30)
    
    print(f"30 天总分钟数: {calc.total_minutes}")
    print(f"99.9% SLO 允许的停机: {calc.calculate_error_budget_minutes():.2f} 分钟")
    print(f"99.9% SLO 允许的停机: {calc.calculate_error_budget_minutes()/60:.2f} 小时")
    
    # 对比不同 SLO 级别
    for target in [0.9, 0.99, 0.999, 0.9999]:
        c = SLOCalculator(target, 30)
        mins = c.calculate_error_budget_minutes()
        print(f"SLO {target*100:>6.2f}% — 允许停机 {mins:>10.2f} 分钟 ({mins/60:>7.3f} 小时)")

运行结果:

30 天总分钟数: 43200
99.9% SLO 允许的停机: 43.20 分钟
99.9% SLO 允许的停机: 0.72 小时
SLO  90.00% — 允许停机    4320.00 分钟 ( 72.000 小时)
SLO  99.00% — 允许停机     432.00 分钟 (  7.200 小时)
SLO  99.90% — 允许停机      43.20 分钟 (  0.720 小时)
SLO  99.99% — 允许停机       4.32 分钟 (  0.072 小时)

2.4 常见 SLO 等级参考

服务等级可用性目标月度允许停机适用场景
基础服务99.9%43.2 分钟内部工具、非核心后台
标准服务99.95%21.6 分钟一般业务系统
重要服务99.99%4.32 分钟核心交易、支付链路
关键服务99.999%26 秒金融核心、医疗急救

三、错误预算策略

3.1 错误预算的核心思想

错误预算是 SRE 最具革命性的概念之一。它的逻辑是:如果服务要求 99.9% 可用性,那 0.1% 的 “错误额度” 就是预算。当预算充足时可以大胆发布,当预算耗尽时必须停止变更直到恢复。

这种机制实现了三个目标:

  1. 统一目标:开发和 SRE 对 “可接受的故障” 有了一致的量化标准
  2. 数据驱动发布:不再凭感觉决定能否发布,而是看错误预算余额
  3. 减少无效争论:当出现故障时,用预算消耗情况评估影响

3.2 错误预算政策设计

# 错误预算政策配置示例
# 保存到 policies/error-budget-policy.yml
error_budget_policy:
  # 30 天滚动窗口
  window: "30d"
  
  # 各等级服务的 SLO 与对应策略
  tiers:
    critical:
      slo: 99.99
      # 预算消耗比例 -> 采取措施
      actions:
        - threshold: 0.75    # 消耗 75%
          action: "增加 Canary 验证时间至 24 小时"
          severity: "warning"
        - threshold: 1.0     # 全部消耗
          action: "暂停所有非紧急发布,启动专题复盘"
          severity: "critical"
      
    standard:
      slo: 99.9
      actions:
        - threshold: 0.5
          action: "发布需 SRE 审批"
          severity: "info"
        - threshold: 1.0
          action: "冻结发布两周"
          severity: "critical"
    
    internal:
      slo: 99.0
      actions:
        - threshold: 1.0
          action: "周会通报"
          severity: "warning"

  # 异常加速消耗检测
  burn_rate_alerts:
    # 1 天内消耗 2% 预算(按正常速度 30 倍)
    fast_burn:
      multiplier: 30
      window: "1d"
      action: "立即通知 on-call 工程师"
    
    # 3 天内消耗 5% 预算(按正常速度 10 倍)
    medium_burn:
      multiplier: 10
      window: "3d"
      action: "创建高优先级工单"

3.3 预算消耗的监控告警

# Alertmanager 规则:错误预算消耗告警
# 保存到 alertmanager-rules/error-budget.yml
groups:
  - name: error_budget_alerts
    rules:
      # 快速消耗:1 天消耗了 30 天预算的 2% = 加速 30 倍
      - alert: ErrorBudgetFastBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1d]))
            /
            sum(rate(http_requests_total[1d]))
          ) > 2 * (
            1 - sum(slo_target)
          )
        for: 5m
        labels:
          severity: critical
          team: sre
        annotations:
          summary: "错误预算快速消耗中"
          description: "服务 {{ $labels.service }} 的错误预算正在以 30 倍速度消耗"
          action: "立即调查近期变更或异常流量"

      # 预算耗尽预警
      - alert: ErrorBudgetExhausted
        expr: error_budget_remaining_percent < 0
        for: 1m
        labels:
          severity: critical
          team: sre
        annotations:
          summary: "错误预算已耗尽"
          description: "服务 {{ $labels.service }} 30 天错误预算已耗尽,冻结非紧急发布"

四、监控与告警设计:黄金信号实践

4.1 告警分层体系

生产环境的告警应该分层设计,避免噪音和遗漏:

# 告警分层配置示例(Grafana Alerting)
# 保存到 grafana/alerts/layered-alerts.yml
apiVersion: 1
groups:
  - orgId: 1
    name: layered_alerting
    folder: SRE
    interval: 30s
    rules:
      # ====== 第 1 层:页面级告警(Page)======
      # 用户直接影响,需要立即人工响应
      - uid: page-latency-p99
        title: "P99 延迟超过 SLO"
        condition: C
        data:
          - refId: A
            relativeTimeRange:
              from: 300
              to: 0
            datasourceUid: prometheus
            model:
              expr: |
                histogram_quantile(0.99,
                  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
                ) > 0.5
              refId: A
          - refId: C
            relativeTimeRange:
              from: 0
              to: 0
            datasourceUid: __expr__
            model:
              type: threshold
              expression: A
              conditions:
                - evaluator:
                    type: gt
                    params: [0.5]
        noDataState: NoData
        execErrState: Error
        for: 5m
        annotations:
          severity: "page"
          runbook_url: "https://wiki/runbooks/high-latency"

      # ====== 第 2 层:工单级告警(Ticket)======
      # 趋势异常,需要当天处理
      - uid: ticket-error-rate-trend
        title: "错误率呈上升趋势"
        condition: C
        data:
          - refId: A
            datasourceUid: prometheus
            model:
              expr: |
                (
                  sum(rate(http_requests_total{status=~"5.."}[1h]))
                  /
                  sum(rate(http_requests_total[1h]))
                ) > 0.001
              refId: A
        for: 30m
        annotations:
          severity: "ticket"
          action: "创建工单,上班后排查"

      # ====== 第 3 层:信息级(Info)======
      # 供分析和报告使用
      - uid: info-budget-weekly
        title: "周度错误预算报告"
        condition: C
        data:
          - refId: A
            datasourceUid: prometheus
            model:
              expr: |
                (1 - sli:availability:ratio_rate7d) / (1 - slo_target)
              refId: A
        for: 1h
        annotations:
          severity: "info"
          action: "自动发送到周会报告"

4.2 SLO 监控看板

{
  "dashboard": {
    "title": "SRE SLO Dashboard",
    "panels": [
      {
        "title": "可用性合规状态",
        "type": "stat",
        "targets": [
          {
            "expr": "sli:availability:ratio_rate30d",
            "legendFormat": "{{service}} — 30天可用性"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                {"color": "red", "value": 0},
                {"color": "yellow", "value": 0.999},
                {"color": "green", "value": 0.9995}
              ]
            }
          }
        }
      },
      {
        "title": "错误预算消耗趋势",
        "type": "timeseries",
        "targets": [
          {
            "expr": "(1 - sli:availability:ratio_rate30d) / (1 - 0.999)",
            "legendFormat": "{{service}} 预算消耗比"
          }
        ]
      },
      {
        "title": "四大黄金信号",
        "type": "graph",
        "targets": [
          {"expr": "sli:traffic:qps_rate5m", "legendFormat": "流量(QPS)"},
          {"expr": "sli:latency:p99_rate5m * 1000", "legendFormat": "P99 延迟(ms)"},
          {"expr": "sli:error:ratio_rate5m * 100", "legendFormat": "错误率(%)"},
          {"expr": "100 - sli:saturation:cpu_rate5m * 100", "legendFormat": "CPU 使用率(%)"}
        ]
      }
    ]
  }
}

五、混沌工程基础

5.1 混沌工程的定义

混沌工程是在分布式系统上进行 受控实验 的学科,通过有意注入故障来验证系统在各种异常条件下的行为,从而提前发现弱点并增强系统韧性。

Netflix 提出的混沌工程原则:“混沌工程是在分布式系统上进行实验的学科,目的是建立对系统承受生产环境中湍流条件能力的信心。”

5.2 五大核心原则

  1. 建立稳态假设(Build a Hypothesis around Steady State Behavior)

    • 先定义系统正常运行时的可观测指标(如 QPS、错误率、延迟)
  2. 多样化真实世界事件(Vary Real-world Events)

    • 故障类型应覆盖硬件故障、网络异常、依赖失效、流量激增等真实场景
  3. 生产环境运行(Run Experiments in Production)

    • 唯有在生产环境才能观察到真实的系统行为和用户影响
  4. 自动化持续运行(Automate Experiments to Run Continuously)

    • 混沌实验不应是一次性的手工活动,而应集成到 CI/CD 流程中
  5. 最小化爆炸半径(Minimize Blast Radius)

    • 通过金丝雀、流量分割、快速回滚等手段控制实验影响范围

5.3 实验安全护栏

#!/usr/bin/env python3
# chaos_guardrails.py — 混沌实验安全保护机制

from dataclasses import dataclass
from enum import Enum
from typing import Optional, List

class ExperimentStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    PAUSED = "paused"
    ROLLEDBACK = "rolled_back"
    COMPLETED = "completed"

@dataclass
class AbortCondition:
    """实验自动终止条件"""
    metric: str          # 监控指标名称
    threshold: float     # 触发阈值
    duration_sec: int    # 持续超过阈值多长时间后终止

@dataclass
class BlastRadius:
    """爆炸半径控制"""
    max_affected_percent: float  # 最大影响流量百分比
    target_service: str          # 目标服务
    excluded_pods: List[str]     # 排除的关键 Pod(如 leader)
    namespace: str               # 限制在特定命名空间

class ChaosGuardrail:
    """混沌实验安全控制器"""
    
    def __init__(
        self,
        blast_radius: BlastRadius,
        abort_conditions: List[AbortCondition],
        experiment_duration_sec: int = 300,
        auto_rollback: bool = True
    ):
        self.blast_radius = blast_radius
        self.abort_conditions = abort_conditions
        self.experiment_duration = experiment_duration_sec
        self.auto_rollback = auto_rollback
        self.status = ExperimentStatus.PENDING
        
    def check_pre_conditions(self, current_metrics: dict) -> bool:
        """实验前检查:确保系统处于健康状态"""
        # 检查当前错误率是否已偏高
        if current_metrics.get("error_rate", 0) > 0.01:
            print("错误率已高于 1%,跳过本次实验")
            return False
        
        # 检查是否处于业务高峰
        if current_metrics.get("qps", 0) > current_metrics.get("peak_qps", 1) * 0.8:
            print("当前流量接近峰值,推迟实验")
            return False
        
        # 检查是否有未恢复的告警
        if current_metrics.get("active_alerts", 0) > 0:
            print("存在活跃告警,终止实验")
            return False
        
        return True
    
    def should_abort(self, metrics: dict) -> Optional[str]:
        """运行时检查:判断是否触发终止条件"""
        for condition in self.abort_conditions:
            value = metrics.get(condition.metric)
            if value is not None and value > condition.threshold:
                return (
                    f"终止条件触发: {condition.metric}={value} "
                    f"超过阈值 {condition.threshold}"
                )
        return None
    
    def validate_blast_radius(self, selected_targets: List[str]) -> bool:
        """验证爆炸半径是否超出许可范围"""
        total_pods = self._get_total_pods(self.blast_radius.target_service)
        affected_percent = len(selected_targets) / max(total_pods, 1)
        
        if affected_percent > self.blast_radius.max_affected_percent:
            print(
                f"影响范围 {affected_percent:.1%} 超出限制 "
                f"{self.blast_radius.max_affected_percent:.1%}"
            )
            return False
        
        # 检查是否排除了关键 Pod
        for excluded in self.blast_radius.excluded_pods:
            if excluded in selected_targets:
                print(f"选中了被排除的 Pod: {excluded}")
                return False
        
        print(f"爆炸半径验证通过: 影响 {len(selected_targets)}/{total_pods} Pod")
        return True
    
    def _get_total_pods(self, service: str) -> int:
        # 实际实现会查询 Kubernetes API
        return 10

# 使用示例:网络延迟实验的安全配置
if __name__ == "__main__":
    guardrail = ChaosGuardrail(
        blast_radius=BlastRadius(
            max_affected_percent=0.10,      # 最多影响 10% 的实例
            target_service="payment-service",
            excluded_pods=["payment-0"],    # 排除主节点
            namespace="production"
        ),
        abort_conditions=[
            # 错误率超过 5% 立即终止
            AbortCondition("error_rate", 0.05, 10),
            # P99 延迟超过 2 秒持续 30 秒终止
            AbortCondition("latency_p99", 2.0, 30),
            # 订单成功率低于 95% 终止
            AbortCondition("order_success_rate", 0.95, 60),
        ],
        experiment_duration_sec=300,       # 实验运行 5 分钟
        auto_rollback=True
    )
    
    print("混沌实验安全护栏已配置")
    print(f"自动回滚: {guardrail.auto_rollback}")
    print(f"实验时长: {guardrail.experiment_duration} 秒")

六、Chaos Mesh 实战

Chaos Mesh 是 PingCAP 开源的云原生混沌工程平台,专为 Kubernetes 设计,支持丰富的故障注入类型和友好的 Web UI。

6.1 环境准备

# 安装 Chaos Mesh(使用 Helm)
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm repo update

# 安装到 chaos-mesh 命名空间
helm install chaos-mesh chaos-mesh/chaos-mesh \
  --namespace chaos-mesh \
  --create-namespace \
  --set dashboard.create=true \
  --set chaosDaemon.runtime=containerd \
  --set chaosDaemon.socketPath=/run/containerd/containerd.sock

# 确认安装
kubectl get pods -n chaos-mesh

6.2 Pod 故障注入实验

# 实验 1:随机杀死 Pod(模拟实例故障)
# 保存到 chaos-experiments/pod-kill.yml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: pod-kill-payment
  namespace: chaos-mesh
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: payment-service
  scheduler:
    cron: "@every 10m"    # 每 10 分钟执行一次
  duration: "30s"         # 故障持续 30 秒
---
# 实验 2:Pod 网络分区(模拟网络隔离)
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-partition-order-db
  namespace: chaos-mesh
spec:
  action: partition
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  direction: both
  target:
    selector:
      namespaces:
        - production
      labelSelectors:
        app: order-db
    mode: one
  duration: "3m"

6.3 网络延迟与丢包实验

# 实验 3:注入网络延迟(模拟跨区域网络抖动)
# 保存到 chaos-experiments/network-delay.yml
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-delay-api
  namespace: chaos-mesh
spec:
  action: delay
  mode: fixed-percent
  value: "50"             # 影响 50% 的目标实例
  selector:
    namespaces:
      - production
    labelSelectors:
      app: api-gateway
  delay:
    latency: "200ms"      # 增加 200ms 延迟
    correlation: "75"     # 75% 的相关性(抖动时序关联)
    jitter: "50ms"        # 随机抖动范围
  duration: "5m"
---
# 实验 4:网络丢包(模拟弱网环境)
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-loss-redis
  namespace: chaos-mesh
spec:
  action: loss
  mode: all
  selector:
    namespaces:
      - production
    labelSelectors:
      app: cache-service
  loss:
    loss: "10"            # 10% 丢包率
    correlation: "50"
  duration: "3m"

6.4 CPU 和内存压力测试

# 实验 5:CPU 压力测试
# 保存到 chaos-experiments/stress-cpu.yml
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: cpu-stress-inventory
  namespace: chaos-mesh
spec:
  mode: fixed-percent
  value: "33"             # 影响 33% 的目标
  selector:
    namespaces:
      - production
    labelSelectors:
      app: inventory-service
  stressors:
    cpu:
      workers: 4            # 4 个 worker 线程
      load: 80              # 每个核心负载 80%
      options: ["--cpu-load-slice", "10"]
  duration: "5m"
---
# 实验 6:内存压力测试
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: memory-stress-search
  namespace: chaos-mesh
spec:
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: search-service
  stressors:
    memory:
      workers: 2
      size: "512MB"         # 每个 worker 分配 512MB
      options: ["--mem-stride-size", "4096"]
  duration: "3m"

6.5 使用工作流编排复杂实验

# 实验 7:多步骤混沌工作流
# 保存到 chaos-experiments/workflow-cascade.yml
apiVersion: chaos-mesh.org/v1alpha1
kind: Workflow
metadata:
  name: cascade-failure-test
  namespace: chaos-mesh
spec:
  entry: entry
  templates:
    - name: entry
      templateType: Serial
      deadline: 15m
      children:
        - delay-before           # 等待 2 分钟
        - kill-payment-pod       # 先杀掉支付 Pod
        - delay-middle           # 等待 1 分钟
        - delay-inventory-network # 再添加库存服务网络延迟
        - verify-recovery        # 验证恢复
    
    - name: delay-before
      templateType: Schedule
      deadline: 2m
      schedule:
        type: Cron
        cron: "0/1 * * * * *"
        concurrencyPolicy: Forbid
    
    - name: kill-payment-pod
      templateType: PodChaos
      podChaos:
        action: pod-kill
        mode: fixed-percent
        value: "25"
        selector:
          namespaces: [production]
          labelSelectors:
            app: payment-service
    
    - name: delay-middle
      templateType: Schedule
      deadline: 1m
    
    - name: delay-inventory-network
      templateType: NetworkChaos
      networkChaos:
        action: delay
        mode: one
        selector:
          namespaces: [production]
          labelSelectors:
            app: inventory-service
        delay:
          latency: "500ms"
        target:
          mode: one
          selector:
            namespaces: [production]
            labelSelectors:
              app: inventory-db
    
    - name: verify-recovery
      templateType: AWSChaos
      awsChaos:
        action: stop-count
        secretName: "cloud-key-secret"
        awsRegion: "ap-northeast-1"
        ec2Instance: "i-1234567890abcdef0"

七、Litmus 混沌实验

Litmus 是 CNCF 沙箱项目,采用 Kubernetes CRD 定义混沌实验,强调声明式配置和 GitOps 集成。

7.1 安装 Litmus

# 安装 Litmus Control Plane
kubectl apply -f https://litmuschaos.github.io/litmus/litmus-operator-v3.0.0.yaml

# 创建混沌服务账号
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: litmus-admin
  namespace: litmus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: litmus-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: litmus-admin
subjects:
- kind: ServiceAccount
  name: litmus-admin
  namespace: litmus
EOF

7.2 Litmus 实验定义

# 保存到 litmus-experiments/pod-delete-experiment.yml
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: payment-chaos
  namespace: litmus
spec:
  appinfo:
    appns: 'production'
    applabel: 'app=payment-service'
    appkind: 'deployment'
  # 停止条件:如果辅助应用 app-check 失败则停止
  auxiliaryAppInfo: ''
  engineState: 'active'
  chaosServiceAccount: litmus-admin
  # 监控:使用 prometheus 进行监控
  monitoring: true
  # 按顺序执行实验
  annotationCheck: 'true'
  experiments:
    - name: pod-delete
      spec:
        components:
          env:
            # 随机杀死 Pod
            - name: TARGET_CONTAINER
              value: ''
            # 杀掉随机 Pod
            - name: PODS_AFFECTED_PERC
              value: '50'
            # 总 Chaos 持续时间
            - name: TOTAL_CHAOS_DURATION
              value: '60'
            # 混沌间隔
            - name: CHAOS_INTERVAL
              value: '10'
            - name: FORCE
              value: 'false'
            - name: LIB
              value: 'litmus'
          nodeSelector: {}
          experimentImagePullSecrets: []
---
# 保存到 litmus-experiments/network-latency-experiment.yml
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: network-latency-chaos
  namespace: litmus
spec:
  appinfo:
    appns: 'production'
    applabel: 'app=api-gateway'
    appkind: 'deployment'
  engineState: 'active'
  chaosServiceAccount: litmus-admin
  annotationCheck: 'true'
  experiments:
    - name: network-latency
      spec:
        components:
          env:
            # 接口名称(空表示所有网卡)
            - name: NETWORK_INTERFACE
              value: 'eth0'
            # 延迟时间(毫秒)
            - name: NETWORK_LATENCY
              value: '2000'
            # 目标端口(空表示所有端口)
            - name: TARGET_PORT
              value: ''
            # 受影响的 Pod 百分比
            - name: PODS_AFFECTED_PERC
              value: '100'
            # 总混沌时长
            - name: TOTAL_CHAOS_DURATION
              value: '120'
            # 混沌间隔
            - name: CHAOS_INTERVAL
              value: '10'
            - name: LIB
              value: 'litmus'
            - name: LIB_IMAGE
              value: 'litmuschaos/go-runner:3.0.0'

7.3 Litmus 工作流与 Probe

Litmus 支持在实验中插入 Probe(探针) 进行健康检查,确保实验在安全范围内运行。

# 保存到 litmus-experiments/probe-health-check.yml
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: probe-example
  namespace: litmus
spec:
  appinfo:
    appns: 'production'
    applabel: 'app=cart-service'
    appkind: 'deployment'
  engineState: 'active'
  chaosServiceAccount: litmus-admin
  experiments:
    - name: pod-cpu-hog
      spec:
        probe:
          # 实验前探针:确保服务健康才进行
          - name: "health-check-before"
            type: "httpProbe"
            mode: "Edge"
            runProperties:
              probeTimeout: "5s"
              retry: 2
              interval: "5s"
              initialiseTimeout: "5s"
            httpProbe/inputs:
              url: "http://cart-service.production.svc.cluster.local:8080/health"
              insecureSkipVerify: false
              method:
                get:
                  criteria: "=="
                  responseCode: "200"
          
          # 持续探针:实验中持续检查
          - name: "order-success-during"
            type: "cmdProbe"
            mode: "Continuous"
            runProperties:
              probeTimeout: "5s"
              retry: 1
              interval: "5s"
            cmdProbe/inputs:
              command: "curl -s -o /dev/null -w '%{http_code}' http://cart-service.production.svc.cluster.local:8080/cart/add"
              source: "inline"
              comparator:
                type: "string"
                criteria: "equals"
                value: "200"
          
          # 实验后探针:确认恢复
          - name: "recovery-check"
            type: "promProbe"
            mode: "Edge"
            runProperties:
              probeTimeout: "10s"
              retry: 3
              interval: "5s"
            promProbe/inputs:
              endpoint: "http://prometheus.monitoring.svc.cluster.local:9090"
              query: "up{job='cart-service'}"
              comparator:
                type: "float"
                criteria: ">"
                value: "0"
        
        components:
          env:
            - name: CPU_CORES
              value: '1'
            - name: PODS_AFFECTED_PERC
              value: '50'
            - name: TOTAL_CHAOS_DURATION
              value: '120'

八、Chaos Monkey 与 AWS 故障注入

8.1 Chaos Monkey 简介

Chaos Monkey 是 Netflix 最早开源的混沌工程工具,核心理念很简单:随机终止生产环境的虚拟机实例。虽然功能看似简单,但它验证了分布式系统的核心假设——单点故障不应影响整体可用性。

8.2 Chaos Monkey 配置(Spinnaker 集成)

{
  "jsonNET": "chaos-monkey.json",
  "description": "Chaos Monkey 配置 - AWS EC2 生产环境",
  "config": {
    "enabled": true,
    "regions": ["us-east-1", "us-west-2"],
    "probability": 1.0,
    "schedule": "0 9-17 * * 1-5",
    "exceptions": [
      {
        "type": "cluster",
        "value": "production-critical-payment-v001",
        "reason": "金融核心集群,受监管要求限制"
      },
      {
        "type": "cluster",
        "value": "production-db-primary-v001",
        "reason": "数据库主节点"
      }
    ],
    "mandatoryGroup": {
      "name": "default",
      "probability": 1.0,
      "grouping": "cluster"
    },
    "leashed": false
  }
}

8.3 AWS Fault Injection Simulator (FIS)

AWS 官方提供了故障注入服务 FIS,可以安全地在 AWS 基础设施上进行混沌实验。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS FIS 实验模板 — EC2 实例终止 + 网络延迟",
  "Resources": {
    "ChaosExperimentTemplate": {
      "Type": "AWS::FIS::ExperimentTemplate",
      "Properties": {
        "Description": "终止 20% 的 API 服务实例并注入网络延迟",
        "RoleArn": {
          "Fn::GetAtt": ["FISRole", "Arn"]
        },
        "StopConditions": [
          {
            "Source": "cloudwatch-alarms",
            "Value": {
              "Fn::Join": [",", [
                {"Ref": "ErrorRateAlarm"},
                {"Ref": "LatencyAlarm"}
              ]]
            }
          }
        ],
        "Tags": {
          "Experiment": "api-resilience-test",
          "Team": "sre"
        },
        "Targets": {
          "APIInstances": {
            "ResourceType": "aws:ec2:instance",
            "SelectionMode": "PERCENT(20)",
            "Filters": [
              {
                "Path": "State.Name",
                "Values": ["running"]
              },
              {
                "Path": "Tags[?Key=='Service']",
                "Values": ["api-gateway"]
              }
            ]
          }
        },
        "Actions": {
          "TerminateInstances": {
            "ActionId": "aws:ec2:stop-instances",
            "Description": "停止选中的 EC2 实例",
            "Targets": {
              "Instances": "APIInstances"
            },
            "StartAfter": ["NetworkLatency"]
          },
          "NetworkLatency": {
            "ActionId": "aws:ssm:send-command",
            "Description": "注入网络延迟",
            "Parameters": {
              "documentArn": "arn:aws:ssm:::document/AWSFIS-Run-Network-Latency",
              "documentParameters": {
                "DurationSeconds": "300",
                "DelayMilliseconds": "200",
                "Interface": "eth0"
              },
              "maxDuration": "PT5M"
            },
            "Targets": {
              "Instances": "APIInstances"
            }
          }
        },
        "LogConfiguration": {
          "CloudWatchLogsConfiguration": {
            "LogGroupArn": {
              "Fn::Join": ["", [
                "arn:aws:logs:",
                {"Ref": "AWS::Region"}, ":",
                {"Ref": "AWS::AccountId"},
                ":log-group:/aws/fis/experiments:*"
              ]]
            }
          },
          "LogSchemaVersion": 2
        }
      }
    }
  }
}

8.4 使用 AWS CLI 启动实验

#!/bin/bash
# run-fis-experiment.sh — 启动 AWS FIS 故障注入实验

EXPERIMENT_TEMPLATE_ID="EXTxxxxx12345"
REGION="us-east-1"

# 检查是否存在告警
ACTIVE_ALARMS=$(aws cloudwatch describe-alarms \
  --alarm-names "API-ErrorRate" "API-LatencyP99" \
  --state-value ALARM \
  --region $REGION \
  --query 'MetricAlarms | length(@)')

if [ "$ACTIVE_ALARMS" -gt 0 ]; then
    echo "存在活跃告警,取消实验"
    exit 1
fi

# 启动实验
echo "启动故障注入实验..."
EXPERIMENT=$(aws fis start-experiment \
  --experiment-template-id $EXPERIMENT_TEMPLATE_ID \
  --region $REGION \
  --tags Team=SRE,Environment=Production)

EXPERIMENT_ID=$(echo $EXPERIMENT | jq -r '.experiment.id')
echo "实验已启动,ID: $EXPERIMENT_ID"

# 等待实验完成(最长 10 分钟)
echo "监控实验状态..."
for i in {1..60}; do
    STATUS=$(aws fis get-experiment \
      --id $EXPERIMENT_ID \
      --region $REGION \
      --query 'experiment.state.status' \
      --output text)
    
    echo "当前状态: $STATUS"
    
    if [ "$STATUS" == "completed" ] || [ "$STATUS" == "stopped" ]; then
        echo "实验结束"
        break
    fi
    
    if [ "$STATUS" == "failed" ]; then
        echo "实验失败,检查日志"
        aws fis get-experiment \
          --id $EXPERIMENT_ID \
          --region $REGION
        exit 1
    fi
    
    sleep 10
done

echo "实验执行完成"

九、故障注入代码实战

9.1 应用层网络延迟注入

#!/usr/bin/env python3
# fault_injection_middleware.py — Flask 混沌工程中间件

import random
import time
import logging
from functools import wraps
from flask import Flask, request, jsonify

app = Flask(__name__)
logger = logging.getLogger(__name__)

class ChaosMiddleware:
    """Flask 混沌工程中间件"""
    
    def __init__(self, app, enabled: bool = False):
        self.app = app
        self.enabled = enabled
        self.latency_injection_ms = 0
        self.error_rate = 0.0
        self.cpu_spike = False
        
        # 注册请求钩子
        self.app.before_request(self.before_request)
        self.app.after_request(self.after_request)
    
    def before_request(self):
        """请求前注入故障"""
        if not self.enabled:
            return
        
        # 1. 注入网络延迟
        if self.latency_injection_ms > 0:
            # 只影响特定百分比的请求
            if random.random() < 0.3:  # 30% 的请求受影响
                delay = self.latency_injection_ms / 1000.0
                jitter = random.uniform(-0.05, 0.05) * delay
                actual_delay = max(0, delay + jitter)
                logger.warning(
                    f"[混沌] 注入延迟 {actual_delay*1000:.0f}ms "
                    f"到 {request.endpoint}"
                )
                time.sleep(actual_delay)
        
        # 2. 注入错误响应
        if self.error_rate > 0 and random.random() < self.error_rate:
            logger.error(f"[混沌] 注入错误到 {request.endpoint}")
            return jsonify({"error": "chaos_injected_error", "code": 503}), 503
    
    def after_request(self, response):
        """响应后处理"""
        if self.enabled:
            response.headers['X-Chaos-Enabled'] = 'true'
        return response
    
    def set_latency(self, ms: int):
        """设置延迟注入量(毫秒)"""
        self.latency_injection_ms = ms
        logger.info(f"混沌延迟设置为 {ms}ms")
    
    def set_error_rate(self, rate: float):
        """设置错误率(0.0 ~ 1.0)"""
        self.error_rate = rate
        logger.info(f"混沌错误率设置为 {rate:.1%}")
    
    def enable(self):
        self.enabled = True
        logger.info("混沌工程已启用")
    
    def disable(self):
        self.enabled = False
        logger.info("混沌工程已禁用")

# 初始化中间件
chaos = ChaosMiddleware(app, enabled=False)

@app.route('/health')
def health():
    return jsonify({"status": "healthy", "chaos": chaos.enabled})

@app.route('/chaos/control', methods=['POST'])
def control_chaos():
    """混沌控制接口(仅内部使用)"""
    data = request.get_json() or {}
    
    if 'enabled' in data:
        if data['enabled']:
            chaos.enable()
        else:
            chaos.disable()
    
    if 'latency_ms' in data:
        chaos.set_latency(data['latency_ms'])
    
    if 'error_rate' in data:
        chaos.set_error_rate(data['error_rate'])
    
    return jsonify({
        "status": "ok",
        "chaos": {
            "enabled": chaos.enabled,
            "latency_ms": chaos.latency_injection_ms,
            "error_rate": chaos.error_rate
        }
    })

@app.route('/api/order', methods=['POST'])
def create_order():
    """模拟下单接口"""
    data = request.get_json()
    # 正常业务逻辑...
    return jsonify({
        "order_id": f"ORD-{random.randint(10000, 99999)}",
        "status": "created"
    })

if __name__ == '__main__':
    # 示例:启用混沌工程
    # chaos.enable()
    # chaos.set_latency(200)
    # chaos.set_error_rate(0.05)
    app.run(host='0.0.0.0', port=5000)

9.2 gRPC 服务故障注入拦截器

// chaos_interceptor.go — gRPC 混沌工程拦截器
package main

import (
	"context"
	"math/rand"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// ChaosConfig 定义混沌注入配置
type ChaosConfig struct {
	Enabled       bool          // 是否启用
	LatencyDelay  time.Duration // 注入延迟
	ErrorRate     float64       // 错误率 (0.0 - 1.0)
	AffectedRatio float64       // 受影响请求比例
}

// UnaryChaosInterceptor 一元拦截器注入故障
func UnaryChaosInterceptor(config *ChaosConfig) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		// 未启用则直接通过
		if !config.Enabled {
			return handler(ctx, req)
		}

		// 按比例决定是否影响当前请求
		if rand.Float64() > config.AffectedRatio {
			return handler(ctx, req)
		}

		// 注入延迟
		if config.LatencyDelay > 0 {
			jitter := time.Duration(rand.Int63n(int64(config.LatencyDelay / 10)))
			actualDelay := config.LatencyDelay + jitter - config.LatencyDelay/20
			time.Sleep(actualDelay)
		}

		// 注入错误
		if config.ErrorRate > 0 && rand.Float64() < config.ErrorRate {
			return nil, status.Errorf(
				codes.Unavailable,
				"chaos injected: service temporarily unavailable",
			)
		}

		return handler(ctx, req)
	}
}

// StreamChaosInterceptor 流式拦截器注入故障
func StreamChaosInterceptor(config *ChaosConfig) grpc.StreamServerInterceptor {
	return func(
		srv interface{},
		ss grpc.ServerStream,
		info *grpc.StreamServerInfo,
		handler grpc.StreamHandler,
	) error {
		if !config.Enabled {
			return handler(srv, ss)
		}

		// 流式请求注入延迟(在第一条消息时)
		if config.LatencyDelay > 0 {
			time.Sleep(config.LatencyDelay)
		}

		if config.ErrorRate > 0 && rand.Float64() < config.ErrorRate {
			return status.Errorf(codes.Internal, "chaos injected stream error")
		}

		return handler(srv, ss)
	}
}

// 使用示例
func main() {
	chaosConfig := &ChaosConfig{
		Enabled:       true,
		LatencyDelay:  100 * time.Millisecond,
		ErrorRate:     0.02, // 2% 错误率
		AffectedRatio: 0.5,  // 50% 请求受影响
	}

	server := grpc.NewServer(
		grpc.UnaryInterceptor(UnaryChaosInterceptor(chaosConfig)),
		grpc.StreamInterceptor(StreamChaosInterceptor(chaosConfig)),
	)

	_ = server
}

9.3 数据库连接故障注入

// ChaosDataSource.java — 数据源代理故障注入
package com.example.chaos;

import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class ChaosDataSource extends HikariDataSource {
    
    private volatile boolean chaosEnabled = false;
    private volatile double connectionFailureRate = 0.0;
    private volatile long connectionDelayMs = 0;
    private final Random random = new Random();
    
    public void enableChaos(double failureRate, long delayMs) {
        this.chaosEnabled = true;
        this.connectionFailureRate = failureRate;
        this.connectionDelayMs = delayMs;
    }
    
    public void disableChaos() {
        this.chaosEnabled = false;
    }
    
    @Override
    public Connection getConnection() throws SQLException {
        // 注入连接延迟
        if (chaosEnabled && connectionDelayMs > 0 && random.nextDouble() < 0.3) {
            try {
                long actualDelay = connectionDelayMs + random.nextInt(50);
                Thread.sleep(actualDelay);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        // 注入连接失败
        if (chaosEnabled && random.nextDouble() < connectionFailureRate) {
            throw new SQLException(
                "Chaos injected: connection refused"
            );
        }
        
        return super.getConnection();
    }
    
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }
}

十、事故响应与事后复盘

10.1 事故响应流程

生产事故发生时,清晰的分级响应流程比技术能力更重要。

# 事故响应流程定义
# 保存到 incident-response/playbook.yml
incident_response:
  # 严重等级定义
  severity_levels:
    sev1:
      description: "关键业务完全中断,大量用户受影响"
      response_minutes: 5       # 5 分钟内必须响应
      war_room: true            # 需要成立作战室
      executive_notify: true    # 通知高管
      postmortem_required: true # 必须写复盘报告
      sla: "99.999%"
      
    sev2:
      description: "核心功能降级,部分用户受影响"
      response_minutes: 15
      war_room: true
      executive_notify: false
      postmortem_required: true
      sla: "99.99%"
      
    sev3:
      description: "非核心功能异常,影响有限"
      response_minutes: 30
      war_room: false
      executive_notify: false
      postmortem_required: false
      sla: "99.9%"
      
    sev4:
      description: "轻微问题,无用户影响"
      response_minutes: 120
      war_room: false
      executive_notify: false
      postmortem_required: false
      sla: "99%"

  # 响应阶段
  phases:
    - name: "发现 (Detection)"
      steps:
        - "确认告警是否真实(降噪)"
        - "评估影响范围(用户、区域、功能)"
        - "根据影响确定严重等级"
        - "在事故通道中发布公告"
      
    - name: "响应 (Response)"
      steps:
        - "on-call 工程师接手指挥权"
        - "如需要升级为 sev1/sev2,立即组建 war room"
        - "启动相关通知(用户、客服、高管)"
        - "执行回滚或缓解措施"
      
    - name: "恢复 (Recovery)"
      steps:
        - "验证系统是否恢复正常"
        - "确认所有依赖服务健康"
        - "保持监控,确保不会反复"
        - "通知相关方事故已解决"
      
    - name: "复盘 (Post-Mortem)"
      steps:
        - "24 小时内完成初版时间线"
        - "72 小时内完成完整复盘文档"
        - "召开无追责复盘会议"
        - "创建可执行的改进项并分配 owner"

10.2 事后复盘模板

# 事后复盘报告模板
## [日期] [服务名] 事故复盘

### 基本信息
- **事故编号**: INC-2026-09-01-001
- **发现时间**: 2026-09-01 14:32 CST
- **持续时间**: 18 分钟
- **严重等级**: sev2
- **影响范围**: 支付接口延迟升高,约 15% 用户下单受阻
- **错误预算消耗**: 23%(月度)

### 时间线(精确到分钟)
| 时间 | 事件 | 操作人 |
|------|------|--------|
| 14:32 | 告警触发:P99 延迟超过 1s | 自动化 |
| 14:33 | on-call 工程师(张三)收到页面 | PagerDuty |
| 14:35 | 确认是订单服务 Pod 异常重启 | 张三 |
| 14:38 | 启动回滚到上一个版本 | 张三 |
| 14:45 | 服务恢复正常 | 张三 |
| 14:50 | 初步根因:新版本数据库连接池配置错误 | 张三 |

### 根因分析(5 Whys)
1. **为什么支付延迟高?** 订单服务响应变慢
2. **为什么订单服务变慢?** Pod 频繁重启导致请求排队
3. **为什么 Pod 频繁重启?** OOMKilled,内存超限
4. **为什么内存超限?** 新版本将连接池 maxSize 从 50 改为 200
5. **为什么配置被错误修改?** PR 评审时未关注配置变更,缺少配置变更检查清单

### 影响评估
- 受影响请求数:约 12,000 次
- 用户投诉:23 条
- 直接收入影响:估计 ¥3,500
- 错误预算消耗:23%

### 应对措施(已执行)
- 回滚到稳定版本 v2.3.1
- 扩容订单服务至 10 个实例

### 改进项
| 改进项 | Owner | 优先级 | 截止时间 | 状态 |
|--------|-------|--------|----------|------|
| 增加数据库连接池配置校验规则 | 李四 | P0 | 2026-09-08 | 待办 |
| PR 模板增加配置变更检查清单 | 王五 | P1 | 2026-09-15 | 待办 |
| 增加 OOM 告警阈值 | 张三 | P1 | 2026-09-10 | 待办 |
| 混沌工程增加连接池满载场景 | 赵六 | P2 | 2026-09-30 | 待办 |

### 经验总结
- 配置变更和代码变更一样危险,需要同等级别的审查
- OOM 场景需要在混沌工程中覆盖
- 错误预算消耗 23%,本月剩余发布风险升高

10.3 自动化复盘数据收集

#!/usr/bin/env python3
# incident_reporter.py — 自动化生成事故时间线

import requests
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List

@dataclass
class TimelineEvent:
    timestamp: datetime
    source: str        # 来源:alert / log / trace / manual
    description: str
    severity: str

class IncidentReporter:
    def __init__(self, prometheus_url: str, alertmanager_url: str):
        self.prometheus_url = prometheus_url
        self.alertmanager_url = alertmanager_url
    
    def generate_timeline(
        self,
        service: str,
        start: datetime,
        end: datetime
    ) -> List[TimelineEvent]:
        """根据时间范围自动生成事故时间线"""
        timeline = []
        
        # 1. 获取 Prometheus 告警记录
        alerts = self._get_alerts_in_range(service, start, end)
        for alert in alerts:
            timeline.append(TimelineEvent(
                timestamp=alert['startsAt'],
                source='alert',
                description=f"告警触发: {alert['labels']['alertname']}",
                severity=alert['labels'].get('severity', 'unknown')
            ))
        
        # 2. 获取异常日志
        logs = self._get_error_logs(service, start, end)
        for log in logs:
            timeline.append(TimelineEvent(
                timestamp=log['timestamp'],
                source='log',
                description=f"异常日志: {log['message'][:100]}",
                severity='error'
            ))
        
        # 3. 获取部署记录
        deployments = self._get_deployments(service, start, end)
        for deploy in deployments:
            timeline.append(TimelineEvent(
                timestamp=deploy['timestamp'],
                source='deployment',
                description=f"部署: {deploy['version']} by {deploy['user']}",
                severity='info'
            ))
        
        # 按时间排序
        timeline.sort(key=lambda x: x.timestamp)
        return timeline
    
    def _get_alerts_in_range(self, service: str, start: datetime, end: datetime):
        """从 Alertmanager 获取告警"""
        try:
            resp = requests.get(
                f"{self.alertmanager_url}/api/v1/alerts",
                params={
                    'filter': f'service="{service}"',
                    'start': start.isoformat(),
                    'end': end.isoformat()
                }
            )
            return resp.json().get('data', [])
        except Exception as e:
            print(f"获取告警失败: {e}")
            return []
    
    def _get_error_logs(self, service: str, start: datetime, end: datetime):
        """从 Loki 获取错误日志"""
        # 实际实现调用 Loki API
        return []
    
    def _get_deployments(self, service: str, start: datetime, end: datetime):
        """获取部署历史"""
        # 实际实现调用 Argo CD / GitLab API
        return []
    
    def print_timeline(self, timeline: List[TimelineEvent]):
        """打印格式化时间线"""
        print("\n=== 自动生成的事故时间线 ===\n")
        for event in timeline:
            time_str = event.timestamp.strftime("%Y-%m-%d %H:%M:%S")
            print(f"[{time_str}] [{event.source:12}] [{event.severity:8}] {event.description}")
        print("\n" + "="*60 + "\n")

# 使用示例
if __name__ == "__main__":
    reporter = IncidentReporter(
        prometheus_url="http://prometheus.monitoring.svc.cluster.local",
        alertmanager_url="http://alertmanager.monitoring.svc.cluster.local"
    )
    
    end = datetime.now()
    start = end - timedelta(hours=2)
    
    timeline = reporter.generate_timeline("order-service", start, end)
    reporter.print_timeline(timeline)

FAQ

Q1:错误预算耗尽后还能发布吗?

不建议。错误预算的设计目的就是用数据约束发布决策。当预算耗尽时,说明系统在当前周期内已经无法承受更多风险。此时正确做法是:暂停非紧急发布,集中精力修复已知稳定性问题,等到新周期预算重置后再恢复正常节奏。紧急安全补丁通常作为例外流程处理,但需要 SRE 负责人和 VP 级别共同审批。

Q2:混沌工程会不会导致真实故障?

任何生产环境实验都存在风险,但混沌工程通过三大机制将风险降至最低:(1)爆炸半径控制,只影响一小部分流量或实例;(2)自动终止条件,当监控指标超过阈值时实验自动停止;(3)演练时段选择,避开业务高峰期和重大活动期间。实践证明,规范执行的混沌工程导致的故障远少于它预防的故障。

Q3:初创公司有必要做 SRE 和混沌工程吗?

有必要,但实施方式应匹配公司阶段。初创公司团队规模小,可以采用以下轻量方案:使用云厂商的托管监控(如 CloudWatch / APM),设定简单的 SLO(如 99.9% 可用性),利用 AWS FIS 或开源 Chaos Mesh 做简单的 Pod 终止实验。关键是先建立 “可观测 + 可恢复” 的基础意识,再随规模扩大逐步完善体系。

Q4:SLA 和 SLO 差别多大合适?

通常建议 SLA 比 SLO 宽松半个到一个数量级。例如内部 SLO 设定为 99.99%,面向客户的 SLA 可以承诺为 99.9%。这个缓冲空间用于应对:不可控因素(如云厂商故障)、临时容量短缺、以及实验性的新功能发布。缓冲太小会导致频繁违约赔偿,太大则失去承诺意义。一般 Buffer = 2x ~ 10x(即内部目标比外部承诺严格 2 到 10 倍)。

Q5:如何选择 Chaos Mesh、Litmus 和 AWS FIS?

选择依据主要看基础设施环境:(1)Chaos Mesh 最适合 Kubernetes 环境,CRD 丰富、UI 友好、社区活跃;(2)Litmus 同样面向 Kubernetes,但在 GitOps 集成和探针(Probe)机制方面更成熟,适合需要声明式实验管理的团队;(3)AWS FIS 是 AWS 托管服务,适合纯 AWS 基础设施,与 CloudWatch、Auto Scaling 等原生集成,无需维护额外集群。许多企业采用组合方案:Kubernetes 层用 Chaos Mesh,AWS 基础设施层用 FIS。

总结

SRE 和混沌工程是构建高可靠分布式系统的两大支柱。SRE 通过 SLI/SLO/错误预算 建立量化的可靠性目标,用监控告警和自动化减少人为失误;混沌工程则通过 主动故障注入 验证系统在真实故障下的韧性,避免 “平时不烧香,临时抱佛脚”。

两者的结合形成了完整的可靠性闭环:

  1. 设定 SLO 定义 “好” 的标准
  2. 建设监控 度量当前状态
  3. 混沌实验 主动发现弱点
  4. 事故响应 提升修复速度
  5. 复盘改进 持续提升 SLO

建议读者从以下实践开始:先选定一个核心服务的四大黄金信号作为 SLI,设定 30 天 99.9% 的 SLO,配置错误预算告警,然后使用 Chaos Mesh 做一次 Pod Kill 实验。这个最小可行实践(MVP)将帮助团队在真实场景中理解 SRE 和混沌工程的价值,并在此基础上逐步扩展体系。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「distributed-systems」更多文章

  1. 分布式高可用架构模式:多活、容灾、降级与 K8s 编排高可用
  2. 分布式链路追踪实战:OpenTelemetry、Jaeger 与 W3C Trace Context
  3. 分布式缓存深度策略:Redis Cluster、一致性哈希与多级缓存架构