Cloudflare 是什么

对于运维同学来说,cloudflare 并不是一个陌生的名词,我们会经常把 cloudflare 称为赛博活佛(因为它提供了免费的全球代理等功能,在其他云上那边都要只能上全球 CDN,收费还不便宜,但是 cloudflare 只需要把域名托管到 cloudflare 平台上就可以免费使用全球加速了)。正如最近网上盛传的一个梗图

c2eecd2f060540954908956be7250c7d.png

cloudflare 已经算是互联网的最根本的基石了,可以说 cloudflare 撑起了一整个互联网。cloudflare 在海外提供了多种云服务,比如权威 DNS(one.one.one.one)、全球 CDN、DDoS 防护、SSL/TLS 加密等,可以说只要目前咱们叫得上名字的大部分海外大厂都在用他们家的服务,比如 Netflix、Spotify、Twitter 等,可以说 cloudflare 在海外已经是举足轻重的地位了。但是因为一些合规原因,cloudflare 在国内业务是代理给国内一家叫科赋锐的公司(名字这么像,估计是为了合规在国内搞的全资子公司),而且经过代理之后,海外 cloudflare 有的优势以及特性在国内都不存在了,所以在国内的普及度远不如腾讯云和阿里云。

Cloudflare 20251118 全球故障

2025 年 11 月 18 日 11:20 UTC(19:20 中国时间),全球多个在 cloudflare 平台托管的域名出现了故障,包括 coinbase、twitter、reddit、github 等,访问报错 500.

所有网站访问的报错都是上边这样的,从域名关联的流量链路共性来看,都经过了一层 cloudflare 代理,都显示 cloudflare 报错,所以可以肯定是 cloudflare 跪了。目前 cloudflare 方面已经发布了相关的故障报告,链接:2025 年 11 月 18 日 Cloudflare 服务中断

这次的故障影响面极大,我们先不看故障报告,用我们运维经验来看分析这个故障,无外乎有以下一些可能:

DDoS

cloudflare 的代理可以理解是一个全球动态加速的 CDN 和优质 IP 的 ALB 的集合。既然这是一个 CDN 服务,那么一定会在全球各地部署边缘节点,并且会有一整套 anycast 策略。也就是说,如果遭受到 DDoS 攻击,如果 anycast 策略没有失能,且没有出现 BUG 向所有节点分发流量的问题(anycast 是基于路由策略实现的,即使出问题也是直接丢包,全量分发流量基本不太可能),那么即使被攻击,最多也是某一个区域出现故障,不会影响到全球用户。而且以 cloudflare 这么大的体量,加上 cloudflare 在前端本来就有一层 WAF,即使流量都穿透到用户那边了,我觉得最先跪下的是产品服务端,而不是 cloudflare。

当然也有一种特殊的可能,就是黑客从全球多点发起大规模的攻击,这个是根本不可能的。就 cloudflare 当前的体量,加上本来也加了一层 WAF 的情况来说,黑客攻击的难度极高,且成本也极高(要是这都想打,这得和 cloudflare 多大仇啊,我只能竖起大拇指了),所以基本可以排查出掉这个说法。

代理服务 BUG

我们都知道,Cloudflare 的代理服务不仅具备传统代理的基础能力,还通过类似 OpenResty 的机制扩展了大量插件功能,从而实现丰富的流量处理逻辑。因此,其代理服务已远超常规意义上的代理范畴。

回顾当天的故障现象,由于所有托管网站都炸了,且未出现部分请求正常的情况,可以推断:如果真是插件代码出现 BUG,那么很可能是某个必须对每条流量进行处理的插件发生异常,导致所有请求都在该环节被阻塞,进而被丢弃或返回错误,而未能继续发往后端服务。

这么看的话,理论是成立的,两个必要条件只要满足一个就基本实锤:

  1. 故障前进行了整系统变更,包括但不限于插件升级、配置变更等。
  2. 故障时间点的流量出现了一个极大的波动,导致代理系统的资源被耗尽。

cloudflare 内部网络故障

这个原因是比较少人考虑到的一种情况。cloudflare 既然代理是个全球动态 CDN,那么原理无外乎是用户请求从 anycast 入口(ingress 向)进入,各边缘节点通过隧道/专线等策略实现互通,流量走这些路由策略以最快的速度到达距离服务端最近的出口网关(egress 向)然后把流量发出去,回包的原理直接把这个链路反过来就好(GCP 的 global load balancer 底层原理就和这个一样)。那么,如果内部 SDN 或者专线出现故障的话,依然会导致整个代理系统雪崩。

这个理论在本次故障中也是成立的。

放下经验,回归报告

报告内容很长,我只摘取我觉得关键的点进行一些分析。

开门见山

并不是任何类型的网络攻击或恶意活动直接或间接引发了此问题。相反,它是因为我们数据库系统权限变更而触发,权限变更导致数据库将多个条目输出到 Cloudflare 机器人管理系统使用的“特征文件”。结果,该特征文件的大小增加了一倍。随后,这个超出预期大小的特征文件传播到构成 Cloudflare 网络的所有计算机。
合乎逻辑的解释是,该文件由 ClickHouse 数据库集群上运行的查询每五分钟生成一次,该集群正在逐步更新,以改进权限管理。只有当查询在集群中已更新的部分运行时,才会生成错误数据。因此,有可能每五分钟生成一组正确的配置文件或一组错误的配置文件,并在 Cloudflare 网络中快速传播。

报告直接开门见山的交代了本次故障的根本原因,指明了这就是由代码变更引入的 BUG,有人可能会问,这个 BUG 难道在上线前 QA 就没有测出来吗?

其实,这种 bug 即使在上线前过了 QA,并且严格按照了灰度发布流程,也未见得就能发现。原因如下(以下内容是从经验出发,可能不单单针对这一次故障):

  1. 如果是小版本更新迭代的话,团队通常是不会做性能压测的(压测浪费时间,并且成本极高),且测试可能会存在一定的边界条件,就会出现:

    • 打流不足,服务无法明确展示 QPS 与消耗资源的关系
  2. 测试出现边界(测试环境和正式环境数据不一致,这个是难免的,甚至有的回归环境数据都是乱的),所以测试用例可能只能覆盖到正常情况,而无法覆盖到边缘情况。

  3. 灰度发布策略也不一定会及时发现问题,这个主要取决于发布策略了。如果首轮放量太小的话,也会出现像上面一样打流不足的情况。所以估计是新版本发布时仅进行了小流量灰度放流,灰度环境未发现异常就直接全量了,然后全量后就炸了

我以前在做云游戏的时候遇到过一个类似问题。当时后端更新新版后引入一个错误的查询,导致查询量超大,进而导致数据库和后端服务器被拖垮,导致服务不可用,这种问题 QA 是测不出来的。(这个故障和本次故障的根因类似,但是机制不同,继续看报告)

问题分析

这个代理架构我在前面已经有说过了,现在直接看核心内容

Cloudflare 的机器人管理模块包含多个系统,其中之一是机器学习模型,我们使用该模型为流经 Cloudflare 网络的每个请求生成机器人评分。我们的客户使用机器人评分来控制允许或禁止哪些机器人访问其网站。
由于底层 ClickHouse 查询行为变更(详见下文),导致生成特征文件时出现了大量重复的“特征”行。这改变了原本固定大小的特征配置文件的大小,从而导致机器人模块触发错误。

这就是个典型的中间件故障导致的整个系统异常的情况。

从结构图中可以看出,流量从客户端到服务端是一个链式的,FL 模块中所有子模块都是独立的,理论上来说一个好的架构在大模块中的子模块应该尽可能完全独立,某一模块出现故障时可以直接降级对应模块以维护业务主流程稳定,但是从本次的故障看来,cloudflare 对于这部分并没有做到很好的降级策略(其实这个也不能怪到 cloudflare,如果真 的在故障时把流量清洗这一块降级掉的话,那攻击流量是很恐怖的,流量直接穿透客户要直接骂街了)。

基于上述分析,我们可以总结出运维架构设计中的几个关键原则:

  1. 强依赖组件的独立性设计:在架构设计阶段,应确保核心模块内的子系统具备高度独立性,当某一组件发生故障时,能够通过降级机制保障业务主流程的持续运行(即使牺牲部分非核心功能)。本质上,就是要为业务流程提供绕过故障节点的能力。

  2. 降级策略的场景化考量:上述原则并非适用于所有场景,例如本次 Cloudflare 的流量清洗场景。以电商系统为例,若订单财务系统发生故障,强行降级以维持交易流程可能导致订单数据不一致或丢失。在非高峰期,直接暂停相关业务流程进行维护,其整体影响和损失反而小于数据异常带来的风险。

数据查询的内容多且繁杂,我用 AI 进行了一下总结:

权限变更引发数据异常:为优化 ClickHouse 分布式查询的安全性与精细化权限管理,Cloudflare 调整数据库配置,让用户可显式访问 r0 数据库(存储分片基础数据)的表元数据,打破了此前仅能查看 default 数据库(分布式表所在库)的限制。
查询缺失数据库筛选条件:机器人管理系统生成特征文件时,执行的表元数据查询指令(如查询 system.columns 表)未添加 “按数据库名称筛选” 的条件,权限变更后,该查询同时返回 default 和 r0 数据库中同名表的列数据,导致结果出现大量重复特征行。
重复数据触发连锁故障:重复条目使特征文件体积翻倍,超出核心代理软件的预设大小限制,直接引发路由软件崩溃,进而导致全网服务中断。

我对 PG 也不是很熟悉,那我们就从一个不懂 PG 的角度来切入,看这里他们踩了几个雷:

  1. 涉及数据库账号权限的,一定要做显式定义,一定要有权限 deny 兜底,以防止权限击穿,操作到不该操作的东西。

  2. 数据变更,特别是权限类的变更,一个不小心就会导致线上出现问题,应该在回归环境充分进行模拟测试,确保变更合理。

  3. 数据库查询时尽量要做显式筛选,避免数据查询精确度问题。

  4. 代码冗余性问题,查询到错误数据就直接 crash 掉了,这显然是不太优雅的。我理解这里应该可以接入一个缓存机制(因为模型数据本来也变的不多,所以引入缓存也不会对业务有很大影响)(尽量用 memcache 这一类的单节点缓存,每个节点只存自己的数据,避免缓存数据异常时整个集群雪崩。)当程序明确感知到查到的数据有异常时,默认从缓存中获取最后一次获取到合法的数据。

总结

其实回看这个故障报告,主要的风险点就是权限变更引入 BUG 进而导致业务雪崩,并且业务没有做到很好的兜底策略。总的来看,有两个教训:

  • 运维中,一定要严格按照既定 SOP 操作,操作前在测试或者回归环境中做严格的变更测试,操作后密切关注业务状态,状态异常立即降级服务或回滚变更。
  • 开发中,做好重点服务依赖隔离,做好容灾降级策略,做好服务异常兜底策略。

但愿这么大的故障,以后还是少出现一些吧。