文章作者:姜南(Slyar) 文章来源:Slyar Home (www.slyar.com) 转载请注明,谢谢合作。

Cloudflare 事故报告: https://blog.cloudflare.com/18-november-2025-outage/
半年内继GCP和AWS之后,Cloudflare在2025年11月18日也经历了一次严重的全球性服务中断。Cloudflare 是一家提供 CDN 加速、DNS 解析和网络安全防护的互联网基础设施公司,许多网站的访问都要经过它的全球网络。当 Cloudflare 近期发生系统故障时,大量依赖其 DNS 或流量转发的服务就无法正常工作,就像“互联网的高速路”突然堵塞,使得网站即使自身没坏也无法被访问。这次事故再次凸显了现代云服务中微小变更可能引发的巨大影响,尤其是在数据库查询行为、特定数据库的特性以及软件对hardcoded限制值的依赖方面。
Bot Management 如何引发全球故障
Cloudflare 的核心代理系统(称为 FL 或 FL2)处理所有流经其网络的请求,无论是 CDN、WAF、还是 Workers。Bot Management 是这个核心代理中的一个模块,用于为每个请求生成机器人评分。
由于 Bot Management 模块是请求处理链路中的一个环节,当这个模块因为无法加载过大的特征文件而崩溃时,整个请求就会返回 HTTP 5xx 错误。由于这个特征文件被快速分发到全球所有节点,并且所有节点的 FL2 实例都以相同的方式失败,因此故障是全球性的。依赖核心代理的其他服务如 Workers KV 和 Access 也因此受到影响而中断。
为何初期怀疑是DDoS攻击
在事故初期,Cloudflare SRE 团队曾一度怀疑这是由大规模 DDoS 攻击所致。主要有以下几个原因:
- 间歇性故障:系统错误呈现波动性,服务时好时坏,并不像典型的内部系统故障那样持续稳定地失败。
- 状态页面同步宕机:一个关键的误导信息是 Cloudflare 的状态页面也几乎在同一时间无法访问。尽管状态页面托管在完全独立于 Cloudflare 核心网络的第三方设施上,这个巧合让团队认为可能存在协同攻击。
- 近期攻击背景:事故发生时,正值一系列大流量 DDoS 攻击(如 Aisuru)频发的时间点,团队自然会将此事件与近期的攻击趋势联系起来。
故障的波动性:时好时坏的原因
服务的间歇性中断是由于底层的数据库变更采用的是渐进式发布。具体来说:
- Bot Management 系统每五分钟会通过查询 ClickHouse 集群生成一次特征文件。
- 导致问题的数据库权限变更正在集群中的节点上逐步推广。因此,在一段时间内,集群中新旧配置的节点并存。
- 每次生成文件的查询可能命中集群中的任何节点。如果命中未更新的节点,生成的文件正常;如果命中已更新的节点,则生成导致错误的文件。
- 这种好坏文件交替生成并快速分发到全球网络的情况,导致了服务时好时坏。直到所有 ClickHouse 节点都更新完毕,系统才稳定在持续失败状态。
核心问题:数据库权限变更
此次宕机的直接导火索是一次数据库系统权限的变更。这个变更导致用于Bot Management(机器人管理)系统的“特征文件”生成逻辑出现了意外。具体来说是一个基于ClickHouse数据库的查询行为发生了改变。
ClickHouse 的特殊性与查询变更
Cloudflare使用ClickHouse数据库集群来生成这个特征文件。由于一个历史原因,当用户针对default数据库查询元数据时用的是该账户的身份,但为了从集群中的其他分片获得r0数据库里的底层表,子查询用的是一个共享的系统账户。为了提升分布式查询的安全性和可靠性,Cloudflare进行了一项权限变更,让用户在查询default数据库里的系统表时,能够同时拥有隐式访问权限并看到r0数据库里的底层表。
问题在于,他们以前默认这个获取特征文件元数据的查询只使用default数据库,所以这个SQL查询没有指定数据库名称:
SELECT
name,
type
FROM system.columns
WHERE
table = 'http_requests_features'
order by name;
在这个变更之后,这个查询同时拥有了default和r0数据库的权限,然后ClickHouse规定如果你不指定数据库,那就返回所有有权限读取的数据库里的内容,于是两个数据库里的元数据表都被返回了,而且因为这个查询没有DISTINCT过滤重复项,最终导致返回的特征值的数量大幅增加。
写死的最大值与内存预分配
Bot Management模块为了性能优化和防止内存无限制消耗,对加载的机器学习特征数量设置了一个hardcoded最大值=200并为他们预分配了内存。在这个事故之前,他们用到的特征值在60个左右。
在上面的权限更改生效之后,超过200个特征值被错误地传输到了Cloudflare的服务器,超过了预设的200上限。
unwrap() 导致的崩溃
在新的FL2代理引擎中,检查特征数量的代码是用Rust编写的。当检测到特征数量超过上限(200)时,代码中的一个 Result::unwrap() 调用失败了,导致了线程崩溃(panic)并引发了5xx错误:
thread fl2_worker_thread panicked: called Result::unwrap() on an Err value

反思
这次事件暴露了几个关键问题:
- 对数据库查询结果的隐式假设: 写SQL竟然不强制要求必须限制表,离谱,也不考虑去重,离大谱。
- hard-code上限: hardcode问题倒是不大,关键你好歹写个unit test吧。
- 错误处理方式: 在关键路径上使用 unwrap() 这种遇到错误就崩溃的模式,而不是更优雅的错误处理和回退机制。
- 关键模块的隔离性: 单个模块(Bot Management)的失败导致了整个请求处理链路的失败,不优雅+1。当然这个模块很微妙,如果直接放行等于bot检查就失效了,可能就一直没人知道,现在直接崩溃反而提早发现问题了。
- 缺乏preprod测试:这个事故其实如果有很好的preprod测试系统是很容易发现的,ClickHouse preprod + BM preprod加上一些prober就可以完美复现5xx并提前阻止更新进入production。
与AWS事件中DNS记录被意外清空类似,Cloudflare的这次事故也是由一个看似微小的变更,通过一系列连锁反应,最终导致了大规模的服务不可用。
转载请注明:Slyar Home » 2025 Cloudflare 宕机事故 TLDR

























