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 价值大得多。
这才是混沌工程#
每一次实验都有明确的假设:
【假设】在 MySQL Primary pod 被删除时:
1. 业务 p99 latency 不超过 2s
2. 业务错误率不超过 0.5%
3. 总恢复时间小于 60s
4. 告警会在 30s 内被触发并通知值班
5. 运维文档里的 failover 步骤能被严格执行如果所有假设都验证通过 → 系统韧性符合预期。如果有假设不通过 → 发现了需要改进的地方——这才是混沌工程的产出。
混沌工程的价值不是"系统能扛"#
而是**"系统在哪儿没准备好"**。第一次做 GameDay 的团队最常见的发现:
- 监控告警根本没接(故障打进去没人知道)
- 关键告警路由错了(发到离职同事的邮箱)
- Alert for: 30m 根本没在演练窗口内触发
- Runbook 步骤已经过时了
连演练都做不成,说明系统根本没准备好被演练——这就是混沌工程的第一层价值。
混沌工程解决的三个问题#
- 验证假设:系统设计时的韧性假设是否真的成立
- 发现盲区:监控、告警、Runbook 的缺口
- 维持韧性:系统演进时韧性不退化(代码改了、配置变了、依赖换了)
二、GameDay:结构化故障演练#
GameDay 是混沌工程里一种特定形式的活动:时间固定、参与者固定、有主题、有假设、有复盘。相比之下"常态化自动故障注入"是另一种形式,更轻量但参与度低。两种都做——GameDay 解决团队学习和文化问题,常态化解决持续验证问题。
五个阶段#
| 阶段 | 时间 | 内容 |
|---|---|---|
| Pre-GameDay | 提前 1-2 周 | 确定主题和场景,写假设文档,审阅风险 |
| Pre-flight | 当天早上 | 确认环境状态、监控/告警、值班人员、回滚路径 |
| Execution | 30-90 分钟 | 按顺序执行实验,记录现象 |
| Debrief | 15-30 分钟 | 团队一起走一遍时间轴 |
| Postmortem | 48 小时内 | 写正式文档,列出 Action Items |
实验假设模板#
【实验标题】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 | 评估业务影响 |
红队和蓝队分开是为了模拟真实故障——蓝队不知道红队会做什么,响应过程更真实。
三、故障场景库#
一年实践下来整理的故障剧本,按影响维度分类:
基础设施层#
- Worker node 强制 drain(模拟 AZ 故障)
- Karpenter 节点突然被回收
- kubelet 不可用导致 pod 全 NotReady
- 某 node 磁盘写满
/var/lib/kubelet - 某 node 时钟偏移 5 分钟
- 某 node 网络 500ms 延迟
- 某 node 网络 10% 丢包
- 整个 AZ 出流量被拒(模拟跨区故障)
Pod / 应用层#
- 业务 pod 被 kill
- 业务 pod OOM
- 业务 pod CPU 被 throttle 到 100%
- 业务 pod 磁盘写满
/tmp - 业务 pod 被 stop-the-world(SIGSTOP)
- sidecar(envoy)被 kill
网络层#
- DNS 解析失败(coredns 全挂)
- DNS 解析慢(1s latency)
- 业务 pod 无法访问 service ClusterIP
- 跨 namespace 通信被 NetworkPolicy 拒绝
- 业务 pod 到外部 API 的出口丢包
- TLS 握手失败(CA 证书过期)
存储层#
- MySQL Primary 被 kill(failover)
- PostgreSQL 主从切换
- Redis 主节点失联
- Kafka broker 被 kill
- S3 区域性不可用(通过 networkchaos 模拟)
- EFS 挂载变成 IO 抖动
中间件#
- Istio Pilot 重启
- NGINX Ingress 全部重启
- Cert-manager 停止工作
- 集群 CA 证书过期
- etcd leader 选举
业务依赖#
- 上游 API 返回 500
- 上游 API 延迟 10s
- 上游 API 断连
- 消息队列消费停止
- 下游数据库只读
人为故障#
- 误删一个关键 Deployment
- GitOps 配置错误触发雪崩部署
- Helm upgrade 失败
- 误改 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,容器镜像要自己构建
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 就够了:
#!/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 确保只影响最少目标。生产演练永远用 one 或 fixed-percent 配合小比例。
2. Stop-Loss 自动回滚#
基于稳态指标的自动熔断:
# 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%"
fi3. 时间窗口限制#
只在"工作日 14:00-16:30"做生产演练——值班人齐、团队清醒、用户流量未到晚高峰。演练脚本开头强制检查时间:
HOUR=$(date +%H)
DAY=$(date +%u)
if [ "$DAY" -ge 6 ] || [ "$HOUR" -lt 14 ] || [ "$HOUR" -ge 17 ]; then
echo "不在允许的演练窗口"
exit 1
fi4. 审批和公告#
- 演练前 2 天在团队频道公告
- 生产演练需要 SRE Lead 和业务 owner 审批
- 演练中在全员频道实时播报
- 结束后写简报
5. 权限隔离#
执行 chaos 实验的 ServiceAccount 权限尽量窄。Chaos Mesh 有 ChaosMeshAllowList 机制,限制能操作的命名空间。
六、案例:一次简单 kill pod 发现的 7 个问题#
演练动作:kubectl delete pod order-api-xxx。看似最简单的实验,结果发现:
- preStop 没配:pod 被立刻 SIGTERM,in-flight 请求全挂
- readiness probe 滞后:新 pod 启动后 JIT 还没 warm up,前 2 秒 p99 飙 5 倍
- Service LB 刷新慢:kube-proxy 刷新 iptables 有 10 秒延迟
- 下游重连不释放:gRPC 长连接没及时探测 broken
- Prometheus up 没告警:pod 挂了但告警规则没覆盖
- Runbook 没有紧急复活步骤:业务团队不知道怎么处理
- PodDisruptionBudget 没配:整个 namespace 都没 PDB
一个 delete pod 带出一串改进项——这就是混沌工程的真正价值。最简单的实验往往能发现最多问题,因为它触及了系统最基础的韧性机制。
七、复盘:比执行重要得多#
没复盘的演练等于没演练。
复盘模板#
# 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 是手动的、低频的。真正把混沌工程变成日常保障是常态化注入:
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"常态化的关键做法#
- 业务需要主动加
chaos-candidate: "true"label 才会被 kill,opt-in 制度 - 从"每周一次"开始,逐步提高到"每天一次",最后到"每小时一次"
- 每次注入都通过钉钉简报形式发到团队频道,保持可见性
- 一旦业务失败率涨到阈值,自动停一周再重启
常态化的价值#
常态化注入的价值不在于"发现新问题",而在于让系统持续维持可恢复性。团队知道凌晨有自动 kill 后,对 deployment 的 graceful shutdown、readiness probe、retry 的重视程度都会上一个台阶。
这相当于"持续的韧性体检"——不体检不知道哪里有问题,定期体检保持健康。
九、组织推进:说服团队和管理层#
推动混沌工程最难的不是技术,是组织。几个有效的切入角度:
- 用事故讲故事。每次真实事故复盘后,问"这个问题可以通过演练提前发现吗?"
- 小范围试点。找一个有痛点的业务团队做第一次 GameDay,拿到改进项和改进后的系统,作为样板
- 转化为 SLO 语言。混沌工程的产出是"SLO 的可信度",没有演练的 SLO 是未经验证的承诺
- 金字塔推广:技术人员 → 技术主管 → 业务 owner → CTO
- 不要神化。混沌工程不是银弹,它只解决"系统在面对已知故障时是否足够健壮"这一类问题
十、文化准则#
四条混沌工程文化准则:
- 永远不在没有人盯着的时候做 prod 演练。自动化注入可以无人值守,但 GameDay 必须有专人看监控
- 实验失败不是团队失败。发现问题是演练的目标,把问题记录下来并改进比"没出事"有价值
- 不做没有假设的实验。随机 kill pod 是 anti-pattern
- 不做没有复盘的实验。演练完大家拍拍屁股走人,比没做还糟糕
十一、落地路径#
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。
实战要点#
- 第一次演练必须选非生产环境。先跑通流程、暴露监控/告警/文档的缺口。
- 没有假设的实验是骚扰。写不清楚"验证什么"就别做。
- 爆炸半径控制是铁律。生产演练只用
mode: one,配自动回滚。 - 复盘比执行重要。没复盘的演练比不做还糟。
- 先从简单场景开始。pod-kill 的价值通常比复杂场景还大。
- 场景库是长期资产。每次事故后把新故障模式补进场景库。
- 常态化注入是终极目标。让韧性验证从"项目"变成"日常"。
- 演练要审审批公告。不能"偷偷"做生产演练,必须让所有相关方知道。
- 复合故障要定期演练。单一故障系统通常能扛,复合故障才是真正的韧性考验。
- 演练结果要和 SLO 关联。每次演练后更新"SLO 可信度"评估。
小结#
混沌工程不是银弹,它只解决"系统在面对已知故障模式时是否足够健壮"这类问题。但它和 SLO 体系结合起来威力巨大:SLO 量化可靠性的目标值,混沌工程验证系统是否真的能达到这个目标值。没有验证的 SLO 是未经测试的承诺。
判断一个团队的混沌工程是否成熟,看三件事:有完整的场景库、生产演练有常态化注入、每次演练有复盘和 Action Items 跟踪。三件事都做到是成熟团队;只做到一两件是"在推进中";一件都没做,SLO 就是"挂在墙上的口号"——没人知道系统是否真能达到。
混沌工程的最高境界是"无聊"——每次演练都按预期通过,没有惊喜。无聊的演练说明系统真的健壮了。但这不代表可以停——系统在演进,韧性需要持续验证。