当前位置: 首页 > news >正文

Chaos Engineering 实战:一次 Zone 故障演练暴露出 3 个隐藏的单点,我用这套预案 15 分钟恢复

Chaos Engineering 实战:一次 Zone 故障演练暴露出 3 个隐藏的单点,我用这套预案 15 分钟恢复

说实话,我们之前一直觉得自己挺高可用的。

多可用区部署、数据库主从、K8s 副本数 3+、告警链路到企业微信。纸面上一看,该有的都有。直到上个月我们第一次认真跑了一次 Zone 级故障演练,才发现“理论上能扛”和“真断了还能扛”之间,差了不止三条街。

演练场景很简单:把 us-east-1b 这个可用区里的所有业务 Pod 全部干掉,模拟一次 zone failure。结果?核心链路在 3 分钟内开始异常,客服群里陆续有人反馈“页面点不动”。更离谱的是,我们花了 15 分钟才完全恢复——而且不是因为业务复杂,是因为三个隐藏的单点没被发现。

这篇文章就把这次演练的过程、暴露的问题、以及我现在沉淀的预案和 checklist 完整记下来。不是纯理论,所有命令和配置都可以直接拿去跑。

背景:为什么要做 Zone 级演练

我们是一套典型的云原生架构:K8s 托管集群、RDS 多可用区、ElastiCache 主从、对象存储。业务流量走 Nginx Ingress → 业务服务 → 消息队列 → 结算系统。看似每个组件都有冗余,但我们之前从没验证过“一个可用区整体挂掉”这种场景。

导火索是隔壁团队的一次真实事故:他们某个可用区因为运营商链路问题整体失联,结果一个看似无关的“配置中心”实例刚好全在那个区,导致全链路服务发现异常。那次事故让我意识到,高可用不能只靠架构图,要靠演练图

于是我们定了一个目标:每季度做一次 Zone 级故障演练,目标不是“证明系统没问题”,而是“主动找出问题”。

演练设计:怎么模拟一个可用区挂掉

我们没有直接物理断网,而是用了 Chaos Mesh 的ZoneChaos配合节点标签来做模拟。核心思路是:把某个可用区标签下的 Pod 全部删除,并且让调度器在一段时间内禁止往这个区调度新 Pod。

先用标签把节点按可用区打好标:

kubectl label nodes--alltopology.kubernetes.io/zone=\--overwritekubectl label nodes$(kubectl get nodes-lzone=1b-oname)\topology.kubernetes.io/zone=us-east-1b--overwrite

然后写一个 Chaos Mesh 实验:

apiVersion:chaos-mesh.org/v1alpha1kind:PodChaosmetadata:name:zone-1b-failurenamespace:chaos-testingspec:action:pod-killmode:allduration:"10m"selector:namespaces:-"production"nodeSelectors:topology.kubernetes.io/zone:"us-east-1b"labelSelectors:app:"order-service"scheduler:cron:"@every 30s"

这个实验会每 30 秒把 us-east-1b 里 production 命名空间下、带app=order-service标签的 Pod 杀一遍。持续 10 分钟。足够暴露问题,也不会真的把集群搞崩。

提醒:演练前一定要在非生产环境先跑三遍。我们第一次是在 staging 跑的,结果把 staging 的 ZooKeeper 选主搞出了脑裂,花了一个小时才恢复。血的教训。

演练中暴露的 3 个隐藏单点

单点一:配置中心只在一个可用区有副本

我们用的是自建的 Nacos 做配置中心。部署文档上写的是三节点,但仔细一看,三个 Pod 全在 us-east-1b。原因很简单:当初部署的时候,集群只有 1b 有足够的资源,后来扩了 1a 和 1c,但 Nacos 的 StatefulSet 没加反亲和性约束,副本一直原地不动。

演练开始后,1b 被干掉,Nacos 三个节点全挂。业务服务启动新 Pod 时读不到配置,直接 CrashLoopBackOff。老 Pod 还能靠本地缓存撑一会儿,但任何需要重启或扩容的场景都会炸。

修复方案是给 StatefulSet 加上强反亲和:

affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-nacostopologyKey:topology.kubernetes.io/zone

然后滚动迁移,强制把副本打散到三个可用区。

单点二:第三方 webhook 的 endpoint 写死了单一可用区入口

我们的支付回调依赖一个第三方服务商。他们的回调地址是区域化的,但我们在文档里抄了一个 us-east-1b 的入口,写死在代码里。1b 一挂,webhook 发不过来,支付状态一直卡在“处理中”。

这个问题特别隐蔽,因为日常完全没问题。只有整个可用区失联时才会暴露。

修复方案是把这个 endpoint 改成多可用区域名 + 健康检查:

importrandomfromurllib.parseimporturlparse WEBHOOK_ENDPOINTS=["https://pay.us-east-1a.example.com/webhook","https://pay.us-east-1b.example.com/webhook","https://pay.us-east-1c.example.com/webhook",]defget_webhook_endpoint():# 实际生产环境建议用 DNS 权重 + 健康探测,而不是随机returnrandom.choice(WEBHOOK_ENDPOINTS)

更稳妥的做法是接对方的全局域名,让 DNS 和任播来处理可用区切换。但这件事必须去供应商侧确认,不能自己脑补。

单点三:有状态会话缓存没有跨区复制

用户登录态存在 ElastiCache Redis 里。Redis 是主从结构,主节点在 1b,从节点在 1a。我们以为主从自动切换就够了,但演练时切换花了 40 多秒,期间大量用户被登出,客服工单直接刷屏。

更坑的是,有些服务在 Redis 不可用时没有降级逻辑,直接 500。等于说 Redis 主从切换虽然发生了,但业务损伤已经造成。

修复分两步:

  1. 把 Redis 主从改成 Cluster Mode,让客户端能自动感知故障转移;
  2. 给所有读缓存的地方加降级:缓存读不到就走数据库,宁可慢不能挂。
defget_user_session(user_id):try:returnredis.get(f"session:{user_id}")exceptredis.ConnectionError:# 降级:直接读数据库,同时打标监控metrics.counter("cache_fallback_db").inc()returndb.query_session(user_id)

15 分钟恢复的时间线

演练当天,我们的 on-call 同学按下面这个时间线恢复了业务:

  • T+0:00:Chaos 实验注入,1b 业务 Pod 开始被批量删除。
  • T+1:30:监控大盘出现订单成功率下降,告警触发。
  • T+2:00:on-call 确认是演练导致,未触发回滚,继续观察。
  • T+3:30:客服开始反馈登录掉线、支付卡住。确认 Redis 主从切换进行中。
  • T+5:00:Redis 完成自动切换,但第三方 webhook 还在丢消息,支付状态不一致。
  • T+7:00:临时切换 webhook 到备用 endpoint,支付回调恢复。
  • T+10:00:Nacos 迁移完成,新 Pod 可以正常启动。
  • T+15:00:全链路指标回到基线,演练结束。

坦白讲,这 15 分钟里至少有 8 分钟是在定位“到底是哪个单点”。如果问题事先都知道,恢复能压缩到 5 分钟以内。所以混沌工程最大的价值不是训练恢复速度,而是把未知风险变成已知风险

我沉淀下来的演练 checklist

现在我们每次演练前都会过一遍这个 checklist,演练后补一条复盘记录。

演练前:

  • 确认演练范围:是 zone、node、还是单 Pod?
  • 确认停止条件:成功率降到多少立即终止?
  • 通知客服和运营,避免“误报警”变成真事故。
  • 在 staging 跑通至少一次完整实验。
  • 备份数据库和关键配置,确保可以回滚。

演练中:

  • 指定一个指挥员,其他人只记录不操作。
  • 每 30 秒记录一次核心指标:订单成功率、P99 延迟、错误率、登录态异常数。
  • 发现超时或失败立即暂停实验,先止血再复盘。

演练后:

  • 输出《单点清单》和《修复计划》。
  • 把修复项排进下一个 sprint,责任人到天。
  • 更新 runbook,把新发现的问题写成标准化恢复步骤。
  • 把有修复的项再跑一次演练,验证是否真的解决了。

写在最后

混沌工程不是“没事找事”。在真实事故里,那 15 分钟可能就是几十万甚至几百万的损失。演练把这 15 分钟“预支”到一个可控的周末,让我们能笑着把问题挖出来。

如果你也在做云原生架构,我建议:先别急着上更复杂的容灾方案,先跑一次 Zone 级故障演练。你可能会发现,你最担心的那个组件其实没问题,真正脆弱的,是那些你从来没想过会挂的东西。

现在我们的演练已经从“季度一次”改成“月度一次”。不是因为我喜欢折腾,而是因为每次都能找到新的坑。高可用这件事,本来就是没有尽头的。


参考资料:

  • Chaos Mesh 官方文档:https://chaos-mesh.org/
  • AWS Well-Architected 可靠性支柱:https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/welcome.html
  • Google SRE Book:Chaos Engineering 章节
http://www.cnnetsun.cn/news/3661463.html

相关文章:

  • 小红书内容保存难题终极解决方案:XHS-Downloader无水印下载完整指南
  • webpack-bin加载器配置指南:轻松处理CSS、TypeScript与Vue文件
  • C2000 Piccolo五大通信外设(SPI/SCI/LIN/I2C/eCAN)深度解析与实战配置
  • 2026年LLM系统工程师核心技能与实战指南
  • 高效解决PL-2303旧芯片Windows 10串口驱动兼容性难题
  • 脉冲排序质量 metrics 详解:从 SNR 到 isi_violations 全面解读
  • 短视频学习效率怎么提高免费工具额度够用吗2026实测多款分享真实经验
  • Docker镜像与容器核心概念及实践指南
  • MediaPlugin源码解析:跨平台媒体处理的核心实现原理
  • 嵌入式USB OTG开发实战:从协议原理到TI MCU实现详解
  • 3分钟快速上手ToastFish:Windows通知栏背单词终极指南
  • TPS65912x电源管理芯片时序配置与嵌入式系统电源设计实战
  • 树莓派GPIO引脚配置详解:pi-gpio物理引脚与BCM映射对照表
  • AI大模型架构解析:从Transformer到多模态融合
  • 从前端到后端:ots项目架构解析与核心组件功能说明
  • TMS320C6474引导模式与引脚功能详解:硬件设计核心指南
  • AI如何影响企业信誉评价及应对策略
  • StopWatch 是 Spring 框架提供的一个轻量级计时工具类
  • 【提示词故事创作黄金模板】:20年AI内容架构师亲授,3步生成影视级叙事框架
  • VC++实现Diffie-Hellman密钥交换:CryptoAPI实战与安全通信原型
  • Video DownloadHelper CoApp架构深度解析:重构浏览器视频下载新范式
  • 快手AI视频生成工具可灵的商业化与技术架构解析
  • FastFormers模型架构详解:从理论到实践的高效Transformer设计
  • Unity回合制战斗系统开发:从状态机到性能优化的5个核心问题
  • 国内汽车集团通过外部技术转移引入电池智能制造技术,其落地过程中的关键成功因素有哪些?
  • MapStruct Plus 的依赖分析
  • MapStruct Plus 版本对lombak1.18.16,1.18.20依赖冲突
  • 提示词创意生成模板实战手册(附NASA级思维框架):从混沌输入到爆款输出的完整闭环
  • 深入解析ePWM动作限定子模块:PWM波形生成的核心机制与配置实践
  • Windows命名管道(IPC)原理与高效通信实践