11/18 晚上八點,我的 FB 上冒出一片哀嚎,先是一堆程式開發圈的朋友發現 ChatGPT、Claude、Perplexity 壞光光該洗洗睡了,隨著通報故障網站範圍擴大,不久大家把矛頭指向 Cloudflare,異常網站會顯示「Internal Server Error」錯誤訊息或停在「正在驗證您是否是人類」畫面,看起來是在 Cloudflare 層出錯。有趣的是,連專門偵測及蒐集網路服務是否故障的 Downdetector 自己也掛了。
(這幾天有人玩梗,做了偵測 Downdetector 是否掛掉的 Downdetecotr's Downdector,以及偵測 Downdetecotr's Downdector 是否掛掉的 Downdetector's Downdetector's Downdetector)

重要網站為確保服務不中斷(所謂高可用度 High Availability,HA),設計時都要千方百計消滅 Single Point of Failure (SPOF,單一故障點,指一旦壞掉整個系統便無法運作的單一元件),砸下重本為系統疊上滿滿備援,用多顆組成磁碟陣列,一顆故障時還能繼續跑、同樣伺服器裝兩台組叢集,一台掛了另一台馬上接手,不斷電系統及發電機自然也是必備,對外專線選不同電信廠商各接一條,A 壞了改連 B 上網,就連電源排插跟散熱風扇都得裝兩套,確保只壞一個死不了。種種努力,一切只為了讓 SLA 從 99.9% 升到 99.99%,把一整年的服務中斷時間從 9 小時壓低到 1 小時。

作足準備的網站一旦連上 Internet 面對來自全球的訪客和駭客,則迎來更多挑戰,需要分流平衡負載,需要防止花招百出的攻擊,最簡單的解法是讓 Cloudflare 等 CDN 或雲端服務站在第一線,借重其遍佈全球的伺服器實現靜態內容分流,另一方面依賴其與駭客交手上萬回合的專業與技術過濾掉大部分惡意攻擊。閃開讓專業的來,比自己做省事一千倍。作為全球主要 CDN 廠商,舉凡 X (Twitter)、ChatGPT、Claude、Spotify、Canva、LOL... 等大家耳熟能詳的服務幾乎都有在用 Cloudflare,讓 Cloudfalre 包辦全球 網路約 20% 的流量。結果...

thumbnail

媒體報導角度,該起事件的經過是:

2025-11-18 日晚間,Shopify、Indeed、Claude、Truth Social、X 等多個網站一度癱瘓,原因是背後的網路服務商 Cloudflare 發生大規模故障。起初傳出是系統遭受 DDoS 攻擊,但之後官方證實並非遭受攻擊,而是源於內部自動化配置管理的罕見交互失誤。主因是資料庫權限調整觸發連鎖反應,導致機器人管理的「特徵檔案」異常膨脹至兩倍大小,進而癱瘓全球路由軟體。
錯誤檔案每 5 分鐘自動生成一次,造成「正常→崩潰→恢復→再崩潰」的循環,工程團隊一度誤判為 DDoS 攻擊。Cloudflare 於 14:30(UTC)阻斷錯誤傳播並回復舊版配置,系統在 17:06 完全恢復,但已造成數小時全球網路中斷。

Cloudflare 在 18 日稍晚在部落格交代了事件的來龍去脈,原子能也做了一集 YouTube 影片提供詳細解說,有了這些資訊來源,就來花點時間從技術面看看這起從 11/18 11:28 UTC 到 17:06 UTC 歷時五個半小時的故障是怎麼一回事?

爆炸點在 Cloudflare 識別機器人行為的 Bot Management (BM) 服務,其使用機器學習的分類器演算法(Classifier)判斷請求是否來自機器人,分類器是以特徵資料作為判斷依據。考慮機器人攻擊手法日新月益,時時變化,特徵資料不宜寫死在程式或設定檔,而是被存進 ClickHouse 資料庫(一種開源 OLAP 資料庫)方便隨時動態調整,而 BM 每五分鐘更新一次,確保遭遇新攻擊手法時能在最短時間內提升防禦。

BM 讀取特徵資料的程序是先由 default 資料庫查詢分散式資料表,再從 r0 資料庫取得真正的特徵資料。原本查 default 要改用固定的系統帳號執行,為了增加安全跟可靠度,Cloudflare 決定讓查詢 r0 的初始使用者帳號就有權限讀取不必切換共用帳號。原本查詢 default 的 SQL 語法如下:

SELECT
  name,
  type
FROM system.columns
WHERE
  table = 'http_requests_features'
order by name;

不過,由於 default 與 r0 都有 system.columns,而 Clickhouse 有個特性,因為使用者帳號現在對 default 跟 r0 都有權限,這個查詢會合併傳回來自兩個資料庫的內容,不只同一筆重複出現,還混入 r0 額外的欄位資料,筆數暴增兩倍以上。

爆炸的 BM 新版引擎 FL2 採用 Rust 開發,基於效能考量會預設配置好 200 筆記憶體空間用來存特徵資料,畢竟一個分析請求用到的特徵數量不太可能超過 200 筆(目前只用到 60 個),抓 200 這個數字也算合理。但現在因 SQL 查詢寫法出錯讓結果筆數翻倍超過 200 筆,記憶體放不下,該處程式寫法是呼叫 unwrap(),選擇在出錯時直接結束程式。因此,一旦 BM 程式抓到過量特徵資料程式便會崩潰結束。

這段有問題的資料庫權限異動及查詢調整採漸進方式發佈到所有資料庫,故在早期只有部分資料庫已更新。當 BM 連到更新過的資料庫拿到超過 200 筆就會爆炸,五分鐘後切回尚未更新的資料庫便恢復正常,呈現時好時壞的狀況,讓 Cloudflare 工程師誤判這堆間歇錯誤是 DDoS 攻擊引發(好巧不巧前一天微軟經歷了 DDoS 攻擊,很容易往這方面聯想),沒在第一時間想到與資料庫異動與程式修改有關。

兩個小時後新做法同步到全部資料庫,所有 BM 拿到的都是超量特徵資料,全面掛點,Cloudflare 工程師這才在自己身上找問題,查出跟 BM 特徵資料庫異動有關,還原舊版特徵資料並暫停定期更新,在故障發生五個半小時後完成修復。

事件裡最低級的錯誤當屬沒抓到資料庫做法調整會導致 BM 爆炸的 Bug,理論上在測試環境就能重現,卻被全面部署引發大爆炸。

至於 Rust 採取預配置最大 200 筆所需記憶體空間的做法,實務上很常見,我沒想到更優美的解法。而遇到錯誤 unwrap() 讓程式崩潰,我覺得合理但有改善空間。

資料出現超過 200 筆還讓程式繼續執行,可能帶來無法預期的結果(在真實世界我遇過有人 try catch 時將值設零或空字串裝作沒事繼續跑,Debug 到讓人咬牙切齒)。在本次事件中,部分未換成 RUST 版的舊版引擎(FL) BM,讀取到錯誤筆數沒壞可繼續執行,但因特徵資料有錯無法運作,變成所有請求一律放行,防護罩完全失效,系統曝露在風險中卻渾然不覺,真的還不如原地崩潰。作為安全防護制,應優先考慮在異常時停掉系統,迫使人為介入檢查,而非停用防護繼續執行。故 BM 無法正常運作時讓系統崩潰的概念是對的,可改善之處是應更積極主動通報相關人員 BM 特徵資料同步出錯,可加快找到問題及修復時間。

不過,鍵盤評論講得輕鬆,輪到自己動手又是另一回事啦,哈!


Comments

# by 小熊子

分批換版也是要每次檢查

# by xfox

那很魔幻了🤣

Post a comment