Cloudflare 2025年11月18日全球服务中断事件分析
2025-11-18 Matthew Prince 阅读时间12分钟
2025年11月18日 11:20 UTC,Cloudflare的网络开始出现核心网络流量交付的重大故障。试图访问我们客户网站的互联网用户看到的错误页面表明,故障发生在Cloudflare网络内部。
此次事件并非由网络攻击或任何形式的恶意活动直接或间接导致。相反,它是由我们某个数据库系统权限的变更触发的。这一变更导致数据库向我们的Bot Management系统使用的"特征文件"中输出了多个重复条目。该特征文件的大小因此翻倍。随后,这个超出预期的特征文件被传播到了构成我们网络的所有机器上。
这些机器上运行的、用于在我们网络中路由流量的软件会读取此特征文件,以使我们的Bot Management系统能够应对不断变化的威胁。该软件对特征文件的大小设有一个限制,而此限制低于文件翻倍后的大小。这导致了软件崩溃。
我们最初错误地怀疑所见症状是由大规模DDoS攻击引起的,之后才正确识别出核心问题,并设法停止了过大特征文件的传播,并用该文件的早期版本进行了替换。到14:30,核心流量已基本恢复正常流动。在接下来的几个小时里,我们努力缓解因流量迅速恢复而导致网络各部分负载增加的问题。截至17:06,Cloudflare的所有系统均已恢复正常运行。
我们为此给客户和整个互联网带来的影响感到抱歉。鉴于Cloudflare在互联网生态系统中的重要性,我们任何系统的任何中断都是不可接受的。我们的网络有一段时间无法路由流量,这对我们团队的每一位成员来说都是极其痛苦的。我们知道今天我们让大家失望了。
本文是对所发生事件的详细复盘,也是对那些失效的系统和工作流程的分析。同时,这也是我们为确保此类中断不再发生而计划采取行动的起点(尽管不是终点)。
服务中断概述
下图显示了Cloudflare网络提供的5xx HTTP状态错误码的数量。这个数值通常应非常低,直到中断发生前也确实如此。
11:20之前的数值是我们网络中观察到的预期5xx错误基线。随后的峰值和波动显示我们的系统因加载错误的特征文件而出现故障。值得注意的是,系统随后会恢复一段时间。对于内部错误来说,这是非常不寻常的行为。
解释是:该文件由在ClickHouse数据库集群上运行的查询每五分钟生成一次,而该集群正在进行逐步更新以改进权限管理。只有查询在集群中已更新的部分上运行时,才会生成坏数据。因此,每五分钟就存在生成一组好或坏的配置文件并迅速传播到整个网络的可能性。
这种波动使得情况变得不明朗,因为整个系统会时而恢复时而再次故障,这取决于分发到我们网络的是好配置文件还是坏配置文件。最初,这使我们相信可能是攻击所致。最终,每个ClickHouse节点都开始生成坏配置文件,波动也在故障状态下趋于稳定。
从14:30开始识别并解决根本问题前,错误一直在持续。我们通过停止坏特征文件的生成和传播,并手动将一个已知良好的文件插入特征文件分发队列,然后强制重启核心代理,解决了该问题。
上图中剩余的长尾部分是我们的团队重启那些已进入错误状态的剩余服务,5xx错误码数量在17:06恢复正常。
以下服务受到影响:
| 服务 / 产品 | 影响描述 |
|---|---|
| 核心CDN和安全服务 | 出现HTTP 5xx状态码。本文开头的截图显示了呈现给最终用户的典型错误页面。 |
| Turnstile | Turnstile 无法加载。 |
| Workers KV | Workers KV 的HTTP 5xx错误率显著升高,因为KV的"前端"网关请求因核心代理故障而失败。 |
| 控制面板 | 虽然控制面板大部分功能仍可运行,但由于登录页面上的Turnstile不可用,大多数用户无法登录。 |
| 邮件安全 | 虽然邮件处理和投递未受影响,但我们观察到对IP信誉源的临时访问丢失,这降低了垃圾邮件检测的准确性,并阻止了一些新域名年龄检测的触发,未观察到关键客户影响。我们还看到部分自动移动操作失败;所有受影响的消息都已审查并修复。 |
| Access | 对大多数用户而言,身份验证失败非常普遍,从事件开始时一直持续到13:05开始回滚。任何现有的Access会话均未受影响。所有失败的身份验证尝试都返回了错误页面,这意味着这些用户在身份验证失败期间从未到达目标应用。在此期间成功的登录在本次事件中被正确记录。当时尝试的任何Access配置更新要么完全失败,要么传播得非常缓慢。所有配置更新现已恢复。 |
除了返回HTTP 5xx错误外,我们在影响期间还观察到来自CDN的响应延迟显著增加。这是由于我们的调试和可观测性系统消耗了大量CPU资源,这些系统会自动为未捕获的错误附加额外的调试信息。
Cloudflare如何处理请求,以及今天哪里出了问题
发送到Cloudflare的每个请求都会通过我们的网络走一条明确定义的路径。它可能来自加载网页的浏览器、调用API的移动应用或来自其他服务的自动化流量。这些请求首先在我们的HTTP和TLS层终止,然后流入我们的核心代理系统(我们称之为FL,即"Frontline"),最后经过Pingora,后者根据需要执行缓存查找或从源站获取数据。
请求通过核心代理时,我们会运行网络中可用的各种安全和性能产品。代理应用每个客户的独特配置和设置,从强制执行WAF规则和DDoS防护,到将流量路由到Developer Platform和R2。这是通过一组特定于领域的模块来实现的,这些模块将配置和策略规则应用于流经代理的流量。
其中一个模块——Bot Management,就是今天中断的根源。Cloudflare的Bot Management包含一个机器学习模型(以及其他系统),我们使用该模型为流经我们网络的每个请求生成机器人分数。我们的客户使用机器人分数来控制哪些机器人被允许访问其站点。
该模型接收一个"特征"配置文件作为输入。在此上下文中,“特征"是机器学习模型用来预测请求是否自动化的个体特性。特征配置文件是各个特征的集合。
该特征文件每隔几分钟刷新一次,并发布到我们的整个网络,使我们能够响应互联网上流量模式的变化。它使我们能够应对新型机器人和新的机器人攻击。因此,随着恶意行为者快速改变策略,频繁且快速地推出该文件至关重要。
我们底层ClickHouse查询行为(下文解释)的一个变更,导致该文件包含了大量重复的"特征"行。这改变了先前固定大小的特征配置文件的大小,导致机器人模块触发错误。
因此,为我们客户处理流量处理的核心代理系统,针对任何依赖机器人模块的流量返回了HTTP 5xx错误码。这也影响了依赖核心代理的Workers KV和Access。
与本次事件无关,我们当时(及现在)正在将客户流量迁移到新版本的代理服务(内部称为FL2)。两个版本都受到了该问题的影响,尽管观察到的影响不同。
部署在新的FL2代理引擎上的客户,观察到了HTTP 5xx错误。在我们旧代理引擎FL上的客户没有看到错误,但机器人分数未能正确生成,导致所有流量获得的机器人分数为零。部署了规则以阻止机器人的客户会看到大量误报。未在其规则中使用我们机器人分数的客户未受到任何影响。
另一个让我们困惑并使我们相信这可能是攻击的症状是:Cloudflare的状态页面宕机了。该状态页面完全托管在Cloudflare基础设施之外,不依赖Cloudflare。虽然结果证明这只是巧合,但它导致诊断问题的一些团队成员认为,攻击者可能同时针对我们的系统和我们的状态页面。
在内部事件聊天室中,我们担心这可能是近期一系列高流量Aisuru DDoS攻击的延续:
查询行为变更
我上面提到,底层查询行为的一个变更导致特征文件包含大量重复行。所讨论的数据库系统使用ClickHouse软件。
为了便于理解,了解ClickHouse分布式查询的工作原理会有所帮助。一个ClickHouse集群包含许多分片。为了从所有分片查询数据,我们在名为default的数据库中有所谓的分布式表(由表引擎Distributed提供支持)。Distributed引擎查询数据库r0中的底层表。底层表是数据存储在ClickHouse集群每个分片上的地方。
对分布式表的查询通过一个共享系统账户运行。作为改进分布式查询安全性和可靠性工作的一部分,正在努力使其在初始用户账户下运行。
在今天之前,当从ClickHouse系统表(如system.tables或system.columns)查询表元数据时,ClickHouse用户只能看到default数据库中的表。
由于用户已经隐式访问r0中的底层表,我们在11:05进行了一项更改,使这种访问显式化,以便用户也能看到这些表的元数据。通过确保所有分布式子查询都能在初始用户下运行,可以以更细粒度的方式评估查询限制和访问授权,避免一个用户的错误子查询影响他人。
上述变更导致所有用户都能访问他们有权访问的表的准确元数据。不幸的是,过去存在一个假设,即类似以下查询返回的列列表将只包含default数据库:
|
|
请注意查询中并未筛选数据库名称。随着我们逐步向给定ClickHouse集群的用户推出显式授权,在11:05的变更之后,上述查询开始返回列的"重复项”,因为这些重复项是存储在r0数据库中的底层表的列。
不幸的是,这正是Bot Management特征文件生成逻辑为了构建本节开头提到的文件中每个输入"特征"而执行的查询类型。上述查询将返回一个类似下表的列列表(简化示例):
然而,作为授予用户的额外权限的一部分,响应现在包含了r0模式的所有元数据,有效地使响应中的行数增加了一倍以上,最终影响了最终文件输出中的行数(即特征数量)。
内存预分配
我们代理服务上运行的每个模块都设置了许多限制,以避免无限制的内存消耗,并作为性能优化手段进行内存预分配。在此特定实例中,Bot Management系统对运行时可使用的机器学习特征数量设置了一个限制。目前该限制设置为200,远高于我们当前使用的约60个特征。再次强调,设置此限制是出于性能原因,我们需要为特征预分配内存。
当包含超过200个特征的坏文件传播到我们的服务器时,此限制被触发——导致系统恐慌。触发此未处理错误的FL2 Rust代码检查如下:
这导致了以下恐慌,进而产生了5xx错误:
thread fl2_worker_thread panicked: called Result::unwrap() on an Err value
事件期间的其他影响
依赖我们核心代理的其他系统在事件期间也受到了影响。这包括Workers KV和Cloudflare Access。团队在13:04降低了对这些系统的影响,当时为Workers KV应用了一个补丁以绕过核心代理。随后,依赖Workers KV的所有下游系统(如Access本身)观察到错误率降低。
Cloudflare控制面板也受到了影响,原因是在内部使用了Workers KV以及在登录流程中部署了Cloudflare Turnstile。
Turnstile受到此次中断的影响,导致没有活动控制面板会话的客户无法登录。从下图中可以看出,在两个时间段出现了可用性降低:11:30至13:10,以及14:40至15:30。
第一个时期,11:30至13:10,是由于对某些控制面板和面板功能所依赖的Workers KV造成了影响。这在13:10得到恢复,当时Workers KV绕过了核心代理系统。
控制面板受到影响的第二个时期发生在恢复特征配置数据之后。积压的登录尝试开始使控制面板不堪重负。这种积压,再加上重试尝试,导致了延迟升高,降低了控制面板的可用性。扩展控制平面并发性后,大约在15:30恢复了可用性。
补救和后续步骤
现在我们的系统已恢复在线并正常运行,我们已经开始研究如何加强它们,以抵御未来类似故障。具体来说,我们正在:
- 以与处理用户生成输入相同的方式,加固对Cloudflare生成的配置文件的摄入。
- 为功能启用更多全局紧急停止开关。
- 消除核心转储或其他错误报告耗尽系统资源的可能性。
- 审查所有核心代理模块中错误条件的故障模式。
今天是Cloudflare自2019年以来最严重的一次中断。我们经历过导致控制面板不可用的中断。也经历过使新功能在一段时间内不可用的中断。但在过去6年多的时间里,我们没有遇到过另一次导致大部分核心流量停止流经我们网络的中断。
像今天这样的中断是不可接受的。我们设计的系统本应具有高度弹性,以确保流量始终持续流动。过去我们发生中断时,总是会促使我们构建新的、更具弹性的系统。
我谨代表Cloudflare全体团队,为我们今天给互联网带来的困扰深表歉意。
| 时间 (UTC) | 状态 | 描述 |
|---|---|---|
| 11:05 | 正常。 | 数据库访问控制变更部署。 |
| 11:28 | 影响开始。 | 部署到达客户环境,首次在客户HTTP流量上观察到错误。 |
| 11:32-13:05 | 团队调查了Workers KV服务升高的流量水平和错误。初始症状似乎是Workers KV响应率下降导致对Cloudflare其他服务的下游影响。尝试了流量操纵和账户限制等缓解措施,以使Workers KV服务恢复正常运行水平。第一个自动化测试在11:31检测到问题,手动调查在11:32开始。事件呼叫在11:35创建。 | |
| 13:05 | 实施了Workers KV和Cloudflare Access绕过——影响降低。 | 调查期间,我们对Workers KV和Cloudflare Access使用了内部系统绕过,使其回退到核心代理的先前版本。尽管该问题在代理的先前版本中也存在,但如以下所述,影响较小。 |
| 13:37 | 工作重点转向将Bot Management配置文件回滚到最后已知良好版本。 | 我们确信Bot Management配置文件是事件的触发因素。团队通过多个工作流研究修复服务的方法,最快的工作流是恢复文件的先前版本。 |
| 14:24 | 停止了新的Bot Management配置文件的创建和传播。 | 我们确定Bot Management模块是500错误的来源,而这由坏配置文件引起。我们停止了Bot Management配置文件的自动部署。 |
| 14:24 | 新文件测试完成。 | 我们观察到使用旧版本配置文件成功恢复,随后集中精力加速全局修复。 |
| 14:30 | 主要影响解决。下游受影响服务开始观察到错误减少。 | 一个正确的Bot Management配置文件在全球范围内部署,大多数服务开始正常运行。 |
| 17:06 | 所有服务解决。影响结束。 | 所有下游服务已重启,所有操作完全恢复。 |