Cloudflare 11-18 宕机事故深度复盘:当数据库变更击穿全球网关

版权所有 © [2025] Vannik 未经作者授权,禁止转载
Doc Map
  1. 影响分析 (Impact analysis)
  2. 事故时间线 (Timeline)
  3. 根因深度分析 (Technical Root Cause)
  4. 响应与恢复过程 (Response and recovery process)
  5. 经验教训总结 (Lessons Learned)
  6. 结语 (End)

2025 年 11 月 18 日,Cloudflare 遭遇了一次严重的全球性服务中断。事故导致全球大量依赖 Cloudflare 代理的服务(包括 ChatGPT, Spotify 等)出现 HTTP 5xx 错误,核心服务如 Workers KV、Access 和 Dashboard 均受到波及。

一句话定性:这是一起典型的配置变更引发的蝴蝶效应。一个旨在加强数据库安全的权限变更,意外导致配置文件体积翻倍,进而触发了边缘节点代理服务(Proxy)中的硬编码内存限制,导致核心进程陷入无限崩溃循环(Crash Loop)。官方已确认并非外部网络攻击。

影响分析 (Impact analysis)

服务 / 产品影响描述
核心 CDN & 安全服务HTTP 5xx 错误,大量请求失败。 (The Cloudflare Blog)
Bot Management配置文件崩溃,导致 bot 检测能力受损。
Turnstile (挑战 /验证)Turnstile 无法加载。 (The Cloudflare Blog)
Workers KVKV 请求失败 (“front end” 门户的服务请求返回 5xx),因为其依赖核心代理。 (The Cloudflare Blog)
Dashboard虽然主面板部分可用,但用户登录失败,因为 Turnstile 不可用。 (The Cloudflare Blog)
Access (身份验证)大量认证失败,尝试登录的请求返回错误页面。 (The Cloudflare Blog)
Email 安全邮件服务受限 — 部分 IP 声誉源暂时不可用,一些垃圾邮件检测准确度下降,但没有重大信息丢失或破坏。 (The Cloudflare Blog)

此外,恢复期间还观察到延迟 (latency) 上升,部分因为调试和可观测性系统 (observability) 在捕获错误时占用了大量 CPU。

事故时间线 (Timeline)

所有时间均为 UTC

  • 11:05 - 工程师开始部署一项针对 ClickHouse 数据库集群的权限控制变更 (ACL Change)。
  • 11:20 - 变更生效。此时,用于生成 Bot 管理配置文件的后端查询开始返回重复的元数据(同时返回了 default 和 r0 两个数据库的元数据)。
  • 11:25 - Bot Management 系统按计划生成了新的“Feature 配置文件”。由于数据重复,文件体积意外翻倍。
  • 11:30 - [故障爆发] 新的配置文件被推送到全球边缘节点。核心代理进程(Rust 编写)在加载该文件时,因超出硬编码的条目限制(~200条)触发 Panic。看门狗机制尝试重启进程,导致全球节点陷入崩溃-重启循环。
  • 11:35 - 监控系统检测到流量异常,SRE 团队介入。初步误判为大规模 DDoS 攻击(因大量 5xx 错误和流量特征)。
  • 13:05 - 确认根因并非攻击,而是内部配置问题。
  • 13:37 - 定位到 Bot Management 配置文件异常。
  • 14:24 - 停止生成新文件,并强制回滚至“最后已知正常(Last Known Good)”版本。
  • 14:30 - [核心恢复] 代理服务成功加载旧版配置,全球核心流量开始恢复。
  • 17:06 - 所有积压队列处理完毕,附属服务(如 Email Security、日志分析)完全恢复。

根因深度分析 (Technical Root Cause)

这次事故是三个独立因素完美“对齐”造成的系统性崩溃:

第一张骨牌:数据库查询的“幽灵数据”

变更本身是善意的(为了收紧权限)。然而,后端 SQL 查询并未显式指定 WHERE database = 'default'。在权限变更前,系统默认只看得到 default;变更后,系统意外“看”到了 r0 副本。 结果:查询结果集包含了两份完全一样的数据。

放大器:缺乏校验的配置生成

Bot Management 系统负责将上述 SQL 结果序列化为二进制的 Feature 文件。 失误:生成脚本缺乏输入验证(Input Validation)。它没有检查生成的条目数量是否在“安全阈值”内,也没有对比新旧文件的大小差异,就直接将其标记为“可用”并推送到生产环境。

致命一击:脆弱的代码假设 (Rust Panic)

Cloudflare 的边缘代理使用 Rust 编写。代码中存在一个关于 Feature 条目数的硬编码上限 (Hard-coded Limit)。 当程序读取这个“翻倍”的文件时:

  • 读取逻辑返回了一个 Error。
  • 错误处理逻辑(或缺乏处理)直接导致了主线程 Panic(崩溃)。由于 Cloudflare 采用无共享架构,所有服务器运行相同的代码和配置,因此它们几乎在同一秒全部崩溃。

响应与恢复过程 (Response and recovery process)

  • 检测与初期响应
    • Cloudflare 在 11:35 UTC 创建了 incident call (事件响应会议) 来集中处理问题。
    • 起初,他们误判症状可能是大规模 DDoS (超大流量攻击),因为错误非常广泛且看起来像服务被压垮。
  • 调查与根因确认
    • 工程团队迅速定位到 Bot Management 模块生成配置文件的问题。
    • 停止 propagating (传播) 新的 feature 文件。
    • 回滚到稳定版本,并重启关键代理。
  • 恢复
    • 截至约 14:30,核心流量大致恢复。
    • 后续几个小时内,各种服务 (代理、KV、Access 等) 完全恢复。
    • 监控团队持续观察,确保没有残留故障。

经验教训总结 (Lessons Learned)

对于 SRE 和架构师而言,这次事故提供了极具价值的参考:

  • 所有的输入都是邪恶的 (All Input is Evil)
    • 永远不要信任配置文件,即使它们是由内部系统生成的。
    • 改进:在配置加载器中必须包含严格的边界检查。如果配置超限,应记录错误并回退到旧配置或进入安全模式,而不是让整个进程崩溃(Graceful Degradation)。
  • 金丝雀发布的颗粒度
    • 虽然 Cloudflare 有分批发布机制,但这次故障传播速度快于监控系统的反馈速度。
    • 改进:配置文件的变更应视为代码变更,必须经过 Staging 环境的自动化测试(包括文件大小、格式校验)。
  • 避免“硬编码”限制
    • 在分布式系统中,硬编码的缓冲区大小或条目限制是定时炸弹。
    • 改进:随着业务增长,这些限制必须动态化,或者至少要有显式的监控报警,在接近阈值时提醒开发人员。
  • 数据库变更的可见性
    • 数据库权限变更往往被视为“低风险”运维操作,但实际上它改变了应用层的数据视图。
    • 改进:任何改变数据“形状”或“可见性”的变更,都应进行全链路的回归测试。

结语 (End)

Cloudflare 的这次宕机再次提醒我们,系统的坚固程度取决于最脆弱的那个假设。即便拥有世界级的防御网络,一行未被捕获的 SQL 重复数据加上一个简单的 .unwrap(),依然足以让半个互联网瘫痪。

作为技术人员,我们需要在追求高性能(Rust/C++)的同时,始终保持对“防御性编程”的敬畏。


附录:

Cloudflare restores services after outage impacts thousands of internet users
Cloudflare resolves outage that impacted thousands, ChatGPT, X and more
Cloudflare outage on November 18, 2025
Cisco ThousandEyes 分析报告