混沌工程不是随机搞破坏
很多人把混沌工程理解成“在线上随便杀几个 Pod,看系统会不会挂”,这既危险又没意义。它的真实目的是用受控的实验,把系统在故障下的行为从“未知”变成“已知”:哪个依赖断了会降级、哪个超时配置会导致级联失败、告警能不能在 1 分钟内触发。
关键在“受控”。没有稳态假设、没有爆炸半径、没有自动中止的故障注入,只是一次事故演练,而且很可能演成真事故。
实验的四个要素
稳态假设:先定义“正常”的可测指标,比如“下单接口成功率高于 99.5%、P99 低于 300ms、订单创建延迟小于 1 秒”。实验期间这些指标必须持续被采集,否则你无法判断影响。
爆炸半径:从最小开始。单个 Pod、单个可用区、单个非核心依赖,再逐步扩大。第一轮实验永远不要碰核心链路和全量流量。
实验方法:延迟、丢包、错误码、CPU/内存压力、Pod 杀死、依赖不可用、时钟偏移。一次只注入一种故障,否则出问题无法归因。
中止条件:稳态指标跌破阈值时自动停止实验并回滚,同时保留人工急停按钮。这一步要在实验开始前就配置好,而不是等出事了再手动操作。
一次网络延迟实验
假设要验证“库存服务变慢时,订单服务能否在 800ms 内降级而不是雪崩”。用 Chaos Mesh 给订单到库存的调用加 300ms 延迟:
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: order-to-inventory-delay
namespace: shop-prod
spec:
action: delay
mode: one
selector:
namespaces:
- shop-prod
labelSelectors:
app: order-api
delay:
latency: "300ms"
jitter: "50ms"
correlation: "25"
direction: to
target:
mode: all
selector:
namespaces:
- shop-prod
labelSelectors:
app: inventory-api
duration: "5m"
scheduler:
cron: "@every 168h"
mode: one 表示只影响一个订单服务 Pod,direction: to 表示只影响出方向,target 锁定被调用的库存服务。这三处共同决定了爆炸半径。
实验期间要持续采样稳态指标:
for i in $(seq 1 300); do
code=$(curl -s -o /dev/null -w '%{http_code}' -m 2 \
http://order-api.shop-prod/actuator/health/readiness)
echo "$(date +%H:%M:%S) readiness=$code"
sleep 1
done
更完整的做法是把成功率、P99、熔断器状态、降级次数一起打到实验面板上,与实验开始时间对齐。只看日志里有没有报错,会漏掉“没有报错但请求全部变慢”的情况。
从演练到常态化
第一次实验通常是手工的:挑一个低峰窗口,业务方知情,值班同学盯着,出问题立刻回滚。这套流程跑顺之后,可以逐步自动化:
- 把实验写成 YAML 存进仓库,和告警规则、runbook 放在一起;
- 在预发环境按周定时跑,结果自动出报告;
- 生产环境只在低峰窗口、且稳态指标正常时允许触发,并强制设置
duration; - 每次实验的结论要落成具体改动:调超时、加降级、改告警阈值、补 runbook。
没有产出改动的演练,本质上是一次团建。衡量混沌工程是否有效,看的不是做了多少次实验,而是“实验暴露的问题被修复了多少”。
常见坑
在没有可观测性的系统上做实验。 故障注入了,但没人能说清影响范围,最后只能等用户投诉。
一次注入多种故障。 网络延迟加 Pod 杀死同时上,故障恢复了也不知道是谁的功劳,实验结论不可信。
爆炸半径直接开到 100%。 生产全量丢包,本意是验证容灾,实际结果是全站不可用。要从一个 Pod、百分之一流量起步。
忽略依赖的重试放大。 注入 300ms 延迟后,上游重试三次,下游实际收到三倍流量,实验结论变成“系统扛不住三倍流量”,而不是原本想验证的超时治理。
实验后不复盘。 记录里只有“实验成功”四个字,没有指标曲线、没有发现的问题、没有后续动作,下次还得从头再来。
把混沌工程当成常驻故障源。 实验必须有明确的开始、结束和清理步骤。忘了清理的 NetworkChaos 会一直生效,直到有人发现某个接口莫名慢了两周。
评论