我们部门上季度做了一个决定,把线上几个核心服务的故障演练从“一年一次、大家紧张兮兮”变成了“随时可能发生、系统自己会扛”。刚开始很多人不理解,觉得这是在给自己找麻烦。结果三个月后,真的有一次数据库抖动把某个下游服务打挂了,链路却没断,用户侧几乎无感。那一刻,所有人都觉得当初折腾这套混沌工程方案是值得的。
我想把这段时间实践 Chaos Mesh 的经验完整记录下来,包括为什么选它、怎么部署、注入哪些故障、踩了哪些坑,给正在做或者准备做稳定性建设的朋友一个参考。
1. 为什么需要混沌工程,以及为什么是 Chaos Mesh
先说一个基础问题:为什么好好的系统要主动搞破坏?
我见过太多团队对“稳定性”的理解停留在监控告警和事后复盘。但真实线上故障往往是突发的,而且有一个很反直觉的特点——故障之间会串联。你以为是数据库慢查询,实际是连接池被打满;你以为是某个 Pod 挂了,实际是负载均衡的健康检查策略有问题。这些问题你不在真实环境里触发,光靠代码评审和测试环境根本发现不了。
混沌工程的核心思路,是用一种实验性的方式,把故障当作一个可验证的假设:系统在某个故障注入下,是否能维持可用的状态。这是一个主动找漏洞的过程,而不是被动等事故。
那市场上的工具其实不少,比如阿里的 ChaosBlade、AWS 的 Fault Injection Simulator、还有个开源的 Litmus。为什么我们最终选了 Chaos Mesh?横向对比之后主要是这几点:
第一,Chaos Mesh 是云原生计算基金会旗下的开源项目,在 Kubernetes 生态里的兼容性做得最彻底。它本身就是一组 Kubernetes CRD,意味着你定义故障实验的方式和定义 Deployment、Service 完全一致,学习成本很低。而且它直接监听 Kubernetes API,故障注入的对象天然就是 Pod、容器、节点这类云原生资源。
第二,它把故障类型覆盖得很全。从 Pod 级别的删除、崩溃、CPU 满载,到网络层面的延迟、丢包、分区,再到时间层面的时钟偏移,还有压力测试需要的资源耗尽。基本覆盖了线上会遇到的绝大多数故障模式,不需要自己拼凑多种工具。
第三,有可视化面板。这一点看起来不核心,但对团队推广特别重要。混沌工程最怕的就是“搞了个实验,做了个寂寞”。面板上能清清楚楚看到实验状态、注入的对象、持续的时间、注入的故障类型,领导和同事都能直观理解,降低了推广阻力。
第四,它支持 Workflow 编排。后面我会详细讲,这功能让我们能做到“一键执行一次完整的故障演练剧本”,而不是一个个手工点。
当然不是说 Chaos Mesh 就是唯一答案。如果你的业务根本没有跑在 Kubernetes 上,或者你只需要在应用层注入故障而不是基础设施层,那另一套方案可能更合适。选择工具的前提是先想清楚自己的场景。
2. 规划故障演练前,先想清楚这三件事
很多人拿到 Chaos Mesh 第一反应就是部署、然后随便选个故障注入、看着 Pod 重启,完了,发个朋友圈说自己在做混沌工程。说实话,这样做三个月后除了团队变累没有任何收益。
我建议在动手之前,先把下面三件事想清楚。
第一件事,划定爆炸半径。混沌工程不是去生产环境乱炸。即便你要在生产环境做,也要先严格限定业务范围。我们内部把服务分成了两类:核心资金链路和一般业务链路。一般业务链路先练手,核心链路先在预发环境演练,再逐步灰度到生产。这个顺序不能反。另外,每个实验一定要有明确的“回滚”方案,Chaos Mesh 每个实验都可以定义持续时间,但你要额外准备一个手动终止的预案,防止实验对象从故障中恢复不了。
第二件事,想清楚你要验证什么假设。混沌工程每一次实验必须有一个明确的假设。比如“用户服务挂了,订单服务依赖它的缓存数据,所以不影响下单主流程”,或者“商品服务延迟增加 500ms,前端有熔断,所以 P95 延迟不会超过 1 秒”。不要把实验设计成“把它搞挂看看会发生什么”,那是故障演练,不是混沌工程。有假设,才有验证,才有后续的改进项。
第三件事,从哪类故障开始。我的经验是,别一上来就搞网络分区这种杀伤力巨大的实验,先做最容易执行、恢复成本最低的故障类型。
给大家一个推荐路线:
- 第一阶段:只做 Pod 删除和容器杀进程,训练团队的故障发现和恢复能力
- 第二阶段:引入 CPU 满载和内存压力,验证弹性伸缩和资源隔离
- 第三阶段:开始玩网络延迟和丢包,验证超时配置、重试策略、熔断降级
- 第四阶段:复杂场景组合,比如为了模拟一个节点宕机,同时注入 Pod 故障和网络分区
每一阶段都要有产出物,可以是监控告警的优化、完善一份应急手册、发现并修复一个隐藏 Bug,否则这个实验就是白做了。
3. 从零部署 Chaos Mesh 的完整过程
Chaos Mesh 的部署方式有几种:Helm Chart、手动 YAML、Operator 方式。我们用的是 Helm 方式,因为升级、回滚、配置管理都方便。
环境信息先说明一下,方便你对照:
- Kubernetes 版本 1.24 以上
- Helm 3.x
- 集群有充足资源,至少预留 2 核 4G 给 Chaos Mesh 自身组件
部署步骤大致是:
# 添加 Helm 仓库 helm repo add chaos-mesh https://charts.chaos-mesh.org helm repo update # 查看版本(建议不要盲目用最新版,先看 release notes) helm search repo chaos-mesh # 创建命名空间并安装 kubectl create namespace chaos-mesh helm install chaos-mesh chaos-mesh/chaos-mesh -n chaos-mesh --version 2.6.1安装完成后,默认会启动几个关键组件,包括 chaos-controller-manager、chaos-dashboard、chaos-daemon。这里有个概念要注意一下:chaos-daemon 是以 DaemonSet 方式跑在每个节点上的,它负责执行具体的故障注入动作。它需要访问节点的 Docker 或 containerd socket,所以安装的时候要关注节点上的容器运行时类型。
如果你的节点用的是 containerd,那默认配置基本可以直接用;如果用的是 Docker,需要设置一个额外参数:
helm install chaos-mesh chaos-mesh/chaos-mesh -n chaos-mesh \ --set chaosDaemon.runtime=docker \ --set chaosDaemon.socketPath=/var/run/docker.sock这个配置不搞对,后面网络类故障注入会直接报错,我们当初就在这卡了半天。
部署完检查一下状态:
kubectl get pods -n chaos-mesh # 预期看到 controller-manager、dashboard、daemon 都处于 Running 状态然后访问 Dashboard。默认是 ClusterIP,用 kubectl port-forward 临时转发一下:
kubectl port-forward -n chaos-mesh svc/chaos-dashboard 2333:2333浏览器打开http://localhost:2333就能看到管理界面了。
另外,务必要注意权限。Chaos Mesh 自身有 RBAC 资源配置,但它操作的资源类型非常多,跨命名空间注入需要额外授权。我们是在安装后单独创建了一个 ServiceAccount,只授予需要演练的命名空间的操作权限,而不是直接在集群级给所有权限。虽然多花了点时间,但安全性值得。
4. 核心故障注入类型逐一拆解
Chaos Mesh 支持的故障类型很多,我先挑最常用的几种,从原理到参数到适用场景都过一遍。这些内容官方文档都有,但很多细节是文档里不会强调的。
4.1 PodChaos:模拟 Pod 异常
PodChaos 是最入门也最实用的一种,最常见的动作是pod-kill和pod-failure。
pod-kill模拟的是 Pod 被人工删除或 OOM Kill,它会让 Pod 直接消失,由 Kubernetes 控制器重新调度拉起。这个实验特别适合验证 Deployment 的副本数配置是否合理、存活探针是否配置正确、资源配额是否够用。
一个简单的例子:
apiVersion: chaos-mesh.org/v1alpha1 kind: PodChaos metadata: name: pod-kill-example namespace: chaos-test spec: action: pod-kill mode: one selector: namespaces: - chaos-test labelSelectors: app: order-service duration: "30s"这里有个易踩的坑:mode: one表示随机选一个符合条件的 Pod 杀掉,duration: 30s指的是实验持续 30 秒,不是 30 秒后恢复被杀的 Pod。pod-kill的语义是“持续不断地杀符合条件的 Pod”,每轮杀完会等待重启完成再继续杀下一个。所以如果你设置 duration 为 5 分钟,实际上这 5 分钟内符合条件的 Pod 一直被轮流杀掉,模拟的就是“Pod 不断崩溃重启”的场景,而不是“杀一次然后停手”。
如果你只想模拟“一次性删除一个 Pod”,直接kubectl delete pod就行,不需要用它。团队分工不同,要注意区分。
pod-failure和pod-kill的区别是:它让 Pod 保持在 Pending 状态,并不会真正把 Pod 杀掉。这个实验模拟的是节点故障或者调度器异常导致的 Pod 不可用。这个更适合验证多副本服务的摘流机制,因为 Pending 状态下 Kubernetes 不会自动重新调度,流量就真的没地方去了,能更真实地暴露负载均衡和 DNS 缓存的问题。
4.2 NetworkChaos:模拟网络故障
网络故障是我们测出来价值最大的一类,因为现实中的网络问题真的太隐蔽了。NetworkChaos 支持延迟、丢包、乱序、带宽限制、分区。
网络延迟注入是最常用的。配置时注意几点:
apiVersion: chaos-mesh.org/v1alpha1 kind: NetworkChaos metadata: name: network-delay-example namespace: chaos-test spec: action: delay mode: one selector: labelSelectors: app: order-service delay: latency: "500ms" jitter: "100ms" correlation: "50" duration: "60s"这里的jitter是抖动,correlation是下一跳延迟和当前延迟的关联度。实战中我强烈建议加 jitter,因为真实网络延迟不是恒定值,而是有抖动的。如果你只设置固定的 500ms,很多超时重试策略反而能轻松绕过去,测不出来问题。
丢包实验是另一个重点,loss参数控制丢包百分比。注意,丢包实验结果影响最大的是 TCP 重传行为,而 TCP 重传超时时间初始值是秒级,所以当你注入 10% 丢包时,应用层感受到的延迟可能远远大于你的设置值。如果发现实验结果和预期不符,先去查 TCP 协议栈参数,不要急着怀疑 ChaOS Mesh 的注入有问题。
网络分区实验我个人建议只在预发环境做,杀伤力太大。它会在目标 Pod 上直接断掉和特定目标之间的网络连接,相当于模拟交换机故障或云厂商安全组误配置,没有完整预案前不要在生产上玩。
4.3 StressChaos:模拟资源压力
StressChaos 包含 CPU 满载和内存耗尽两类。这个主要用来验证弹性伸缩和 Pod 的资源 Limit 是否合理。
CPU 满载实验配置比较简单:
apiVersion: chaos-mesh.org/v1alpha1 kind: StressChaos metadata: name: cpu-stress-example namespace: chaos-test spec: mode: one selector: labelSelectors: app: order-service stressors: cpu: workers: 2 load: 90 duration: "60s"workers表示启动多少个压力线程,load是每个线程的负载百分比。这里有个细节:如果你的 Pod 设置了 CPU Limit,而节点上 CPU 核数没有预留足够,压测可能会触发 CPU Throttling,表现出的现象是服务处理能力下降但 CPU 使用率看起来不高。这不是故障注入失败,而是 Kubernetes 本身的资源限制机制起作用了,你可以通过kubectl top pods和容器运行时指标来区分。
内存压力实验我建议给的值一定要比 Limit 小一点,否则直接把 Pod 打 OOM kill 了,验证不到渐进式内存增长带来的问题,只看到“直接重启”。这其实也是一种故障场景,但如果你想验证的是内存泄漏后的现象,就要把压力控制在一个合适的区间。
4.4 TimeChaos:时钟偏移
TimeChaos 是比较特别的一种,做分布式系统或者鉴权相关服务的人会深有体会。它通过注入时钟偏移,验证依赖时间戳的业务逻辑是否有容错性。
举个实际例子,我们一个内部服务用 JWT 做鉴权,token 里带了签发时间和过期时间。我当时设计了一个实验,把服务 Pod 的时钟往后拨 10 分钟,结果测出了一个大问题:日志系统记录的时间和实际时间完全对不上,排查问题时根本没法定位。那次的实验结论就是:所有关键服务必须用统一日志时间,日志时间戳取请求进入网关的时间,而不是落库时本地进程的时间。
这就是混沌工程的魅力,实验前你根本想不到会暴露这种问题。
4.5 物理机故障注入
如果你的服务并没有完全容器化,Chaos Mesh 还有一个 chaosd 工具,可以注入物理机级别的故障,包括磁盘读写故障、文件系统故障、网络故障。我们当时有少部分遗留服务还在物理机上,主要用到了磁盘延迟注入。这一块配置稍微复杂,需要先在物理机上安装 chaosd 服务,再通过 Chaos Mesh 的物理机资源定义来管理。如果你的环境没有这类需求,可以先跳过,不优先投入精力。
5. 一个完整的实操案例:电商下单链路故障演练
前面把每个故障类型分开讲了,但真实场景里故障从来不是单独出现的。我来完整拆解一个我们做得比较成功的演练,你可以直接参考这个流程去设计自己的方案。
背景是这样的,我们模拟的是一个“用户下单”的简单链路:前端请求 -> API 网关 -> 订单服务 -> 库存服务 -> 支付服务(这里用 Mock)。目标是对整条链路做一次“库存服务不可用”的演练,验证订单服务是否有降级方案。
演练开始前,我们做了三件事:
第一,明确了假设:库存服务延迟超过 2 秒时,订单服务应该快速失败并返回友好提示,而不是一直阻塞,同时 Redis 中的库存缓存可以作为兜底,保证下单主流程不中断。
第二,确定了监控指标:订单接口的成功率、P95 延迟、库存服务的错误率、系统负载。并且提前设置了告警阈值。
第三,准备了终止条件:如果成功率低于 95% 或者 P95 延迟超过 3 秒,立即终止实验。
实验设计如下:
首先注入一个简单的网络延迟,从 200ms 开始,逐步递增,观察订单服务在不同延迟下的表现。这一步不是直接上故障,是让系统有一个缓冲适应期,同时验证监控指标是正常的。
然后直接注入 5% 丢包 + 500ms 延迟的组合故障,模拟真实网络抖动。这一步持续了 2 分钟,订单服务的 P95 延迟从原来的 300ms 飙升到 2.1 秒,成功率在部分 Pod 中掉到了 88%。
接着升级为库存服务完全不可用,注入pod-kill+ 网络分区,让订单服务彻底无法访问库存服务。
演练结束后,我们拿到了非常有价值的结果,也发现了三个真实问题:
第一个问题,调用库存服务的超时时间设置为 15 秒,太长了。下游故障时,订单服务线程被大量占用,最终导致整个订单服务响应变慢。后来调整超时时间为 3 秒,加上快速失败策略,问题解决。
第二个问题,Redis 缓存没有设置 null 值缓存。库存服务挂了,缓存里查不到某个商品 ID,请求直接穿透到数据库,数据库的压力反而上去了。后来加了空值缓存和逻辑过期时间。
第三个问题,监控告警没有覆盖到下游依赖的健康状态。库存服务已经挂了,但订单服务的告警还没触发,因为告警只盯应用自身的错误率。后来补了一个基于依赖健康状况的告警规则。
你可能会觉得这些问题都挺“基础”的,但事实就是很多线上事故最后复盘下来都是这类基础问题。主动演练的意义就在于:让你在自己的掌控下先发现问题,而不是等真实故障来临时被动应对。
6. 基于 Schedule 实现常态化演练,而不是一次性的活动
我一直觉得,把混沌工程做成“季度大扫除”是没意义的。真正的价值来自常态化,让系统一直都在“被验证”的状态。
Chaos Mesh 提供了一种叫 Schedule 的 CRD,可以直接按 Cron 表达式定时触发实验。这意味着你可以把故障演练做成自动化任务,让系统每周固定时间自己折腾自己。
我举个例子,我们的某个中间服务每周二凌晨两点自动执行一次 5 分钟的 CPU 满载实验:
apiVersion: chaos-mesh.org/v1alpha1 kind: Schedule metadata: name: weekly-cpu-stress namespace: chaos-test spec: schedule: "0 2 * * 2" historyLimit: 5 concurrencyPolicy: Allow type: StressChaos stressChaos: mode: one selector: namespaces: - chaos-test labelSelectors: app: middleware-service stressors: cpu: workers: 2 load: 80 duration: "5m"有几个点值得注意。
historyLimit默认值挺大的,如果是周级任务建议改成 5 或更小,否则 etcd 里会积累大量已完成的历史实验记录,时间久了会影响集群性能。
concurrencyPolicy决定上一次实验还没结束,下一次又该触发时怎么办。一般用Allow就行,但如果你的实验可能运行超过间隔时间,建议用Forbid,防止多个实验叠加。
使用 Schedule 有一个认知转变需要强调:混沌工程正在从“项目制”向“产品化”转变,故障注入是一种持续验证的手段,而不是一次性的活动。团队里所有人也都理解了这一点之后,稳定性建设才算真正起步。
如果需要更复杂的编排,Chaos Mesh 还提供了 Workflow。可以把多个故障阶段串起来,比如第一阶段注入网络延迟 1 分钟,第二阶段注入 CPU 满载 2 分钟,第三阶段注入 pod-kill,它们按顺序执行。这就像写测试用例一样,把整套故障剧本固化下来。每次大版本上线前,我们会在预发环境跑一遍定义好的 Workflow,作为发布准入的前置条件之一。
7. 常见问题和避坑指南
用 Chaos Mesh 这几个月,遇到的坑不少,有些是配置问题,有些是概念理解偏差,我直接整理成速查表。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| NetworkChaos 注入后无效果 | chaos-daemon 无法访问容器运行时 socket | 检查 DaemonSet 日志,确认 runtime 类型和 socketPath 配置 |
| PodChaos 实验执行失败 | selector 选中的 Pod 数量为 0 | 用 kubectl get pods --show-labels 检查 label 是否匹配 |
| 实验状态一直 Pending | 没有创建对应的 RBAC 权限 | 检查 ServiceAccount 是否有目标命名空间的写权限 |
| StressChaos 导致节点负载过高 | 压力参数设置太大,超过节点资源余量 | 先小参数验证,同时使用资源配额限制 |
| 注入后恢复时间过长 | duration 设置太长,或者没有配置恢复策略 | 设置合理 duration,必要时手动删除 chaos 资源 |
| Dashboard 看不到实验记录 | 跨命名空间查看权限不足 | 检查 dashboard 的 RBAC 配置 |
| Schedule 任务不触发 | cron 时区设置不对 | Chaos Mesh 默认用 UTC,注意时区换算 |
另外还有三个实操中的心得体会:
第一,所有实验对象都要提前通过 label 精准选中,强烈不建议用 namespace 级别的大范围注入。我们刚开始图省事,选中了一个命名空间下所有 Pod,结果把一堆无关服务也牵扯进来了,日志告警一堆,场面一度失控。后来规定,所有实验必须精确到具体的 workload 或 label。
第二,注入网络故障之前,先确认目标服务有没有配置合理的探针。如果存活探针和就绪探针都没配置,那断网几分钟后 Pod 会被打成不健康,影响范围会扩大好几倍。这其实也暴露了基础设施短板,但在实验前先解决基础设施问题会更稳妥。
第三,实验结束后不要只看监控图表,一定要去看被注入服务的日志。很多问题在监控层面看不出来,但是日志里已经报错报了两分钟。把日志和监控对照起来看,才能拼出完整的故障现场。
关于和监控告警体系的联动,我们也做了个小改进,通过 Chaos Mesh 自带的事件机制,把所有实验的开始、结束、异常事件都转发到统一的告警平台,这样团队其他人不用打开 Dashboard 也知道正在发生什么,避免实验过程中误报警导致大家白忙活一场。
8. 从演练到质变:我们的经验总结
最后再聊几句个人感受。
真正把 Chaos Mesh 跑起来之后,团队的心态发生了挺微妙的变化。最开始大家把故障演练当成负担,觉得“本来没事,你非要点一把火”。但经历过几次真实故障被提前验证出来后,所有人开始主动关心实验设计,甚至在版本上线前会主动要求跑一轮演练,这比任何 PPT 安全培训都管用。
我觉得可以从一个小规模的场景开始,比如选一个非核心服务,做一次 pod-kill 实验,跑通整个流程。在流程没有那么顺畅的时候,先不要追求复杂的 Workflow,不要追求自动化,先把“人为定义一个实验、执行它、观察结果、形成结论”这条链路跑通,让团队建立信心。之后再逐步引入网络故障、资源压力、Schedule 定时任务和 Workflow 编排。混沌工程的实施不在于你用了多少高级功能,而在于团队是否真的具备从故障中学习的能力。总之,工具只是开始,持续修正和改进系统韧性才是目的。