路线图

15 混沌工程(Chaos Engineering)

星辉 2026-07-02 阅读 6 min 1,091 字 路线图
15 混沌工程(Chaos Engineering) 封面

概述#

混沌工程不是"随便 kill 几个 pod 看看系统会不会挂"——那叫骚扰,不是工程。真正的混沌工程是在受控条件下,通过主动注入故障来验证系统韧性假设的实验

本章从混沌工程的核心理念到 GameDay 实操到常态化故障注入,建立完整的实践框架。实战经验大量来自混沌工程 GameDay 实战指南。

混沌工程和 SLO 体系是天生一对:SLO 量化可靠性的目标值,混沌工程验证系统是否真的能达到这个目标值。没有验证的 SLO 是未经测试的承诺,没有 SLO 的混沌是无目的的骚扰。

一、混沌工程的核心理念#

四个常见误区#

误区 1:混沌工程 = 随便 kill pod

kill pod 是最基础的一类故障,但"每小时随机 kill 一个 pod 看看"不是混沌工程,是骚扰。有价值的演练都有明确的假设——我相信 A 在 B 条件下会 C,演练就是为了验证这个信念。

误区 2:先有完美系统再演练

恰恰相反。系统越不完美,演练越值得做。第一次演练暴露的全是监控、告警、文档这些"本该有"的东西,价值极大。

误区 3:只在测试环境做

Chaos Engineering 的原始论文(Netflix Principles of Chaos)就强调 prod 演练的必要性。测试环境永远无法复现 prod 的拓扑、负载、故障模式。但是,prod 演练必须有 blast radius 限制和安全护栏

误区 4:混沌工具选型是最重要的

远远不是。工具选型是 20%,演练设计和团队文化是 80%。一个正确设计的 kubectl delete pod 比一个复杂的 Chaos Mesh workflow 价值大得多。

这才是混沌工程#

每一次实验都有明确的假设

text
【假设】在 MySQL Primary pod 被删除时:
  1. 业务 p99 latency 不超过 2s
  2. 业务错误率不超过 0.5%
  3. 总恢复时间小于 60s
  4. 告警会在 30s 内被触发并通知值班
  5. 运维文档里的 failover 步骤能被严格执行

如果所有假设都验证通过 → 系统韧性符合预期。如果有假设不通过 → 发现了需要改进的地方——这才是混沌工程的产出

混沌工程的价值不是"系统能扛"#

而是**"系统在哪儿没准备好"**。第一次做 GameDay 的团队最常见的发现:

  • 监控告警根本没接(故障打进去没人知道)
  • 关键告警路由错了(发到离职同事的邮箱)
  • Alert for: 30m 根本没在演练窗口内触发
  • Runbook 步骤已经过时了

连演练都做不成,说明系统根本没准备好被演练——这就是混沌工程的第一层价值。

混沌工程解决的三个问题#

  1. 验证假设:系统设计时的韧性假设是否真的成立
  2. 发现盲区:监控、告警、Runbook 的缺口
  3. 维持韧性:系统演进时韧性不退化(代码改了、配置变了、依赖换了)

二、GameDay:结构化故障演练#

GameDay 是混沌工程里一种特定形式的活动:时间固定、参与者固定、有主题、有假设、有复盘。相比之下"常态化自动故障注入"是另一种形式,更轻量但参与度低。两种都做——GameDay 解决团队学习和文化问题,常态化解决持续验证问题。

五个阶段#

阶段时间内容
Pre-GameDay提前 1-2 周确定主题和场景,写假设文档,审阅风险
Pre-flight当天早上确认环境状态、监控/告警、值班人员、回滚路径
Execution30-90 分钟按顺序执行实验,记录现象
Debrief15-30 分钟团队一起走一遍时间轴
Postmortem48 小时内写正式文档,列出 Action Items

实验假设模板#

text
【实验标题】MySQL 主库 failover 时业务影响
【假设】在 MySQL Primary pod 被删除时:
  1. 业务 p99 latency 不超过 2s
  2. 业务错误率不超过 0.5%
  3. 总恢复时间小于 60s
  4. 告警会在 30s 内被触发并通知值班
【演练动作】kubectl delete pod mysql-primary-0 -n database
【稳态指标】
  - sum(rate(http_requests_total{status!~"5.."}[1m])) / sum(rate(http_requests_total[1m])) > 0.995
  - histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m])) < 2
【回滚条件】
  - 错误率 > 5% 持续 60s,立即结束实验
  - 非预期的影响蔓延到无关服务
【参与人】SRE A, DBA B, 业务 owner C, 主持 D
【时间窗口】工作日 14:00-15:00(低峰)

核心原则:如果你连假设都写不清楚,说明对系统理解还不够——不该做这个实验。

GameDay 的角色#

角色职责
主持组织流程、控制时间
红队执行故障注入
蓝队响应故障、做恢复操作
观察员记录现象、不参与操作
业务 owner评估业务影响

红队和蓝队分开是为了模拟真实故障——蓝队不知道红队会做什么,响应过程更真实。

三、故障场景库#

一年实践下来整理的故障剧本,按影响维度分类:

基础设施层#

  1. Worker node 强制 drain(模拟 AZ 故障)
  2. Karpenter 节点突然被回收
  3. kubelet 不可用导致 pod 全 NotReady
  4. 某 node 磁盘写满 /var/lib/kubelet
  5. 某 node 时钟偏移 5 分钟
  6. 某 node 网络 500ms 延迟
  7. 某 node 网络 10% 丢包
  8. 整个 AZ 出流量被拒(模拟跨区故障)

Pod / 应用层#

  1. 业务 pod 被 kill
  2. 业务 pod OOM
  3. 业务 pod CPU 被 throttle 到 100%
  4. 业务 pod 磁盘写满 /tmp
  5. 业务 pod 被 stop-the-world(SIGSTOP)
  6. sidecar(envoy)被 kill

网络层#

  1. DNS 解析失败(coredns 全挂)
  2. DNS 解析慢(1s latency)
  3. 业务 pod 无法访问 service ClusterIP
  4. 跨 namespace 通信被 NetworkPolicy 拒绝
  5. 业务 pod 到外部 API 的出口丢包
  6. TLS 握手失败(CA 证书过期)

存储层#

  1. MySQL Primary 被 kill(failover)
  2. PostgreSQL 主从切换
  3. Redis 主节点失联
  4. Kafka broker 被 kill
  5. S3 区域性不可用(通过 networkchaos 模拟)
  6. EFS 挂载变成 IO 抖动

中间件#

  1. Istio Pilot 重启
  2. NGINX Ingress 全部重启
  3. Cert-manager 停止工作
  4. 集群 CA 证书过期
  5. etcd leader 选举

业务依赖#

  1. 上游 API 返回 500
  2. 上游 API 延迟 10s
  3. 上游 API 断连
  4. 消息队列消费停止
  5. 下游数据库只读

人为故障#

  1. 误删一个关键 Deployment
  2. GitOps 配置错误触发雪崩部署
  3. Helm upgrade 失败
  4. 误改 DNS 记录

每个剧本都有一份"预期行为 + 观测手段 + 回滚步骤"的文档。场景库是一年下来最大的资产,远比任何工具选型重要

场景库的维护#

场景库不是建完就完事,需要持续维护:

  • 新故障模式加入(线上出过的事故要补到场景库)
  • 老场景定期 review(系统变了,老场景可能不再适用)
  • 每个场景有 owner(谁负责维护这个场景的文档)
  • 场景分级(基础 / 进阶 / 复合)

四、工具选型#

Chaos Mesh#

PingCAP 开源,CNCF 孵化(incubating)项目。在 K8s 原生性和 UI 上做得最好。

优点:

  • K8s CRD 原生,kubectl apply -f pod-kill.yaml 就能跑
  • 丰富的故障类型:pod/network/stress/dns/io/time/kernel/http
  • Dashboard 直接看实验状态
  • Schedule 支持 cron 常态化注入
  • Workflow 支持复杂组合场景

缺点:

  • 对非 K8s 环境(EC2、裸机)支持差
  • RBAC 粒度不够细,容易给太多权限
  • StressChaos 依赖 stress-ng,容器镜像要自己构建
yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: kill-order-api
  namespace: chaos-testing
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces:
      - order
    labelSelectors:
      app: order-api
  duration: "30s"

LitmusChaos#

CNCF incubating。设计理念是"实验即 CR",每个实验是一个 ChaosExperiment 资源,加上一个 ChaosEngine 去触发。

优点:

  • ChaosHub 有丰富的公共实验
  • 更强调"实验参数化 + 可复用"
  • 和 Argo Workflow 集成好

缺点:

  • 学习曲线比 Chaos Mesh 陡
  • UI 偏管理视角,不如 Chaos Mesh 易用

自研脚本#

对于简单场景,一个 Bash 脚本 + kubectl + tc + iptables 就够了:

bash
#!/usr/bin/env bash
set -euo pipefail

NAMESPACE=order
POD=$(kubectl get pod -n $NAMESPACE -l app=order-api -o name | head -1)

kubectl exec -n $NAMESPACE $POD -- \
  tc qdisc add dev eth0 root netem loss 10% delay 200ms

trap 'kubectl exec -n $NAMESPACE $POD -- tc qdisc del dev eth0 root' EXIT

echo "注入完成,5 分钟后自动清理"
sleep 300

这段脚本的价值在于"trap 兜底回滚"——任何异常退出都会清理 tc 规则。

选型建议#

  • GameDay 用 Chaos Mesh + 脚本混合:CR 定义标准场景,脚本处理非 CR 场景
  • 常态化注入用 Chaos Mesh Schedule
  • 跨集群和非 K8s 故障用 AWS Fault Injection Simulator 或脚本

五、安全护栏:演练不能变成事故#

生产环境演练必须有严格的安全机制:

1. 爆炸半径(Blast Radius)限制#

每次实验只影响最小单元:

  • Pod-level:只 kill 一个 pod
  • Node-level:只影响一个 node
  • Service-level:只对一个 service 注入
  • Tenant-level:只影响一个租户

Chaos Mesh 的 mode: one 确保只影响最少目标。生产演练永远用 onefixed-percent 配合小比例。

2. Stop-Loss 自动回滚#

基于稳态指标的自动熔断:

yaml
# CronJob 监控,触发阈值立即清理所有 chaos 资源
apiVersion: batch/v1
kind: CronJob
metadata:
  name: chaos-stop-loss
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: checker
            image: curlimages/curl
            command: ["sh", "-c"]
            args:
              - |
                err_rate=$(curl -s "http://prometheus/api/v1/query?query=..." | jq ...)
                if [ "$err_rate" -gt "5" ]; then
                  kubectl delete -n chaos-testing podchaos --all
                  kubectl delete -n chaos-testing networkchaos --all
                  echo "熔断:错误率 $err_rate%"
                fi

3. 时间窗口限制#

只在"工作日 14:00-16:30"做生产演练——值班人齐、团队清醒、用户流量未到晚高峰。演练脚本开头强制检查时间:

bash
HOUR=$(date +%H)
DAY=$(date +%u)
if [ "$DAY" -ge 6 ] || [ "$HOUR" -lt 14 ] || [ "$HOUR" -ge 17 ]; then
    echo "不在允许的演练窗口"
    exit 1
fi

4. 审批和公告#

  • 演练前 2 天在团队频道公告
  • 生产演练需要 SRE Lead 和业务 owner 审批
  • 演练中在全员频道实时播报
  • 结束后写简报

5. 权限隔离#

执行 chaos 实验的 ServiceAccount 权限尽量窄。Chaos Mesh 有 ChaosMeshAllowList 机制,限制能操作的命名空间。

六、案例:一次简单 kill pod 发现的 7 个问题#

演练动作:kubectl delete pod order-api-xxx。看似最简单的实验,结果发现:

  1. preStop 没配:pod 被立刻 SIGTERM,in-flight 请求全挂
  2. readiness probe 滞后:新 pod 启动后 JIT 还没 warm up,前 2 秒 p99 飙 5 倍
  3. Service LB 刷新慢:kube-proxy 刷新 iptables 有 10 秒延迟
  4. 下游重连不释放:gRPC 长连接没及时探测 broken
  5. Prometheus up 没告警:pod 挂了但告警规则没覆盖
  6. Runbook 没有紧急复活步骤:业务团队不知道怎么处理
  7. PodDisruptionBudget 没配:整个 namespace 都没 PDB

一个 delete pod 带出一串改进项——这就是混沌工程的真正价值。最简单的实验往往能发现最多问题,因为它触及了系统最基础的韧性机制。

七、复盘:比执行重要得多#

没复盘的演练等于没演练

复盘模板#

text
# GameDay 复盘 - 2026-07-01

## 演练主题
MySQL 主库 failover 场景

## 时间轴
14:00 宣布开始
14:01 执行 kubectl delete pod mysql-primary-0
14:02 告警 MySQLPrimaryDown 触发,钉钉收到
14:02:30 业务 p99 从 150ms 涨到 800ms
14:03 新 primary 选举完成
14:04 p99 回落到 200ms
14:06 业务恢复至基线
14:10 实验结束,清理

## 假设验证
[✓] 业务 p99 不超过 2s
[✗] 业务错误率不超过 0.5%(实际观测 1.2%)
[✓] 总恢复时间 < 60s(实际 40s)
[✓] 告警 30s 内触发
[✗] 运维文档能被严格执行(step 3 描述不准)

## 发现的问题
1. 错误率超出阈值:客户端连接池没有快速剔除失效连接
2. 运维文档步骤 3 的命令已经过时
3. 告警聚合导致钉钉消息被合并
4. Grafana dashboard 的 p99 面板 delay 了 30s

## Action Items
- [ ] JDBC 连接池加 validateOnBorrow(@张三,1 周)
- [ ] 更新 failover runbook(@李四,本周)
- [ ] 告警聚合窗口从 5m 改 30s(@王五,本周)
- [ ] Grafana p99 面板加 1m 对比线(@王五,本周)

## 没验证到的
- 跨区域 failover 没测
- 长事务在切换时的行为没观察

核心原则:每条问题必须有对应的 Action Item,有 owner 和 deadline。下一次演练开始前要 review 上一次的 Action Item 执行状态。

八、常态化故障注入#

GameDay 是手动的、低频的。真正把混沌工程变成日常保障是常态化注入

yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: Schedule
metadata:
  name: daily-pod-kill
spec:
  schedule: "0 3 * * 1-5"   # 工作日凌晨 3 点
  historyLimit: 10
  type: PodChaos
  podChaos:
    action: pod-kill
    mode: one
    selector:
      namespaces: [order]
      labelSelectors:
        chaos-candidate: "true"
    duration: "30s"

常态化的关键做法#

  1. 业务需要主动加 chaos-candidate: "true" label 才会被 kill,opt-in 制度
  2. 从"每周一次"开始,逐步提高到"每天一次",最后到"每小时一次"
  3. 每次注入都通过钉钉简报形式发到团队频道,保持可见性
  4. 一旦业务失败率涨到阈值,自动停一周再重启

常态化的价值#

常态化注入的价值不在于"发现新问题",而在于让系统持续维持可恢复性。团队知道凌晨有自动 kill 后,对 deployment 的 graceful shutdown、readiness probe、retry 的重视程度都会上一个台阶。

这相当于"持续的韧性体检"——不体检不知道哪里有问题,定期体检保持健康。

九、组织推进:说服团队和管理层#

推动混沌工程最难的不是技术,是组织。几个有效的切入角度:

  1. 用事故讲故事。每次真实事故复盘后,问"这个问题可以通过演练提前发现吗?"
  2. 小范围试点。找一个有痛点的业务团队做第一次 GameDay,拿到改进项和改进后的系统,作为样板
  3. 转化为 SLO 语言。混沌工程的产出是"SLO 的可信度",没有演练的 SLO 是未经验证的承诺
  4. 金字塔推广:技术人员 → 技术主管 → 业务 owner → CTO
  5. 不要神化。混沌工程不是银弹,它只解决"系统在面对已知故障时是否足够健壮"这一类问题

十、文化准则#

四条混沌工程文化准则:

  1. 永远不在没有人盯着的时候做 prod 演练。自动化注入可以无人值守,但 GameDay 必须有专人看监控
  2. 实验失败不是团队失败。发现问题是演练的目标,把问题记录下来并改进比"没出事"有价值
  3. 不做没有假设的实验。随机 kill pod 是 anti-pattern
  4. 不做没有复盘的实验。演练完大家拍拍屁股走人,比没做还糟糕

十一、落地路径#

text
Month 1: 搭 Chaos Mesh,dev 环境跑 pod-kill,做一次完整 GameDay,跑通流程
Month 2-3: 扩到 staging,覆盖 10-15 个场景,建立场景库和文档
Month 4-6: 谨慎推 prod,非高峰期 pod-kill + network-delay,建立审批和回滚机制
Month 6+: 常态化注入启动,每周 GameDay,每月一次"复合故障"演练

一年后两个明显变化:

  • 业务代码的 resiliency 显著提升(retry、超时、熔断普遍有了)
  • 团队对线上事故的反应速度变快(流程熟了)

这两件事都是真金白银省下来的 downtime。

实战要点#

  1. 第一次演练必须选非生产环境。先跑通流程、暴露监控/告警/文档的缺口。
  2. 没有假设的实验是骚扰。写不清楚"验证什么"就别做。
  3. 爆炸半径控制是铁律。生产演练只用 mode: one,配自动回滚。
  4. 复盘比执行重要。没复盘的演练比不做还糟。
  5. 先从简单场景开始。pod-kill 的价值通常比复杂场景还大。
  6. 场景库是长期资产。每次事故后把新故障模式补进场景库。
  7. 常态化注入是终极目标。让韧性验证从"项目"变成"日常"。
  8. 演练要审审批公告。不能"偷偷"做生产演练,必须让所有相关方知道。
  9. 复合故障要定期演练。单一故障系统通常能扛,复合故障才是真正的韧性考验。
  10. 演练结果要和 SLO 关联。每次演练后更新"SLO 可信度"评估。

小结#

混沌工程不是银弹,它只解决"系统在面对已知故障模式时是否足够健壮"这类问题。但它和 SLO 体系结合起来威力巨大:SLO 量化可靠性的目标值,混沌工程验证系统是否真的能达到这个目标值。没有验证的 SLO 是未经测试的承诺。

判断一个团队的混沌工程是否成熟,看三件事:有完整的场景库、生产演练有常态化注入、每次演练有复盘和 Action Items 跟踪。三件事都做到是成熟团队;只做到一两件是"在推进中";一件都没做,SLO 就是"挂在墙上的口号"——没人知道系统是否真能达到。

混沌工程的最高境界是"无聊"——每次演练都按预期通过,没有惊喜。无聊的演练说明系统真的健壮了。但这不代表可以停——系统在演进,韧性需要持续验证。