一行权限代码,如何让全球互联网“短暂熄火”?

Cloudflare 11·18 史上最严重故障全解析(深度技术长文 / 5000 字)

本文包含对 Cloudflare 技术架构、风险管理与工程流程的批判性解读,这些判断均基于可验证的文件内容,不代表绝对结论,仅作为技术视角下的专业分析。


目录

  1. 引言:一次“不是攻击”的互联网崩溃
  2. 全局事件时间线:从 11:05 到 17:06
  3. 故障缘起:一项权限变更如何引发系统级连锁反应
  4. Cloudflare 架构解剖:为什么一个文件能拖垮全球?
  5. 波动现象:为什么故障“周而复始”像遭遇 DDoS?
  6. 多系统雪崩:Workers KV、Access、Turnstile、Dashboard 全部倒下
  7. 技术深度解析:特征文件是如何被“撕裂”的?
  8. 更深层的问题:系统假设破碎与架构级隐患
  9. 行业对比:为什么 AWS/GCP 不会被一个特征文件拖死?
  10. 前瞻推演:Cloudflare 下一步会强化哪些机制?
  11. 这次事故对行业的真正意义
  12. 总结:最应该被记住的 80/20 关键洞察

01|引言:一次“不是攻击”的全球性互联网崩溃

2025 年 11 月 18 日,对于全球互联网而言是一段尴尬的时刻:

  • 大量网站返回 HTTP 5xx 错误
  • Cloudflare 加速、CDN、安全、访问控制等核心功能全面异常
  • Cloudflare Dashboard 无法登录
  • Turnstile 验证组件失效
  • Workers KV 出现大面积故障
  • 一些企业服务直接瘫痪数小时

当全球用户怀疑这是一次大型 DDoS 或供应链攻击时,Cloudflare 的官方解释却让所有工程师愣住了:

这不是攻击,而是一次权限变更引发的配置文件生成异常,导致核心代理集体崩溃。

一句话总结:

一次数据库权限优化 → 特征文件体积暴涨 → 系统崩溃 → 全球流量中断。

这次事件不仅是技术事故,更是一个绝佳的案例:
现代云基础设施越来越强大,同时也更脆弱——尤其是当它们高度集中自动化时。


02|从 11:05 到 17:06:这场事故如何一步步扩大?

下面的时间轴以 Cloudflare 官方公布为准,展示了整个事故链路:

timeline
    title Cloudflare 11·18 大故障时间线
    11:05 : ClickHouse 权限变更发布  
    11:20 : 异常特征文件开始生成  
    11:28 : 客户侧首次出现大规模 HTTP 5xx  
    11:32-13:05 : Workers KV 与 Access 大量失败  
    13:05 : KV 与 Access 启用应急绕过  
    13:37 : 决定回滚 Bot 管理特征文件  
    14:24 : 停止创建错误特征文件  
    14:30 : 核心网络恢复正常  
    17:06 : 全平台完全恢复

从这个过程可以清晰看到:

  • 11:05 的改动是根源
  • 11:20 出现异常,但症状并不明显
  • 11:28 全球出现 HTTP 500 级错误
  • 13:05 第一轮真正有效的应急操作
  • 14:30 故障实质性解决
  • 17:06 完整收尾

整个事故持续约 6 小时,其影响范围之广程度,是 Cloudflare 自 2019 年以来最严重的一次。


03|故障源头:ClickHouse 权限变更 × 无保护的内部生成文件

这次事故的起点,是 ClickHouse 的一个权限优化:

让内部查询可以看到 r0(底层表)的元数据,而不仅仅是 default 数据库下的表。

表面上是一次无害的小改动,甚至还提高了安全性和可审计性。
但问题在于:

Cloudflare 有一段代码默认假设查询结果只有 default 库中的列。

当权限扩大后:

  • system.columns 查询结果突然包含了来自 default 和 r0 的重复列
  • 这些重复列被当成“新特征”写入了 Bot Management 的 ML 特征文件
  • 特征数量从 60+ 暴涨到 200+
  • 想当然触发了 ML 模块的特征数量上限
  • 代理软件(FL/FL2)加载文件时直接 panic

也就是说:

数据库权限变更 → 查询结果翻倍 → 文件体积翻倍 → 代理服务内存限制触发 → 全线崩溃。

一条链条一个环节事故,全球云服务就跟着倒下。


04|架构层面:为什么 Cloudflare 会被一个文件拖垮?

Cloudflare 的代理体系是高度统一的,大致如下:

flowchart TD
    A[用户请求:浏览器、APP、API] --> B[TLS / HTTP 终止层]
    B --> C[核心代理 Front Line (FL/FL2)]
    C --> D[安全模块:WAF、Bot、DDoS、防火墙]
    D --> E[缓存系统 / 源站请求 / Workers / R2]

其中,Bot 管理模块的特征文件位于:

  • 核心代理的安全产品链条内部
  • 每几分钟更新一次
  • 同步推送到全球所有节点

它不是可选项,而是 Cloudflare 核心代理路径上的必经模块。

因此:

当特征文件导致模块崩溃时,代理直接中断,不存在兜底机制。

这是一种典型的“耦合型架构风险”:

  • 没有 graceful degrade(优雅降级)
  • 没有模块隔离
  • 没有 feature cap 防线
  • 没有灰度部署(直接全网推送)

在这种架构下:

一旦共享模块出现错误,影响就是“全球级扩散”,而不是局部故障。


05|为什么故障会“周期性波动”?看起来像遭受 DDoS?

这次的一个“迷惑性特征”是:

  • 故障并非一次性爆发
  • 而是每 5 分钟一次,忽好忽坏

原因如下:

  • 特征文件是由多个 ClickHouse 节点独立生成的
  • 权限变更是逐步 rollout 的
  • 所以一部分节点生成正确文件,一部分节点生成错误文件
  • 文件通过内部管道在全球同步
  • 正常/错误文件交替覆盖,系统就出现“周期性死亡”

这让故障排查团队一度误以为:

是攻击者在进行周期性、大规模、复杂的 DDoS 掩护。

同时,Cloudflare 自己的状态页也同时宕机,让大家更加确信“这是攻击”。

但实际原因只是一个巧合。


06|多系统雪崩:KV、Access、Turnstile、控制台接连倒下

从表象看,这次事故最先出问题的不是 Bot 模块,而是 Workers KV 和 Access。

原因很简单:

  • 二者强依赖核心代理
  • 核心代理崩溃 → KV 前端网关失败
  • KV 不可用 → Access 无法验证会话
  • Access 鉴权失败 → Turnstile 控制台登陆受阻
  • turnstile 无法加载 → Dashboard 用户无法登录

这是一个典型的链式依赖效应:

flowchart TD
    A[Bot 特征文件异常] --> B[核心代理崩溃]
    B --> C[KV 网关失败]
    C --> D[Access 认证失败]
    D --> E[Turnstile 加载失败]
    E --> F[Cloudflare 控制台无法登录]

Cloudflare 表示:

Access 所有身份验证失败请求均正确记录,无未授权访问发生。

但对于用户端来说:

  • 无法登录 Dashboard
  • 无法部署规则
  • 无法修改配置
  • 无法查看统计
  • 无法排查自身业务错误

这类故障的破坏力远超一个“页面 500”。


07|深入核心:特征文件究竟被“膨胀”成了什么样?

特征文件的构建方式很简单:

  • 遍历机器学习模型需要的所有字段
  • system.columns 查询对应列
  • 根据列属性生成特征配置

在正常情况下,查询结果大概长这样(示例):

name type
ip String
user_agent String
uri String

但在权限变更后:

  • default 表的字段出现一次
  • r0 底层表出现一次
  • 一些字段甚至出现超过两次(Cloudflare 数据库结构较复杂)

于是,ML 特征生成逻辑误以为:

“你给了我这么多新字段,那我全都加入特征文件!”

结果爆炸:

  • 60+ 特征 → 200+
  • 远超代理为特征预分配的内存(limit = 200)
  • Rust 代码触发 .unwrap() panic
  • 整个代理线程全部崩溃

Cloudflare 原始代码类似如下(伪示例):

if features.len() > MAX_FEATURES {
    panic!("too many features");
}

这里缺乏:

  • 防御式编程
  • 自动降级策略
  • 意外输入保护
  • 文件体积检测

换句话说:

这是一个典型的“内部供应链文件”缺乏安全校验导致的系统性风险。


08|根本问题:这是一个“假设崩溃”事故,而不是 Bug

Cloudflare 的所有工程师都相信:

查询元数据时,只会返回 default 库的内容。

但权限变更让这个假设失效。
系统没有因为条件改变而进化,最终导致整个链条被击穿。

这不是 Bug,而是:

系统假设长期未更新 → 权限调整 → 假设崩溃 → 全局故障

这种事故极具代表性:

  • 权限改变:看似安全
  • 查询结果变更:没有监控
  • 特征文件变大:无人感知
  • 代理崩溃:缺乏保护
  • 系统恢复:依赖手动回滚

本质上,这是一个 技术债 × 架构耦合 × 自动化扩散 共同作用的结果。


09|行业对比:为什么 AWS/GCP 不会被一个文件拖死?

这不是 Cloudflare 技术不好,而是架构逻辑不同。

平台 架构特性 结果
Cloudflare 单代理路径 + 全网同步配置 一个特征文件全网崩溃
AWS/GCP 多区域隔离 + 服务解耦 故障通常局部,不会全球传播

AWS/GCP 的大规模系统通常具有:

  • 区域级隔离(Region Boundary)
  • AZ 级隔离
  • Service Mesh 隔离
  • 配置灰度
  • Feature Gate
  • Canary 执行
  • 自愈退化逻辑

而 Cloudflare 的全球代理网络因为需要极致性能和一致性,采用的是 统一执行框架,因此:

优点:快到无法比拟
缺点:错误传播速度也“光速”

这次特征文件事故就是典型例子。


10|前瞻推演:Cloudflare 下一步会做什么?(推测性分析)

以下内容属于逻辑推演(已标注)。

推测 1:特征文件沙盒化

  • 先验证文件结构
  • 再验证字段数量
  • 再验证模型兼容性
  • 最后才允许进入部署管道

预计未来会采用 Canary + Shadow Test 机制。


推测 2:Bot 特征模块从代理中解耦

当前的设计过于危险:

  • Bot 模块几乎是“核心链路单点”
  • 一崩即全崩
  • 没有 fallback 机制

未来可能变成:

  • Bot scoring 放到独立进程
  • 代理失败时自动降级
  • 对模型特征数量采用硬限制 + 自动修正
  • 不再依赖 .unwrap() 这种高风险调用

推测 3:内部生成文件 =“潜在恶意文件”

Cloudflare 可能会把所有内部生成的文件也当做:

  • 不可信输入
  • 需要强校验
  • 需要版本验证
  • 强制 schema 约束
  • 体积限制强制生效

这代表 Cloudflare 内部将迈向另一种“零信任”:

零信任配置文件。


11|这次事故对行业真正意味着什么

这起事故不仅属于 Cloudflare,也属于整个互联网行业。

它暴露出一个普遍趋势:

现代互联网的崩溃风险更多来自内部错误,而不是外部攻击。

表现为:

  1. 自动化系统扩大错误的速度 > 工程师修复错误的速度
  2. 系统耦合度过高会让任何错误变成“全局放大器”
  3. 内部生成的数据文件是新的“供应链风险源”
  4. 权限调整、性能优化、微小改动都能造成全球级问题

这对所有工程团队都是强调:

  • 要对变化保持敬畏
  • 要对“假设”保持怀疑
  • 要对自动化保持谨慎
  • 要在系统中构建更多“免疫力”

12|总结:5000 字的内容,用 80/20 方式归纳为 10 句话

  1. Cloudflare 11·18 故障不是攻击,而是一次权限变更导致的内部错误。
  2. ClickHouse 查询结果变大 → 特征文件膨胀 → 代理 panic。
  3. Bot 模块是 Cloudflare 核心代理链路上的关键单点。
  4. 特征文件无保护、无灰度、无隔离,是事故放大的核心原因。
  5. 故障呈现周期性,是因为 ClickHouse 节点滚动更新。
  6. Workers KV、Access、Turnstile、Dashboard 均因依赖链路而被拖死。
  7. 故障根因不是 Bug,而是假设崩溃。
  8. Cloudflare 的统一架构 = 性能极强 + 风险集中。
  9. AWS/GCP 因多区域隔离,不会出现类似全局崩溃。
  10. 现代互联网真正的风险,是内部自动化链路的脆弱点。