故意弄坏生产:混沌工程不是乱砸,是实验设计
「这周的实验计划:在生产杀掉一个 Pod。」
把这句话丢进团队群,通常会收获两拨人。一拨觉得你疯了;另一拨开口就问:稳态指标是什么?半径圈到哪?怎么中止?
后一拨人所在的团队,故障来了不靠运气。
混沌工程不是新词:2011 年 Netflix 上云后推出 Chaos Monkey,随机杀生产实例;2015 年归纳成 Principles of Chaos。它的定义是在生产或类生产系统上主动注入故障、观测系统响应 ,以发现未知弱点、验证韧性假设的实验方法论。
但工具和「敢砸」从来不是难点------工具人人会装,假设没人写【从业者判断】。这篇只讲后者。
一、边界:主动注入和等故障上门,学费差一个量级
故障在生产必然发生:变更、硬件、依赖,总有一个先来。你只有两个选择------等它挑时间、挑半径来教你,或者你挑时间、挑半径预演。主动小剂量注入,是拿可控的小成本,换掉不可控的大事故。
混沌工程和故障注入测试(FIT)经常被混为一谈,先分清:
| 故障注入测试 | 混沌工程 | |
|---|---|---|
| 问题形态 | 「这个 bug 修了吗?」 | 「系统会怎么失效?不知道哪会断」 |
| 方法 | 断言式:注入 A,断言 B | 探索式:扰动系统,观测稳态是否守住 |
| 结果 | 通过 / 失败 | 新认知,常发现预期之外的耦合故障 |
断言式测试问的是已知问题,探索式实验找的是未知弱点。混沌工程的产出不是「系统挺稳的」,是失效模式的新认知。
顺带一句:很多团队手里的 break 脚本、故障开关,本质都是故障注入原语,是学习的拐杖;套上假设、护栏、结论,才升级成实验。
二、稳态假设:先写下「系统应该保持什么」
混沌实验唯一的问题形态是:扰动之后,稳态守住了吗?所以第一步不是想注入什么故障,而是写下系统正常时长什么样------这就是稳态假设(steady state hypothesis)。
**没有 SLI 的混沌是砸场子------你连「坏了没有」都无法判定。**凭感觉说「好像没坏」出不了结论;拿用户投诉当探测器,实验就变成了真实事故。
模板直接抄(每个实验一份,进 git):
text
在 <SLI> 处于 <水平> 的稳态下,
注入 <故障>(爆炸半径 = <范围>),
预期 <SLI 在 SLO 内保持/在 X 分钟内恢复>,
因为 <系统的韧性机制>。
若预期不成立,说明 <韧性机制失效/缺失>,产出行动项。
一个可以直接照做的实例:
在 podinfo「5 分钟非错误响应占比 100%、响应时间 <100ms」的稳态下,注入「每分钟杀死一个 Pod」(半径 = default ns 中 app=podinfo 的一个实例),预期可用性仍 ≥99%,因为 Deployment 副本冗余 + Service 端点摘除会让流量在存活副本间继续。若跌破 99%,说明冗余或摘除机制有问题(如 readinessProbe 缺失)。
注意「因为」那半句------它是整个实验的价值所在。机制写不出来,说明你只是想看烟花。
假设里的数字不能拍脑袋,注入前先量 2~5 分钟基线。以一个每秒探测一次的探针 Pod 为例:
bash
kubectl logs pod/probe | awk '{t++; if($2==200) ok++} END{printf "samples=%d ok=%d avail=%.2f%%\n", t, ok, ok*100/t}'
# samples=240 ok=240 avail=100.00%
kubectl logs pod/probe | awk '{s=$3+0; if(s<0.1)a++; else if(s<0.3)b++; else c++} END{printf "<100ms:%d 100-300ms:%d >300ms:%d\n",a,b,c}'
# <100ms:240 100-300ms:0 >300ms:0 ← 这两行数字写进假设文档,它们才是稳态定义
注入前后没有对照数据的实验,做完也说不清稳在哪、坏在哪。
三、实验卡四要素:缺一张卡,就不准碰生产
把稳态假设扩展成一张完整的实验卡,四个字段缺一不可。
**一,假设。**即第二节那条模板,重点是可证伪:写下预期数字与背后的机制。
**二,注入变量。**一次实验只动一个变量。同时杀 Pod 又加网络延迟,无论结果好坏都无法归因------你验证的是「两种故障叠加」,不是任何单一机制。
**三,爆炸半径。**影响面要有硬边界:namespace、label、实例百分比,显式写进卡里,也写进审批单。
**四,终止条件。**两层缺一不可。人工止损线:SLI 跌破阈值、出现计划外影响,值守者立即中止,且有权不经讨论直接动手;自动到期:每个实验必带 duration,到点自动解除,防止「实验做完集群还坏着」。
abort 手段要在注入之前就验证过,不是出了事再翻文档:
bash
kubectl -n chaos-testing delete podchaos pod-kill-podinfo
# 删除实验 CR 即恢复;值守终端提前敲好这条命令,真要中止时只差一个回车
没有终止条件的实验不是实验,是赌运气的事故预告。
四、升级路径:从单 Pod 到可用区,一级一级挣门票
半径的阶梯是固定的:单 Pod → 单 Deployment → 单节点 → 控制面 → 可用区。规则也是固定的:新实验从「单实例、1 分钟」起步,同场景至少成功三次,才允许放大到下一级。
为什么要这么慢?因为半径既是安全边界,也是对照变量。只按 namespace 圈定半径是常见错误:kube-system 里的 CNI、DNS 可能被卷进来,实验同时扰动多个变量,结论不可归因;影响面还会随 namespace 内容漂移------今天 5 个 Pod,明天 50 个。
注入前先验证半径非空,一条命令:
bash
kubectl get pods -l app=podinfo -n default
# 有输出 = 半径命中;No resources found = selector 写错,注入了也「什么都没发生」
单 Pod 级别的实验长这样(Chaos Mesh):
yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-kill-podinfo
namespace: chaos-testing
spec:
action: pod-kill
mode: one
selector:
namespaces:
- default
labelSelectors:
app: podinfo
scheduler:
cron: "@every 1m"
期间持续对照观测,每 60 秒滚动输出可用性:
bash
for i in 1 2 3 4 5; do
printf '%s ' "$(date +%T)"
kubectl logs pod/probe | tail -60 | awk '{t++; if($2==200) ok++} END{printf "avail=%.2f%%\n", ok*100/t}'
sleep 60
done
判定标准要事先写在卡里:5 分钟窗口 avail ≥99%,假设成立;明显跌破,就去查 readinessProbe 与 endpoints 摘除延迟------这本身就是高价值结论。
放大半径是挣来的,不是排期排出来的。
五、工具只是注入手段:Chaos Mesh 与 ChaosBlade
工具与方法论的关系一句话:Chaos Mesh、ChaosBlade、break 脚本提供的都是故障原语;混沌工程是套在原语外面的实验设计。原语不挑人,实验设计挑。
Chaos Mesh,CNCF 孵化项目,K8s 原生,一切实验都是 CRD------实验可 git 版本化、可审计、可复现:
| CRD | 能力 |
|---|---|
| PodChaos | 杀 Pod / 持续 NotReady / 杀容器 |
| NetworkChaos | 延迟、丢包、分区 |
| StressChaos | CPU、内存压力 |
| IOChaos | 磁盘延迟与错误 |
| TimeChaos | 时钟偏移 |
| DNSChaos | 解析错误、延迟 |
| HTTPChaos | 中断、延迟、状态码篡改 |
| Schedule / Workflow | 定时与多步编排(串行、并行、条件分支) |
ChaosBlade ,阿里巴巴开源,CLI 优先:blade create <场景> 即时生效,blade destroy 恢复,也提供 Operator 把实验写成 CRD。
亮点是主机层实验------进程 CPU、网卡、磁盘、文件系统------不依赖 K8s,VM 加容器的混合环境一套工具通吃,官方中文文档完善。
选型一句话:深耕 K8s 的平台团队选 Chaos Mesh,要 Workflow 编排和全场景 CRD;混合环境、要快速见效的中文团队选 ChaosBlade,一条命令见效果。
选错工具顶多效率低,没有假设的实验才是白做。
六、进生产的六条护栏:混沌验韧性,演练验流程
六条护栏逐条过,缺一条就别碰生产:
- 监控先行:目标系统必须有 SLI 与告警------混沌实验的第一观众是监控,不是人眼
- 审批与窗口:走变更单流程,爆炸半径写进审批单,避开业务高峰与大促冻结期
- 半径递增:单实例、1 分钟起步,同场景三次成功才放大
- 一键 abort + 自动到期:每个实验带 duration,值守者有权不经讨论直接中止
- 预算保护:错误预算剩余 <25% 时暂停混沌------预算快烧穿还注入故障,等于自纵火
- 先 staging 后生产:staging 复现不了的(真实数据量、真实依赖拓扑)才进生产,首轮只跑最小剂量
再划一条容易混淆的线:混沌工程和恢复演练不是一回事【从业者判断】。混沌验的是韧性机制------冗余够不够、摘除快不快、超时配得对不对,产出是架构事实;恢复演练验的是流程------谁 ack、谁决策、runbook 好不好翻,产出是熟练度。两个都该做,但别混成一锅:拿混沌实验去练值班流程,既污染归因,又练不到手。
组织形态上可以合办 GameDay:一组人操作注入,一组人只观测记分------稳态守住几个指标、恢复用了多久,规则与计分卡事先公开。节奏建议:每季度一次全组 GameDay,每周一次 15 分钟个人小实验。
七、怎么向老板申请「弄坏生产」
领导问「会不会把生产搞挂」,别回答「不会」------没人信,也不诚实。分三层答:
风险层:承认存在残余风险,但用护栏压缩它------半径从单实例起步、每个实验带 duration 与一键 abort、值守者可无讨论中止、冻结期与错误预算联动(预算 <25% 不演练)。
收益层:故障在生产必然发生,主动小剂量注入让我们在可控时间、可控半径内预演,比等真实故障来教我们便宜得多;Netflix、阿里等大规模实践可作参照。
落地层:给出可审计的推进计划------先 staging 跑三个月、SLI 齐备后再上生产、首轮只跑最小剂量、每次实验产出复盘与行动项。
老板要的不是保证不出事,是最坏情况可控、有审计痕迹【从业者判断】。拿热情去申请,拿到的是拒绝;拿护栏和推进计划去申请,拿到的是流程。
结尾:这周就能做的三件事
- 从近期复盘或 top 风险清单里挑一个场景(不是随手注入),用第二节的模板写一条稳态假设。
- 在测试集群跑一次 15 分钟最小闭环:基线 → 注入 → 对照 → abort,命令都在上文。
- 把实验卡模板搬进仓库,建个目录就能开始:
bash
mkdir -p docs/experiments
留一个挑战:把你的第一条稳态假设贴到评论区。我赌一半人写得出「预期什么」,写不出「因为什么机制」------写不出那半句的,说明还没到能注入的时候。
完整实验卡、Chaos Mesh 实验 YAML 与六条护栏清单,GitHub 搜 sre-learning-hub。