Skip to content

混沌工程 Chaos Engineering:主动韧性测试实战,从故障注入到生产演练

2023 年我们经历了一次生产事故:某个 pod 网络抖动导致服务间调用超时,级联故障让三个核心服务同时不可用。事后复盘发现,如果提前做过混沌实验,这个故障模式是可以被提前发现并修复的。从那之后我们开始系统性地引入混沌工程。

这篇分享从工具选型到生产实验的完整路径,包括三次实战案例和一次失败教训。

混沌工程的核心思维:假设驱动

混沌工程不是"随便扔故障",而是基于假设的受控实验:

1. 建立稳态假设
   "这个服务在正常情况下应该 99.9% 可用,平均响应时间 <200ms"

2. 设计实验
   "如果 kill 掉 50% 的 pod,服务还能维持稳态吗?"

3. 执行实验
   注入故障 → 观察 → 记录

4. 验证假设
   "假设成立:服务降级但可用,P99 延迟从 200ms 升到 800ms,仍在 SLA 内"
   "假设不成立:服务完全不可用,需要修复依赖配置"

关键原则:实验必须有假设、有观察指标、有回滚计划。三者缺其一,不要执行。

工具选型:Chaos Mesh vs Litmus vs Chaos Blade

维度 Chaos Mesh LitmusChaos Chaos Blade
厂商 美团开源 阿里云开源 阿里开源
K8s 支持 原生 CRD 原生 CRD DaemonSet 方式
实验类型 丰富(网络/CPU/IO/Kernel) 中等(偏 K8s) 轻量(脚本为主)
中文文档 优秀 良好 一般
社区活跃度 高 中高 中
学习曲线 中 低 最低
适合场景 生产级混沌实验 国内企业、合规需求 快速验证、简单场景

我们的选择:生产环境用 Chaos Mesh(功能最全、文档最好),staging 环境用 Chaos Blade(轻量、部署快)。

实验设计方法论:四步走

Step 1:明确假设

假设:当 2 个 payment-service pod 同时 Crash 时,
      剩余 pod 能承接全部流量,API 可用性不低于 99.5%

Step 2:定义实验范围

# chaos-mesh/pod-failure.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: payment-pod-failure
  namespace: payment-system
spec:
  selector:
    matchLabels:
      app: payment-service
      env: production
  mode: fixed-count     # 精确影响 2 个 pod
  value:
    count: 2
  action: pod-failure   # 杀掉 pod,不是模拟延迟
  duration: '5m'        # 5 分钟后自动恢复
  scheduler:
    cron: '@every 1h'   # 每小时执行一次(实验窗口期)

关键设计: - mode: fixed-count:精确控制影响范围,不会误伤其他服务 - duration: 5m:实验有上限,避免无限长故障 - scheduler.cron:低峰时段执行(凌晨 2-4 点),减少业务影响

Step 3:定义观察指标

# 实验期间必须持续观察的指标
- payment_service_request_success_rate   # 成功率
- payment_service_latency_p99            # P99 延迟
- payment_service_error_count_5xx        # 5xx 错误数
- payment_service_pod_healthy_count      # 健康 pod 数量
- downstream_dependency_status           # 下游依赖是否受影响

原则:观察指标必须在实验开始前就定义好,不能实验过程中临时想。实验的目的是验证假设,不是探索现象。

Step 4:定义回滚条件

# 满足任一条件立即终止实验
rollback_conditions:
  - metric: payment_service_request_success_rate
    condition: '< 0.95'       # 成功率低于 95% 立即停止
  - metric: payment_service_latency_p99
    condition: '> 2000'       # P99 超过 2 秒停止
  - manual: true              # 随时可以手动停止

实战案例一:Pod 死亡实验(成功)

假设:payment-service 的 50% pod 同时 crash 时,服务能自动恢复。

# 执行混沌实验
cmctl create podchaos payment-pod-failure -f chaos-mesh/pod-failure.yaml

# 观察实验效果
cmctl get podchaos payment-pod-failure

# 查看 Grafana 面板
# 对比实验前后 30 分钟的指标曲线

结果: - ✅ 假设成立:自动扩容在 45 秒内完成,成功率保持 99.8% - ⚠️ 发现次生问题:新 pod 启动时 DNS 解析有 2 秒延迟,导致少量请求超时 - 🔧 修复:在 K8s 的 kube-proxy 里加了 DNS cache TTL

教训:这个实验帮我们发现了一个生产里永远不会出现的竞态条件——扩容期间的 DNS 延迟。如果在生产里遇到,排查成本会高得多。

实战案例二:网络分区实验(失败)

假设:payment-service 和 DB 之间网络分区 30 秒,服务能优雅降级。

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: payment-db-network-partition
  namespace: payment-system
spec:
  selector:
    matchLabels:
      app: payment-service
  targetSelector:
    matchLabels:
      app: postgres-primary
  action: partition     # 网络分区
  direction: both       # 双向阻断
  duration: '30s'

结果: - ❌ 假设不成立:payment-service 的连接池没有正确释放,DB 恢复后出现大量连接堆积 - 🔥 实际影响:故障持续 30 秒,但连接池恢复花了 8 分钟 - 💥 连锁反应:下游调用 payment-service 的服务也超时,两个服务同时不可用

复盘:这个实验失败的价值极高——它暴露了我们从未测试过的故障场景。修复方向: 1. DB 连接池加 maxLifetime 和 idleTimeout 参数 2. payment-service 加 circuit breaker(熔断器) 3. 下一个混沌实验专门验证 circuit breaker 是否生效

这个案例说明:混沌工程不是为了证明"系统是健壮的",而是为了提前发现脆弱点。失败比成功更有价值。

实战案例三:CPU 压力实验(部分成功)

假设:支付服务 CPU 利用率达到 90% 时,QPS 会自动限流,不会引发级联故障。

apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: payment-cpu-stress
  namespace: payment-system
spec:
  selector:
    matchLabels:
      app: payment-service
  mode: fixed-count
  value:
    count: 2
  stressors:
    cpu:
      workers: 4
      load: 80          # 80% CPU 负载
      queues: 4
  duration: '10m'

结果: - ✅ 部分成立:QPS 确实下降了,但下降幅度(30%)超出预期(预期 10%) - ⚠️ 发现新问题:CPU 压力下 GC 停顿时间从 50ms 跳到 800ms,导致单次请求延迟激增 - 🔧 修复:调整 JVM 堆内存大小 + G1 GC 参数

关键洞察:CPU 压力实验暴露了GC 行为这个肉眼看不到的问题。如果不是混沌工程,这个问题可能要等到生产大促时才会暴露。

安全红线:生产实验的五个约束

1. 范围约束:只能影响目标服务,不能扩散到上下游
2. 时间约束:低峰时段执行,时长有上限(最长 10 分钟)
3. 回滚约束:任一观察指标越界立即自动停止
4. 权限约束:实验执行者需要有 SRE 审批,不能单人操作
5. 通知约束:实验开始前必须在 Slack/钉钉通知相关团队

硬性规则: - 生产环境每周最多 2 个混沌实验 - 大促前 7 天禁止任何混沌实验 - 必须先在 staging 环境完整跑一遍

FAQ

Q: 混沌工程和 A/B 测试有什么区别?

混沌工程验证的是系统可靠性假设,A/B 测试验证的是业务功能假设。两者方法论类似(假设→实验→观察→结论),但目标不同:混沌工程的目标是发现脆弱点,A/B 测试的目标是找到最优业务方案。工具也完全不同——混沌工程用 Chaos Mesh,A/B 测试用业务平台。

Q: 混沌实验失败了怎么办?

混沌实验"失败"(假设不成立)是正常且有价值的结果。关键是: 1. 记录实验过程和失败原因 2. 分析根因并制定修复计划 3. 修复后重新设计实验验证 4. 把实验过程整理成团队知识(runbook、postmortem)

不要把"假设不成立"当成事故,这是混沌工程的目的。

Q: 如何说服管理层支持混沌工程?

用数据说话: - 展示一次生产事故的成本(停机时间 × 每小时收入损失) - 展示混沌实验发现的脆弱点数量(量化风险) - 展示修复成本远低于事故成本 - 强调"主动发现" vs "被动救火"的区别

我们团队的做法:第一次混沌实验后出了一份 postmortem,包含"如果不做混沌实验,这次事故会造成多少损失"的估算。这份报告直接说服了 CTO 批准了混沌工程的年度预算。