故障演练:Chaos Mesh 与应急 SOP
一句话定位:没出过事的集群最危险——主动制造故障,才能在真出事时从容应对。
写在前面
我带过一个团队,集群跑了半年没出过故障,大家觉得"稳了"。结果某天一个 etcd 节点磁盘满,触发连锁反应,半数 Pod 被驱逐,业务挂了 40 分钟才定位到根因。事后复盘,我们一致同意上 Chaos Mesh 做故障演练。半年里我们主动注入了 30 多种故障,提前暴露了 5 个潜在隐患。从此团队的口头禅变成:"不演练,就没有真正的稳。"这篇把 Chaos Mesh 故障注入和应急 SOP 一起讲,让"主动出事"成为运维习惯。
核心问题
- 没出过事的集群最危险,怎么主动制造故障?Chaos Mesh 注入网络/IO/Pod/时间等故障,模拟真实异常。
- 演练怎么设计才不搞砸生产?稳态假设 → 受控注入 → 观察 → 恢复 → 复盘,严格守边界。
- 出事时怎么快速响应?应急 SOP 分级响应,通报链清晰,复盘闭环。
一、原理剖析
1.1 Chaos Mesh 架构
Chaos Mesh 是 PingCAP 开源的云原生混沌工程平台:
核心组件:
- chaos-controller-manager:控制器,监听 Chaos CRD,调度实验
- chaos-daemon:DaemonSet,在每个节点执行故障注入(网络 tc、IO 等)
- chaos-dashboard:Web UI,管理实验和归档
- chaosctl:命令行工具
1.2 故障注入类型全景
Chaos Mesh 提供丰富的 CRD,覆盖主流故障类型:
| CRD | 类型 | 模拟什么 | 真实场景对应 |
|---|---|---|---|
| PodChaos | Pod | Pod Kill/Pod Failure/Container Kill | 节点宕机、OOMKill |
| NetworkChaos | 网络 | 延迟/丢包/重复/带宽限制 | 网络抖动、跨机房延迟 |
| IOChaos | 磁盘 IO | 读写延迟/错误/有限带宽 | 磁盘慢、IO 阻塞 |
| TimeChaos | 时钟 | 时间偏移 | NTP 失同步 |
| StressChaos | 资源 | CPU/内存压测 | 资源争抢、内存泄漏 |
| DNSChaos | DNS | DNS 解析失败/延迟 | DNS 服务故障 |
| HTTPChaos | HTTP | 请求延迟/错误响应 | 上游服务异常 |
| JVMChaos | JVM | 方法异常/延迟/GC | Java 应用故障 |
1.3 演练工作流设计
一次完整的演练遵循稳态假设方法:
稳态假设(Steady State Hypothesis)是核心——先定义"什么是正常",再注入故障看系统是否仍保持正常。比如"P99 延迟 < 200ms"“错误率 < 0.1%”,注入后系统违反假设就说明有隐患。
二、实战操作
2.1 Chaos Mesh 安装
# 添加 Chaos Mesh 仓库helm repoaddchaos-mesh https://charts.chaos-mesh.org helm repo update# 安装(指定版本支持 K8s 1.30)helminstallchaos-mesh chaos-mesh/chaos-mesh\-nchaos-testing\--create-namespace\--version2.7.0\--setchaosDaemon.runtime=containerd\--setchaosDaemon.socketPath=/run/containerd/containerd.sock\--setcontrollerManager.replicaCount=3\--setdashboard.replicaCount=2\--setdashboard.securityMode=false# 生产可开启 RBAC# 验证kubectl get pods-nchaos-testing# chaos-controller-manager / chaos-daemon / chaos-dashboard 都 Running# 暴露 dashboard(测试用)kubectl port-forward-nchaos-testing svc/chaos-dashboard2333:2333# 访问 http://localhost:23332.2 PodChaos 实验:模拟 Pod 被杀
模拟某个 Deployment 的 Pod 被随机杀死,验证副本自愈:
# podchaos-kill.yamlapiVersion:chaos-mesh.org/v1chaoskind:PodChaosmetadata:name:pod-kill-app1namespace:chaos-testingspec:action:pod-kill# 杀 Podmode:one# 一次杀一个containerName:""selector:namespaces:-app1labelSelectors:"app":"backend"# 只针对 backendscheduler:cron:"@every 5m"# 每 5 分钟杀一次duration:"30m"# 演练持续 30 分钟kubectl apply-fpodchaos-kill.yaml# 观察实验状态kubectl get podchaos-nchaos-testing kubectl describe podchaos pod-kill-app1-nchaos-testing# 观察 Pod 自愈watch-n2'kubectl get pods -n app1 -l app=backend'2.3 NetworkChaos 实验:模拟网络延迟
模拟 app1 → app2 的网络延迟,验证超时重试:
# networkchaos-delay.yamlapiVersion:chaos-mesh.org/v1chaoskind:NetworkChaosmetadata:name:net-delay-app1-to-app2namespace:chaos-testingspec:action:delaymode:all# 所有匹配 Podselector:namespaces:-app1labelSelectors:"app":"backend"direction:to# 出方向流量target:selector:namespaces:-app2labelSelectors:"app":"frontend"mode:alldelay:latency:"500ms"# 延迟 500mscorrelation:"50"# 50% 相关性jitter:"100ms"# 抖动 100msduration:"10m"kubectl apply-fnetworkchaos-delay.yaml# 观察 app1 调 app2 的延迟变化kubectlexec-napp1 deploy/backend --curl-w"%{time_total}\n"-o/dev/null-shttp://frontend.app2:802.4 IOChaos 实验:模拟磁盘慢
模拟 PV 读写延迟,验证应用对慢 IO 的容忍:
# iochaos-delay.yamlapiVersion:chaos-mesh.org/v1chaoskind:IOChaosmetadata:name:io-delay-app1namespace:chaos-testingspec:action:latencymode:allselector:namespaces:-app1labelSelectors:"app":"db"volumePath:/var/lib/postgresql/datapath:"/var/lib/postgresql/data/**/*"delay:"200ms"# 每次 IO 延迟 200mspercent:50# 50% 的 IO 受影响duration:"10m"2.5 StressChaos 实验:模拟 CPU 打满
# stresschaos-cpu.yamlapiVersion:chaos-mesh.org/v1chaoskind:StressChaosmetadata:name:cpu-stress-app1namespace:chaos-testingspec:mode:oneselector:namespaces:-app1labelSelectors:"app":"backend"stressors:cpu:workers:2load:80# CPU 打到 80%duration:"5m"2.6 演练工作流(Workflow CRD)
把多步演练编排成 Workflow,串行/并行执行:
# workflow-drill.yamlapiVersion:chaos-mesh.org/v1alpha1kind:Workflowmetadata:name:app1-drillnamespace:chaos-testingspec:entry:maintemplates:-name:maintemplateType:Serial# 串行children:-kill-pod-network-delay-cpu-stress-name:kill-podtemplateType:PodChaosdeadline:"5m"podChaos:action:pod-killmode:oneselector:namespaces:[app1]labelSelectors:{"app":"backend"}-name:network-delaytemplateType:NetworkChaosdeadline:"10m"networkChaos:action:delaymode:allselector:namespaces:[app1]labelSelectors:{"app":"backend"}delay:latency:"300ms"direction:to-name:cpu-stresstemplateType:StressChaosdeadline:"5m"stressChaos:mode:oneselector:namespaces:[app1]labelSelectors:{"app":"backend"}stressors:cpu:workers:2load:90kubectl apply-fworkflow-drill.yaml kubectl get workflow-nchaos-testing# dashboard 里可看到可视化执行流程三、踩坑与排查
坑1:Chaos Mesh 注入后没生效
现象:创建了 PodChaos,但目标 Pod 没被杀。
原因:selector 没匹配到 Pod,或 chaos-daemon 没在该节点运行。
解决:
# 1. 检查 selector 是否匹配kubectl get pods-napp1-lapp=backend# 没结果说明 label 写错# 2. 查看 experiment 状态kubectl get experiment-nchaos-testing kubectl describe experiment<name>-nchaos-testing# 看 failed 或 not match 信息# 3. 确认 chaos-daemon 在所有节点 Runningkubectl get ds-nchaos-testing chaos-daemon坑2:网络故障注入后没清理干净
现象:NetworkChaos 已删除,但节点上 tc 规则还在,网络仍异常。
原因:chaos-daemon 异常退出,没清理 tc 规则。
解决:
# 手动清理 tc 规则ssh<node>tc qdisc del dev eth0 root# 或用 chaosctl 工具清理curl-sSLhttps://mirrors.chaos-mesh.org/latest/install.sh|shchaosctl clean坑3:演练误伤生产,业务挂了
现象:演练 Pod Kill 误删了没有副本的 Pod,业务中断。
原因:selector 范围太大,或没设演练边界。
解决(预防为主):
- 演练前确认目标 Pod 都有 ≥2 副本 + PDB
- 用 namespace 隔离演练对象
- 先在测试环境演练,验证后再上预发
- 设置
duration上限,防实验忘记删 - 开启
--webhook拦截高风险注入
四、应急 SOP 与复盘
4.1 应急响应分级
| 等级 | 定义 | 响应时间 | 通报范围 | 处理人 |
|---|---|---|---|---|
| P0 | 核心业务全挂 | <5 分钟 | 全员+管理层 | 值班 SRE |
| P1 | 核心业务降级 | <15 分钟 | SRE+业务负责人 | 值班 SRE |
| P2 | 非核心受影响 | <30 分钟 | SRE 团队 | 值班 SRE |
| P3 | 单节点/告警 | <2 小时 | SRE 内部 | 当班 SRE |
4.2 应急响应 SOP
┌─────────────────────────────────────────────┐ │ 应急响应 SOP 流程 │ ├─────────────────────────────────────────────┤ │ 1. 告警触发 → 值班确认(5 分钟内) │ │ 2. 通报:群里发"P0 事件,启动应急" │ │ 3. 止血:先恢复业务(重启/回滚/切流) │ │ 4. 定位:查日志、metrics、最近变更 │ │ 5. 修复:根因修复 │ │ 6. 验证:业务恢复正常 │ │ 7. 收尾:通报"已恢复",建复盘工单 │ │ 8. 复盘:24 小时内出复盘报告 │ └─────────────────────────────────────────────┘4.3 事后复盘模板
# 故障复盘报告:<标题> ## 一、故障概述 - 发生时间:2026-07-18 02:00 ~ 02:40(40 分钟) - 影响范围:app1 命名空间所有服务,约 5000 用户受影响 - 故障等级:P0 ## 二、故障影响 - 业务层面:订单服务不可用,损失预估 X 万 - 数据层面:无数据丢失 - 用户层面:下单失败,部分用户重复下单 ## 三、时间线 | 时间 | 事件 | |---|---| | 02:00 | 告警触发:app1 Pod 大量 CrashLoopBackOff | | 02:05 | 值班 SRE 确认,启动应急 | | 02:10 | 初步定位:etcd db 满,apiserver 只读 | | 02:20 | 执行 etcd compact + defrag | | 02:30 | apiserver 恢复,Pod 重建 | | 02:40 | 全部业务恢复 | ## 四、根因分析 - 直接原因:etcd db 写满(2GB quota),转只读保护 - 深层原因:未开启 auto-compaction,db 增长无监控 ## 五、改进措施 | 措施 | 负责人 | 截止日期 | 状态 | |---|---|---|---| | 开启 etcd auto-compaction | 张三 | 2026-07-20 | 进行中 | | 增加 etcd db size 监控告警(70%) | 李四 | 2026-07-19 | 完成 | | 把 etcd quota 调到 8GB | 王五 | 2026-07-20 | 进行中 | | 增加 Chaos Mesh 演练:etcd 满场景 | 赵六 | 2026-07-25 | 计划中 | ## 六、经验教训 - 备份和监控同等重要,缺一不可 - etcd 是集群命根子,要有专项巡检 - 应急流程要演练,不能只在文档里4.4 演练安全边界
- 演练范围明确(selector 精确匹配,namespace 隔离)
- 演练目标 Pod 必须有 ≥2 副本 + PDB
- 每个实验设
duration上限,防遗忘 - 先测试环境,再预发,最后生产灰度
- 演练前通知业务方,避开高峰
- 准备一键回滚(删除 Chaos CR 即可恢复)
- 演练时有人盯监控,异常立即终止
- 生产演练从低烈度开始(Pod Kill 单个 → 网络延迟 → 资源压力)
- 记录演练日志,便于复盘
五、最佳实践
- 生产集群部署 Chaos Mesh,定期演练(每月一次)
- 用 Workflow CRD 编排多步演练
- 稳态假设前置:先定义 SLO,再注入故障
- 演练覆盖:Pod/网络/IO/CPU/内存/DNS 全类型
- 应急 SOP 贴在值班台,P0/P1 流程烂熟于心
- 每次故障 24 小时内出复盘报告
- 复盘聚焦改进措施,不追责
- 改进措施跟踪到完成,闭环管理
- 演练结果归档,形成故障知识库
- chaos-daemon 资源给足,避免自身故障
- dashboard 开启 RBAC,限制演练权限
- 把"演练成功率"作为 SRE 团队 KPI
六、小结
混沌工程的核心思想是"主动拥抱故障"。与其等故障找上门,不如我们主动制造可控的故障,提前暴露系统的薄弱点。Chaos Mesh 让故障注入变得声明式、可编排、可观测,但工具只是手段,真正有价值的是"稳态假设 → 注入 → 观察 → 复盘"这套方法论。配合应急 SOP 和复盘机制,你的团队就能从"被动救火"升级为"主动防御"。没出过事的集群最危险——把这句话刻在心里,把演练变成习惯,真到出事那天,你才能从容不迫。
思考题
- PodChaos 的
pod-kill和container-kill有什么区别?分别模拟什么场景? - 演练时稳态假设 SLO 设的太严(如错误率 0),会导致什么问题?应该怎么设?
- 生产环境做 Chaos Mesh 演练,最大的风险是什么?如何把风险降到最低?
延伸阅读
- Chaos Mesh 官方文档:https://chaos-mesh.org/docs/
- 混沌工程原则:https://principlesofchaos.org/
- Google SRE 应急响应:https://sre.google/sre-book/incident-response/