故意弄坏生产:混沌工程不是乱砸,是实验设计

故意弄坏生产:混沌工程不是乱砸,是实验设计

「这周的实验计划:在生产杀掉一个 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,一条命令见效果。

选错工具顶多效率低,没有假设的实验才是白做。

六、进生产的六条护栏:混沌验韧性,演练验流程

六条护栏逐条过,缺一条就别碰生产:

  1. 监控先行:目标系统必须有 SLI 与告警------混沌实验的第一观众是监控,不是人眼
  2. 审批与窗口:走变更单流程,爆炸半径写进审批单,避开业务高峰与大促冻结期
  3. 半径递增:单实例、1 分钟起步,同场景三次成功才放大
  4. 一键 abort + 自动到期:每个实验带 duration,值守者有权不经讨论直接中止
  5. 预算保护:错误预算剩余 <25% 时暂停混沌------预算快烧穿还注入故障,等于自纵火
  6. 先 staging 后生产:staging 复现不了的(真实数据量、真实依赖拓扑)才进生产,首轮只跑最小剂量

再划一条容易混淆的线:混沌工程和恢复演练不是一回事【从业者判断】。混沌验的是韧性机制------冗余够不够、摘除快不快、超时配得对不对,产出是架构事实;恢复演练验的是流程------谁 ack、谁决策、runbook 好不好翻,产出是熟练度。两个都该做,但别混成一锅:拿混沌实验去练值班流程,既污染归因,又练不到手。

组织形态上可以合办 GameDay:一组人操作注入,一组人只观测记分------稳态守住几个指标、恢复用了多久,规则与计分卡事先公开。节奏建议:每季度一次全组 GameDay,每周一次 15 分钟个人小实验。

七、怎么向老板申请「弄坏生产」

领导问「会不会把生产搞挂」,别回答「不会」------没人信,也不诚实。分三层答:

风险层:承认存在残余风险,但用护栏压缩它------半径从单实例起步、每个实验带 duration 与一键 abort、值守者可无讨论中止、冻结期与错误预算联动(预算 <25% 不演练)。

收益层:故障在生产必然发生,主动小剂量注入让我们在可控时间、可控半径内预演,比等真实故障来教我们便宜得多;Netflix、阿里等大规模实践可作参照。

落地层:给出可审计的推进计划------先 staging 跑三个月、SLI 齐备后再上生产、首轮只跑最小剂量、每次实验产出复盘与行动项。

老板要的不是保证不出事,是最坏情况可控、有审计痕迹【从业者判断】。拿热情去申请,拿到的是拒绝;拿护栏和推进计划去申请,拿到的是流程。

结尾:这周就能做的三件事

  1. 从近期复盘或 top 风险清单里挑一个场景(不是随手注入),用第二节的模板写一条稳态假设。
  2. 在测试集群跑一次 15 分钟最小闭环:基线 → 注入 → 对照 → abort,命令都在上文。
  3. 把实验卡模板搬进仓库,建个目录就能开始:
bash 复制代码
mkdir -p docs/experiments

留一个挑战:把你的第一条稳态假设贴到评论区。我赌一半人写得出「预期什么」,写不出「因为什么机制」------写不出那半句的,说明还没到能注入的时候。

完整实验卡、Chaos Mesh 实验 YAML 与六条护栏清单,GitHub 搜 sre-learning-hub。

相关推荐
Thneonl1 小时前
别上来就 strace:60 秒十条命令看清一台病机
后端·程序员
马剑威(威哥爱编程)1 小时前
【AI全栈后端12-11】Spring Boot 把 AI 接口真正上线扛量:限流 / 降级 / 可观测
人工智能·spring boot·后端
tachibana22 小时前
Golang 中数组和切片的区别?
开发语言·后端·golang·数组·切片
我是人✓2 小时前
Spring IOC注解全解析和Spring整合测试单元
java·后端·spring
Sam_Deep_Thinking3 小时前
CountDownLatch实现原理
java·后端·面试·程序员
高频因子挖掘机3 小时前
量化回测前的数据审计:如何判断股票历史行情是否缺失、重复或存在偏差
后端·github·api
mldong3 小时前
审批流程图上那些亮着的线,数据库里一条都没存:jeeflow 工作流引擎的高亮是重算出来的
后端·架构
geovindu3 小时前
rust: Borg Pattern(续)
开发语言·后端·设计模式·rust·博格模式