从 Cloudflare 两次宕机事故,到线上故障处理经验分享

wallhaven-rq68qq

真实案例:Cloudflare 近期两次宕机

第一次服务中断:2025年11月18日

2025 年 11 月 18 日 11:20 UTC,Cloudflare 网络开始出现严重故障。这是Cloudflare 自 2019 年以来最严重的一次服务中断,在过去 6 年多时间里从未发生过导致大部分核心流量中断的故障。

下图显示了 Cloudflare 网络处理的 5xx 错误 HTTP 状态代码的数量:

Volume of HTTP 5xx requests served by the Cloudflare network

时间线

时间 (UTC) 状态 描述
11:20 故障开始 错误的特征文件开始在网络中传播
~13:05 开始排查 回滚部分变更,但问题仍在持续
14:30 核心恢复 停止生成错误文件,手动插入已知好文件
17:06 完全恢复 所有系统恢复正常

根因分析

Cloudflare 的 Bot Management 系统使用机器学习模型为每个请求生成机器人评分。该模型依赖一个"特征配置文件"(feature file),包含用于判断流量是否为自动化的特征集合。该文件每几分钟刷新一次并发布到整个网络。

故障由 ClickHouse 数据库权限变更触发:

  1. 权限变更导致查询输出大量重复的"特征"行
  2. 特征文件大小增加了一倍,超过了软件设置的大小限制
  3. 核心代理软件(FL 和 FL2)读取该文件时,超出限制导致崩溃
  4. 对于旧版 FL 代理,虽然未返回 5xx 错误,但机器人评分全部变为 0,导致误封大量正常流量

暴露问题

  • 异常处理不合理。 软件设置了特征文件大小限制以保护系统,但当文件超出限制时直接崩溃,而非采用降级策略(如跳过 Bot Management 模块继续处理流量)。代码里设置最大值保护系统无可厚非,但要区分核心流程和非核心流程,避免一个非核心逻辑的错误影响整个系统。
  • 止损服务循环依赖。 Cloudflare 的状态页面托管在基础设施之外,本应独立。但由于登录页面使用 Turnstile 验证,而 Turnstile 依赖 Cloudflare 网络,导致大部分用户无法登录状态页面。止损系统不应依赖于它所管理的服务。
  • 配置发布没有灰度。 Cloudflare 的配置发布使用全局配置系统 Quicksilver,几秒钟内传播到所有服务器,没有像代码变更一样使用渐进式部署系统。这意味着任何配置错误都会瞬间影响全球网络。

Cloudflare 的改进措施

  • 审核、改进并测试所有处理网络流量的系统的故障模式,确保在意外错误状态下也表现出明确定义的行为。
  • 更改内部"紧急访问"程序并消除任何循环依赖关系,确保事件期间能快速访问所有系统。
  • 实施"Fail-Open"错误处理策略:当配置文件损坏或超出范围时,记录错误并默认到已知良好状态或跳过评分继续处理流量,而非硬性失败。
  • 配置变更也采用与软件发布相同的渐进式部署系统。

第二次服务中断:2025年12月05日

2025 年 12 月 5 日 08:47 UTC,Cloudflare 部分网络开始出现严重故障。总影响时间约 25 分钟,受影响客户约占 Cloudflare 处理的 HTTP 总流量的 28%。

下图显示了事件发生期间 Cloudflare 网络返回的 HTTP 500 错误(底部的红线),以及与未受影响的 Cloudflare 总流量(顶部的绿线)之间的对比:

500 error codes served by Cloudflare's network during the incident

时间线

时间 (UTC) 状态 描述
08:47 故障开始 配置变更部署并传播到网络
08:48 全面影响 变更完全传播
08:50 事件声明 自动化告警触发
09:11 开始回滚 配置变更被撤销
09:12 事件结束 回滚完全传播,流量恢复

根因分析

为了保护使用 React 的客户免受关键漏洞 CVE-2025-55182 的影响,Cloudflare 执行了一项安全缓解措施:将 WAF 缓冲区大小从 128KB 增加到 1MB(Next.js 的默认限制)。

第一项更改:使用渐进式部署系统逐步增加缓冲区大小。

第二项更改:在试运行过程中发现内部 WAF 测试工具不支持增加的缓冲区大小。由于该工具不影响客户流量,团队决定将其关闭。

问题出在第二项更改的实施方式:

  • 使用 Quicksilver 全局配置系统实施关闭操作
  • 该系统不支持渐进式部署,而是几秒钟内将更改传播到所有服务器
  • 该系统在 11 月 18 日事故后已在审核中

在 FL1 代理版本中,关闭 WAF 规则测试工具触发了 Lua 代码中的一个隐藏 bug:

-- FL1 代理中的错误代码
if rule_result.action == "execute" then
    -- 当规则被跳过后,rule_result.execute 不存在,导致 nil 值访问
    rule_result.execute.results = ruleset_results[tonumber(rule_result.execute.results_index)]
end

该 bug 已存在多年,在 FL2(用 Rust 重写)中不存在。当 killswitch 系统跳过"execute" 动作后,代码仍尝试访问不存在的 rule_result.execute 对象,导致 Lua 异常:

[lua] Failed to run module rulesets callback late_routing: /usr/local/nginx-fl/lua/modules/init.lua:314: attempt to index field 'execute' (a nil value)

暴露问题

  • 配置变更未灰度。 Quicksilver 系统没有渐进式部署机制,配置错误会瞬间影响全球网络。没有灰度就没有局部观察的机会,影响面最大化。
  • 重大事故后没有冷静期。 上次重大事故(11月18日)发生仅 17 天后,又出现重大事故,严重损害客户信心。Cloudflare 承诺在完成改进措施前锁定所有网络变更。

Cloudflare 的改进措施

  • 配置变更也使用渐进式部署:对传播到网络的任何配置更改进行受控部署,就像软件二进制版本发布一样。
  • 增强版本控制与回滚能力:配置变更应包含健康验证和快速回滚能力。
  • 锁定网络变更:在重新开始变更之前,确保增强渐进式发布和回滚系统。

线上故障处理经验分享

事前:构建有韧性的系统

系统故障是必然的——不是"会不会"的问题,而是"什么时候"的问题。与其祈祷故障永不发生,不如把系统设计成能从故障中恢复。

区分核心与非核心流程

很多故障的根源是:非核心逻辑的错误搞垮了核心链路。

┌─────────────────────────────────────────────────┐
│  请求入口                                        │
└───────────────────┬─────────────────────────────┘
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
┌───────────────┐     ┌───────────────────┐
│   核心流程     │     │   非核心流程       │
│  认证 → 业务   │     │  Bot评分/风控      │
│  处理流量      │     │  特征提取          │
└───────────────┘     └───────────────────┘
        │                       │
        │   非核心异常          │
        │──────────────────────┘
        │      ❌ 崩溃         │
        ▼                       ▼
┌─────────────────────────────────────┐
│         整个请求失败                  │
└─────────────────────────────────────┘

Cloudflare 第一次事故就是典型案例:Bot Management 的特征文件超限,导致核心代理崩溃。非核心流程的错误不应该影响核心链路。

场景 做法
强依赖不可用 应用启动时检查,异常直接退出,提前暴露问题
弱依赖不可用 降级处理,如权限系统异常时使用最近审批人
任何异常 判断是否需要告警,让运维感知到问题

消除循环依赖

止损系统最怕什么?怕它要救的系统恰恰是它自己的依赖。

Cloudflare 的控制台托管在基础设施外,本应独立,却因登录需要 Turnstile 验证——而 Turnstile 依赖 Cloudflare 网络——导致故障时用户无法使用控制台。

┌──────────────┐         ┌──────────────┐
│  状态页面     │ ──依赖──▶│  Turnstile   │
└──────────────┘         └──────────────┘
      │                        │
      │        ┌───────────────┘
      │        │
      ▼        ▼
┌─────────────────────────────────────┐
│       Cloudflare 核心网络            │
│    (故障时全部不可用)               │
└─────────────────────────────────────┘

原则:止损系统应该是"物理隔离"的。 部署系统、K8S 管控组件、监控告警平台——这些基础管控系统要用独立部署,避免和业务系统形成循环依赖。

变更管理的三原则

Cloudflare 第二次事故的根因之一是 Quicksilver 配置系统不支持灰度,配置变更瞬间传遍全球。

要求 说明
可灰度 任何变更(代码+配置)都要能逐步放量,先灰度 1% 观察
可观测 变更伴随监控指标,异常第一时间能看到
可回滚 准备好回滚预案

实践建议: 重大功能上线时,用功能开关(Feature Flag)控制。重大功能往往需要多次迭代完善,异常时只需关闭开关即可恢复,无需回滚代码,也就不会影响其他需求的上线进度。

预案与演练

预案不是写完就完事,而是需要定期验证的。

预案覆盖的场景:

  • 单节点故障(一主多从架构下主节点挂了、某台机器挂了)
  • 机房级别故障(整个机房掉电、机房网络分区)
  • 依赖服务故障(下游超时/熔断/降级)

验证方式:

  1. 定期演练:注入故障(如断网、杀掉进程),看系统表现是否符合预期
  2. SOP 腐化检查:每次故障演练后更新 SOP,保证 SOP 的正确性

工具脚本沉淀

故障发生时,时间是最宝贵的。没人有精力在现场学习新命令(🐶老板看着呢)。

故障发生时的理想状态           实际状态
─────────────────────────    ─────────────────────────
grep /var/log/xxx             grep /var/??? 日志路径对吗?
ps -ef | grep java            ps -e?? f 参数记混了
kubectl rollout undo          rollback 还是 undo?

为什么需要沉淀:

  1. 经过验证 vs 临时搜索:网上搜的命令可能不适用于你的环境,团队内部沉淀的命令是验证过的
  2. 减少认知负荷:故障压力下人容易慌,熟悉的命令能减少犯错概率

沉淀什么:

类别 示例
日志查看 tail -f、grep 过滤模式、journalctl 技巧
进程诊断 CPU/内存占用排序、线程堆栈导出
网络诊断 curl 健康检查、telnet 端口、tcpdump 抓包
容器/K8S 日志查看、Pod 状态、重启策略调整
数据库 连接检查、慢查询、死锁排查

主动学习

故障复盘是成本最高的学习方式。更高效的方式是学习别人的事故。

Cloudflare 的事故报告是行业标杆——他们不仅公开了时间线、根因、改进措施,还把内部系统的设计缺陷也一并披露。Google SRE Book 里的"Postmortem Culture"章节同样值得细读。

被动成长 ──▶ 事故中学习 ──▶ 代价高、成长慢
主动学习 ──▶ 事故前阅读 ──▶ 成本低、提前规避

事中:快速止损是第一优先

核心目标是止损,而不是先排查清楚,定位到根因,再去止损。

确认故障和影响范围

感知到故障后,先确认系统状态,排查误报的情况。如果不清楚故障对应的影响,就对核心功能进行冒烟测试,得出影响范围(前提是不要花费太多时间)。随后对故障进行通报,描述清楚现状,影响范围和当前动作。对于一些不确认的推测,也可以补充下,但要明确说明正在确认。

优先梳理近期变更

大部分故障都是变更引起的,只有我们找到这个变量,就能快速止损。

在梳理近期变更时,可以记录下时间线,方便理清问题的同时,后续复盘也能用到。

先止损,再定位

查找是否有相关SOP,如有,请严格按照SOP执行。避免当时不清醒,紧张,考虑不全面。

其他常见的止损手段有:

  • 切流到正常的机房
  • 回滚近期变更
  • 摘流异常节点
  • 关闭功能开关
  • 限流、熔断、降级

如果是晚上变更出现异常,应立即回滚,并排查清楚根因,并停止变更,等到第二天白天再继续变更。避免晚上不清醒,对异常处理不当,评估不准确,继续变更造成事故。

止损也需要灰度,止损动作本质上也属于线上变更,应该遵循可灰度原则,避免引入新的故障。一定要禁止梭哈。

该升级就升级

如果故障影响较大,或者短时间内无法解决,就应该告知团队和老板你的难处,把问题抛出来,寻求更多支持。别因为压力的原因,只想着自己如何尽快解决这个问题。

故障通报也要定时同步,让大家知晓止损进展,哪怕没有进展,也要说明。

保持良好的心态

紧急情况下,紧张、焦虑的情绪会随之产生。我们要知道情绪的存在,不要被它带着走。

当前最重要的事情是解决这个问题,尽力做好就行,不要管其他的。例如这个事故如何定责,要背责任怎么办。

我见过在同一个事故中,截然不同的处理方式。有个变更导致了故障,随后在回滚时操作失误,引发了其他问题。其中一个同学比较紧张焦虑,在会上不时的说出一些情绪化的语言,记不清原话了,大概就是在埋怨、担忧。而另一个同学就稳重很多,会上发言都是围绕着当前问题的处理,没有一句废话。

恢复后继续观察

故障恢复后,需要对系统核心功能进行冒烟测试,确保一切正常。

检查监控大盘、服务日志,确保服务运行正常。

事后:复盘与改进

故障恢复后,不代表事情结束,更有价值的是故障后的复盘。发现系统或者流程上存在的问题,避免同类问题再次发生。

尽快复盘

复盘材料编写应尽量在故障恢复后的 24 小时内进行。时间拖得越久,参与者对现场的记忆越模糊,很多关键细节也会丢失。

即使事中已经尽力保存日志、监控和操作记录,也很难百分之百还原现场,因此复盘越早越好。

找到根因,也要找到放大因素

复盘不能停留在“某段代码有 Bug”或“某个人配置错了”。

人一定会犯错,更重要的问题是:为什么一次普通错误能够进入生产,又为什么会扩大成严重故障?

除了直接根因,还要继续分析:

  • 为什么在变更方案评审时没发现问题
  • 为什么变更时 doubleCheck 没有发现问题
  • 为什么测试和灰度没有发现问题
  • 为什么监控没有更早告警
  • 为什么异常没有被隔离或降级
  • 为什么回滚和止损花了这么长时间
  • 为什么现有 SOP 没有发挥作用

Cloudflare 的两次事故中,代码和配置问题只是直接原因。缺少灰度、异常处理不合理,以及止损系统存在循环依赖,才是故障影响被放大的关键因素。

形成明确的改进项

每条改进措施都应有负责人和完成时间,避免复盘结束后只留下一份文档。

改进项通常包括:

  • 修复直接导致故障的缺陷
  • 补充监控和告警
  • 增加灰度、回滚和降级能力
  • 更新故障处理 SOP
  • 增加自动化测试或故障演练
  • 消除单点和循环依赖

改进项需要持续跟踪直到关闭,应该录入到相关平台进行追踪。

重大故障后设置冷静期

重大事故发生后,可以设置一段变更冷静期。例如未来一周内暂停非紧急线上变更,只允许故障修复和必要操作。

如果连续发生事故,会放大事故影响,进一步损害用户信任。

让系统从故障中成长

故障本身不会自动带来成长,只有完成复盘、落实改进并验证效果,故障成本才没有白白付出。

一次好的复盘,最终应该让监控更灵敏、止损更迅速、系统更健壮,也让团队下一次面对类似问题时更加从容。

总结

线上故障处理是一门系统工程,需要在事前预防、事中止损、事后复盘三个阶段都做到位:

  • 事前:构建有韧性的系统,做好预案和演练
  • 事中:止损优先,快速恢复,及时协同
  • 事后:及时复盘,持续改进,让系统更健壮

每一次故障都是系统和团队成长的机会。


参考资料

  1. Cloudflare outage on November 18, 2025
  2. Cloudflare outage on December 5, 2025
  3. Code Orange: Fail Small — our resilience plan following recent incidents
  4. Google SRE Book - Postmortem Culture
  5. Netflix Chaos Engineering Principles
作者:Yuyy
博客:https://yuyy.info
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇