vatt'ghern jaskier's ballads
本文 3 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

2025 年 11 月 18 日,一份 Bot Management 的 ML classifier 資料檔在全網所有節點同一瞬間被讀進記憶體;它格式異常、調用者沒有 graceful path,Cloudflare 邊緣半數 routing 直接 panic。十七天後,12 月 5 日,一個被翻轉的 control flag 又從 global configuration system 一路推到全網——同樣的 failure shape、同樣的 blast radius。一年前那場宣告「fail small」的全公司動員,被自己的網路反問了兩次:你打算寫到什麼時候,才換成寫工具與規則?

Code Orange 收口——Cloudflare 把一年的 fail-small 計劃落實到 Snapstone 與 Engineering Codex

月初,Cloudflare 的 Jeremy Hartman 在 blog 上宣告了 Code Orange 結束——這個從 2025 年中啟動、橫跨「兩個多季度」的全公司動員, 凍結了大部分非必要 feature 工作、把整個 engineering org 押在 reliability。 收口的成果不是一份內部報告,而是兩個被寫進日常 workflow 的工具: Snapstone,把所有 configuration change 強制套上 progressive rollout 與 automated rollback; 以及 the Codex,一份由 AI code review 在每個 PR 上強制執行的內部規則集。 這篇要拆的不是這兩個工具的功能單, 而是「為什麼是這兩個工具」——把一年的事故經驗壓縮成可重複執行的形式, 遠比再請所有人「小心一點」有效。

drag handle along the timeline · Code Orange 11 個月、6 個關鍵事件

2025-06 2025-06 2026-05
2025-06 拖動軸上的圓點,看 Code Orange 11 個月內每個關鍵時刻網路狀態與工程組進度。

互動圖表

時間軸走過 11 個月 6 個事件,Snapstone 分階段 rollout 讓設定錯誤在 canary 就被攔住。

2025 年 6 月:Code Orange 宣告的那天,與「fail small」最初的意思

Code Orange 是 Cloudflare 內部的紅色警戒級別—— 出現某種超出常規修補範疇的系統性問題時, CEO 會宣告 Code Orange,工程組所有非必要 feature 工作暫停、 人員集中到「修這件事」上。 歷史上罕用:產品線爆量時的 reliability scramble、 某次大型安全事件、再就是 2025 年中這次—— 全網規模的 routing 異常後, 公司決定不要把這場事故當作「再寫一份 postmortem 就好」處理, 而是宣告 Code Orange,把整個 engineering org 的方向盤打到 reliability。

當時對外的承諾叫 fail small。 這個詞容易被理解成「不要讓事故發生」——但這不是 Cloudflare 的意思。 Fail small 的字面意義是「failure 還是會發生,但要讓 blast radius 收得小」。 對應的工程動作有四條: 壞輸入要被擋在 perimeter(不要進到 hot path); service 之間要假設 upstream 可能在錯誤狀態(不能直接相信讀到的資料); configuration change 不應該瞬間全網生效(要 progressive、要可量測、要可回滾); 當主路徑失效時,要有 break-glass 路徑能在「dashboard 都壞了」的條件下 重新接管系統。

列出來像四個基本款——但每一條在當時的 codebase 裡 都有大量「沒這樣寫」的真實案例。 最常見的一條是 Rust 程式裡的 .unwrap(): 拿到一個 ResultOption, 懶得處理 error case 直接展開, runtime 撞到 None / Err 就 panic。 產品 code 裡到處是這種「在 dev 環境 99% 不會發生」的展開, 產線上一旦 input 偏離預期就直接掛掉。

第二條是 service A 收到 service B 的 response 後不檢查狀態欄位、 直接當有效資料用: B 進入降級狀態時還是會 200,但 payload 是空的或殘缺的, A 把空集合當「沒有結果」往下游送,行為靜默偏離。 這條的危險在於它不會在 monitoring 上跳—— error rate 沒漲、latency 也正常, 只是某個業務指標(被攔截的 bot 數、被命中的 rule 數)開始異常, 而這類指標通常沒有 page 等級的 alert。

第三條最戲劇化:configuration system 把一份檔案推到 100% 邊緣節點, 沒有「先推 1%、看 health metrics、再推 10%」這個漏斗。 這條的反直覺之處是它在「正常時候」看起來像 feature 不是 bug—— 全網秒級生效對 incident response、對「我需要立刻把這條 rule 推上去擋攻擊」 這類緊急場景是有價值的。 Fail small 不是廢掉這個能力, 而是把它從「預設行為」改成「明確 opt-in 的緊急路徑」。

Code Orange 一開始的方法是傳統的—— 加 review 強度、辦 architecture deep-dive、寫 best-practice doc、開教育訓練。 這條路對「個別 PR」有用,但對「下個月入職的工程師」是 zero leverage—— 文件會老、會被人忽略、會在搜尋失敗時被默默繞過。 Cloudflare 後來的判斷是:這場 reliability 工程不能靠「請所有人小心」收口, 必須把判斷力編譯進工具—— 這就是 Snapstone 與 Codex 後來成為核心交付物的理由。

// fail small 在 code 層面要消滅的三種典型寫法

// 1) Rust 程式:拿到 Option/Result 不做 fallback,直接 unwrap
let cfg = load_classifier_data().unwrap();   // data file 格式變了 → panic

// 2) Service-to-service:不檢查 upstream 健康狀態
let neighbors = upstream.fetch_routes().await?;
return route_best(neighbors);                // upstream 降級回空集合 → 靜默路由錯誤

// 3) Config rollout:one-shot 全網推送
config_store.publish(file_v2);               // 100% 邊緣節點同一秒讀到,bad input → 全網 panic

這三種寫法在 2025 年 6 月以前並不是被允許的 (任何 senior reviewer 看到都會在 PR 上 push back), 但它們仍然會在某個下午被快速 merge—— reviewer 漏看、reviewer 不夠 senior、reviewer 信任作者、 PR 太小不值得 deep review、deadline 壓力—— 理由可以列出十個,每一個單獨看都「不過分」, 加總起來就是 11/18 與 12/5 那兩次事故。 Code Orange 的核心轉折發生在「決定不再相信 reviewer attention 是充分機制」的那一刻。

2025 年 11 月與 12 月:兩起事故,同一個 failure shape

Code Orange 啟動後的前幾個月,工程組明顯收到效果—— 但實際數字之外的 disconfirming 證據在年底接連送上門。 11 月 18 日,Bot Management 團隊在做一次 ML classifier 的 rollout, 這個 classifier 會讀一份 model data file 做 inference。 新版本的 data file 在某個欄位的格式跟 caller 預期不同—— caller 的 deserialize path 沒有 graceful fallback,直接 panic。 問題是這份 file 的 publish 路徑是「一推即全網生效」, 所以邊緣節點同時讀到、同時崩潰、 半數 routing 在幾秒內失能。

這次事故有兩個值得單獨拆的細節。 一是 ML classifier 的 data file 本來就是「會定期更新」的物件—— 每次 retrain 都會產出新版本、然後 publish。 這個更新頻率讓「把它當 binary deploy 處理」的提議在事前看起來不合理: deploy 一個 model file 跟 deploy 一個 binary 不是同類動作。 Code Orange 後段的判斷是:對 hot path 的 input 來說, 「來源頻率」不是 progressive 與否的判準, 「壞了的 blast radius」才是。 任何能直接決定 hot path 行為的東西, 都要走 progressive。

二是 caller 端的 deserialize code 直接 unwrap 的選擇—— 這個寫法在當時的 codebase 並不孤立。 Rust 的 type system 已經逼著作者面對 Result, 但 .unwrap() 提供了一個「把 Result 當 T 用」的脫逃艙; 這個脫逃艙在「我確信這個值不會 Err」的場景被當成 ergonomic 工具用, 但「我確信」這個前提在 production 跟 dev 環境的可靠度差距很大。 Rule A 的存在不是因為 Rust 沒給 unwrap 選擇, 而是因為「production code 裡的 unwrap」幾乎總是錯的—— 把這條 invariant 寫進 AI reviewer 比寫在 wiki 上有用得多。

12 月 5 日,一個 control flag 被翻轉到 production—— 這個 flag 跑在 global configuration system 裡, target 是某個 hot path 的行為開關。 Flag 本身被測試過,但被翻轉後觸發的下游 service 假設了一個它從未驗證過的前置條件——upstream 的某個欄位「應該」非空。 事實上在新 flag 的語意下那個欄位確實可能為空, dependent service 收到空值後行為偏離, 連鎖把另一塊核心服務拖垮。

12/5 那次的 failure 比 11/18 更難 attribute—— flag 翻轉這個動作本身沒問題,被測試過、被 reviewer 看過、 甚至有 small-scale rollout 測試。 問題出在 service-to-service 的 contract 隱含假設: A service 一直以來收到的某個欄位都非空, 開發者就把這個觀察當成 invariant 寫進了 code, 沒有顯式 validation。 當 upstream B 因為 flag 翻轉開始合法地送空值時, A 直接 happy path 處理空集合、把錯誤往下游 propagate。 這條 chain 上沒有任何 service 在 monitor「我的 invariant 被打破了」。

兩起事故的 root cause 表面上完全不同—— 一個是 data file 格式問題,一個是 control flag 語意問題。 但放在一起看,failure shape 完全一致: bad input 沒有 graceful path、 change 在 perimeter 失效後直接擴散到 hot path、 blast radius 是整個邊緣網路。 這把 Code Orange 推進到第二階段—— 既然「個別 code review 攔不住」、 「個別 team 自己做 progressive rollout」執行不到位, 那就把這兩件事寫成系統強制行為。

click column header to sort · 2 起事故 × 6 個維度

日期 受影響 service 觸發物 缺的防線 blast radius failure shape
2025-11-18 Bot Management ML classifier 的 data file caller 沒有 fallback / 沒有 progressive rollout 半數邊緣 routing 數分鐘內失能 bad input → hot path panic
2025-12-05 核心配置生效服務 global config 中的 control flag downstream 未驗證 upstream 狀態 關鍵核心服務級聯失效 bad input → hot path panic

互動圖表

兩起 Cloudflare 事故在 blast radius 等 6 個維度並列,共同根因都是壞輸入打穿熱路徑。

表的最後一欄是這篇 post 的 punch line。 把 11/18 跟 12/5 並排,failure shape 是同一句話: bad input 被推到 hot path、沒有 fallback、blast radius = 全網。 這意味著 Cloudflare 此前對「reliability」的精力分配是錯的—— 大家在寫 postmortem 時太聚焦於 「為什麼那份特定的 data file 變了」「為什麼那個特定的 flag 被翻轉」, 但這兩件事是隨機事件、攔不住下一次。 真正可以被工程化的是把「sliced rollout + state validation」 變成系統預設行為—— 這就是 Snapstone 與 Codex 在 Q1 2026 加速進場的脈絡。

這個分析的方法論本身值得記下—— 把多起事故抽象成「同一個 failure shape」需要刻意的訓練。 人類在做 incident analysis 時, 認知偏誤會把每一起事故當成獨立故事處理: 11/18 的故事是「Bot Management data file 出問題」、 12/5 的故事是「control flag 翻轉出問題」, 這兩個故事在表面詞彙完全不重疊, 所以「同根因」的判斷需要刻意去找抽象層。 Cloudflare 的應對是把所有 incident analysis 都納入 Codex 的 input—— 每場 postmortem 都要回答「這暴露了哪條我們尚未 enforce 的 invariant」, 把答案沉澱回 rule set。

Snapstone:把 progressive rollout 變成 configuration change 的預設形狀

Snapstone 處理的問題很具體:在 Cloudflare 過去的工程文化裡, configuration change 跟 code change 是分開的兩個系統—— code 走 binary deploy、有 CI、有 staging、有 canary、有 health-mediated 釋放; config 走 configuration system,「publish」這個動作預設是「立刻全網生效」。 每個 team 可以自己在上面疊一層 health-mediated deployment 邏輯, 但 Hartman 的描述是「這需要每個 team 顯著的個別投入,並且沒有被一致地套用」—— 意思是有人做、有人沒做、有人做了一半, 整體可靠性取決於最弱的那個 team。

這個「最弱 team 決定整體可靠性」的觀察值得單獨記下。 在大型工程組織裡,任何「需要每個 team 自己做對」的紀律 都會在 N 越大時越快崩潰—— 新 team 形成、老 team 重組、人員流動、優先級漂移, 都會讓「自願 opt-in 的 best practice」的覆蓋率單調下降。 對 reliability 這種「最弱環節決定整體」的領域, 這個動力學是致命的—— 99% team 都做對也不夠,剩下 1% 的 team 一旦觸發 global outage, 其他 99% 的努力都白費。

Snapstone 的設計是把這層 progressive rollout 變成 first-class、預設啟用、跨 team 一致的元件。 Hartman 的描述是:「給 configuration deployments 預設提供 progressive rollout、 real-time health monitoring 與 automated rollback 的統一方式」。 關鍵詞是「by default」—— team 不需要顯式 opt-in、也不需要自己實作分批與 rollback 邏輯。 它的「configuration unit」定義是 dynamic 的: 可以是一份 data file(像 11/18 那次的 ML classifier data)、 也可以是一個 control flag(像 12/5 那次的開關), 任何「team 想要套上 health 機制」的單位都可以註冊。

click any stage to read its responsibility · Snapstone 4 個關卡

一次 config change 必經的 4 個關卡 Snapshot 封裝待釋放單位 Canary 少量節點先吃 Health Gate 指標 OK 才放行 Global 才到 100% 指標 regression automated rollback 不需要人介入、不需要 incident channel

Snapshot · 把要釋放的東西包成 atomic unit

不論是 data file、control flag、policy bundle 或 routing rule,先封裝成一個 Snapstone 可識別的 unit。這個 unit 帶了 metadata:哪一個 service 擁有它、哪些指標應該被觀察、health 失敗時的 rollback target 是哪個版本。

不知道什麼:unit 內部的語意。Snapstone 對它一視同仁——任何「可能讓系統行為改變的東西」都應該走這個入口。

Canary · 先送到一小撮節點,不是全網

取代了過去「publish 即全網生效」的預設。Canary 階段只把新版本送到 cohort A——對 edge service,這通常是一小批節點;對 control plane service,可能是一個 region 的副本。原本的版本繼續服務其他流量。

不知道什麼:哪個版本是「正確」的。Canary 不做正確性判斷,只做存在性——讓兩個版本同時在線、把判斷推給下一關。

Health Gate · 觀測指標,不過關就停在這裡

real-time health monitoring 比較 canary cohort 與其他 cohort 的關鍵指標:error rate、p99 latency、特定 service 的自定義 SLO。指標在規定窗口內維持健康,才放行到下一個 cohort(典型:1% → 10% → 50% → 100%)。任何 regression 都觸發 automated rollback,把 canary cohort 回到舊版本。

不知道什麼:「regression 是真的嗎」。Health Gate 採用保守策略——寧可誤判 rollback、也不放行壞版本。是否要 retry 由人後續判斷。

Global · 抵達 100% 的唯一路徑

只有經過所有中間 cohort 的 health gate 才會走到 global。對 11/18 那種「壞 data file」,這意味著 bad input 在 canary 階段就會被 health gate 攔下——blast radius 收斂到 1%、不再是 100%。對 12/5 那種「壞 control flag」一樣適用:flag 翻轉先在 canary cohort 觀察 downstream service 的健康,才放行全網。

不知道什麼:100% 之後出現的問題。Snapstone 只負責「漏斗」,不負責「永久監控」——後者是 SRE 的事。

互動圖表

Snapstone 四階段:Canary + Health Gate 自動回滾確保設定錯誤不推到 Global 節點。

讀 Snapstone 的設計時,最容易誤讀的一點是把它當成 「Cloudflare 終於做了 canary deploy」——這個讀法不準。 Canary 對 binary deploy 早就是業界標配;Snapstone 的核心動作 是把「configuration」這個原本被視為輕量、可立即生效的類別, 重新分類進 binary deploy 同等級的釋放紀律裡。 對工程組的日常體感,這個改動很大:原本 「改個 flag、按 publish、十秒內全網生效」的肌肉記憶, 被替換成「改個 flag、提交 Snapstone change、它跑 canary → health gate → 才到 global, 整個流程可能要十幾分鐘」。代價是 deploy velocity 下降, 回報是 11/18 與 12/5 那種事故的 blast radius 在 canary 階段就收斂。

另一個值得記下的判斷是 Cloudflare 沒有把 Snapstone 寫成 「另一個 deployment system」與既有 binary deploy 並排—— 而是把它直接掛在現有的 configuration system 後面當 mandatory gateway。 team 沒辦法繞過去走「快速通道」、 沒辦法用「我只是改一個 flag」當理由跳過 progressive rollout。 把工具設計成「不需要意志力就能做對的事」, 是這場 reliability 工程跟「請大家小心一點」最根本的差別。 這個判斷在 Code Orange 進到後半段時被多次驗證—— 任何「允許跳過」的設計,遲早會在某個下午有人因為 incident 壓力跳過、 然後變成新的 cultural norm。Mandatory gateway 不接受例外, 包括最 senior 的工程師、包括 CEO 的 dashboard, 都要走同一條 Snapstone path。

Snapstone 上線後的觀測點是 deployment cadence。 Hartman 給的具體數字:Workers runtime 系統在 2026 年 5 月初的 某個七天窗口內,觸發了 50+ 次 deployment。 對「configuration 進入 progressive 時代」的擔憂之一是 「分段釋放會不會慢到讓 deploy 變稀」—— 50 次 / 7 天 的節奏說明不會。 Progressive 是把每次 deploy 變得更安全,不是變得更貴。 這個觀察對任何「想推 progressive deploy 但擔心 velocity 倒退」的團隊 是個有用的 reference point——只要 health gate 的判斷是 automated, 人不需要在每一段 promotion 點等決策,cadence 就不會被綁死。

Snapstone 在實作上有個值得獨立記下的細節: 它支援「rollback target」這個欄位, 讓 unit owner 在註冊時就指定「壞了的話回到哪一版」。 最常見的選項是 last-known-good—— 系統自動追蹤最後一次成功跑完所有 cohort 的版本當 fallback。 這個設計對 11/18 那次的情境很關鍵: 壞 data file 在 canary 失敗時, automated rollback 不需要 incident channel 上有人裁決 「該回到哪一版」,系統直接回到 last-known-good, 把 mean-time-to-recover 從「等人類響應的分鐘級」 壓到「health gate 失敗即觸發的秒級」。

# Snapstone 註冊一個 configuration unit 的概念形狀
unit:
  name: bot-mgmt-classifier-data
  owner: bot-management-team
  type: data-file
  schedule:
    - cohort: canary-1pct
      hold: 5m
    - cohort: canary-10pct
      hold: 10m
    - cohort: canary-50pct
      hold: 15m
    - cohort: global
  health:
    - metric: edge_5xx_rate
      compare: canary_vs_baseline
      threshold: +0.5pp
      action: rollback
    - metric: ml_inference_p99
      compare: canary_vs_baseline
      threshold: +20pct
      action: rollback
  rollback:
    target: last-known-good
    auto: true

Engineering Codex:把事故經驗壓成 AI reviewer 在每個 PR 上 enforce 的 lint 規則

Snapstone 處理的是「release 過程」這一面; Codex 處理的是另一面——「進到 code 裡的寫法」這一面。 Hartman 的描述是:「the Codex 是一份在我們整個 codebase 內 透過 AI code review 強制執行的規則集」、 「適用範圍是 software development lifecycle 的每個階段, 從 design review 到 deployment 到 incident analysis」。 重點不在「我們有了 lint」, 而在「每條 rule 都是某次事故壓縮出來的 invariant」。

blog 裡只給了兩條代表性 rule,但這兩條已經能看出 Codex 的形式:

  • Rule A:「不要在 tests 與 build.rs 以外的地方使用 .unwrap()。」這條對應的是 11/18 那種 hot path panic——產品 code 裡的 .unwrap() 是「我假設這個 Option/Result 一定是 Some/Ok」的硬編碼,當 input 偏離預期就是 panic。Rule 不禁 .unwrap() 在測試環境用,因為測試碼 panic 等於 test fail,是想要的行為。
  • Rule B:「Services 必須驗證 upstream dependency 處於預期狀態後才能 process。」這條對應的是 12/5 那種 silent corruption——A 不檢查 B 的健康狀態、直接相信收到的資料。Rule 的反面是「任何從外部 service 收到的 response,都要 validate 狀態欄位後才能進入下游邏輯」。

形式上,Codex 的 rule 結構是「如果你需要 X,請用 Y」+ 連結到解釋 RFC。 「請用 Y」很關鍵——如果只給「不要 X」、 reviewer 與作者就會在 PR 上爭執替代方案; 給出 canonical 答案就把這個爭執消除掉。 這個微小的形式差異對 large codebase 的累積效益巨大—— 每條規則都帶著正確答案出場,就避免了 「reviewer 拒絕但沒人知道怎麼修」這種讓 PR 卡住的死局。 下面切換 tab 看三條代表性 rule 的全貌。

Rule A · 禁用 production .unwrap()

內容:不要在 tests 與 build.rs 以外的地方使用 .unwrap()。如果你需要展開 Option,請用 ok_or_else(...) 配合明確的 error type;如果你需要展開 Result,請用 ? 並讓 caller 決定 fallback。

起源:11/18 Bot Management 事故——ML classifier data 在 deserialize 時走到 unwrap,bad input → panic → 半網 routing 失能。

Rule B · 處理 upstream response 前要驗證狀態

內容:Services 必須驗證 upstream dependency 處於預期狀態後才能 process。如果你需要相信 upstream 的 response payload,請用該 service 提供的「狀態 + payload」雙欄位 envelope,先看狀態欄位、確認 healthy 才能消費 payload。

起源:12/5 control flag 事故——downstream service 收到 upstream 的 response 後沒檢查 health 欄位、直接取 payload,upstream 退化時 payload 是空集合,行為靜默偏離。

AI code review 怎麼 enforce

每條 rule 被翻譯成 AI reviewer 可以判斷的 pattern——對 Rule A 是 syntactic 偵測 .unwrap() 呼叫並排除 test path;對 Rule B 是 semantic 偵測「對某 service 的 client method 結果直接取欄位、跳過驗證」的呼叫鏈。任何 PR 觸發 AI flag 就強制走額外的 manual review。Hartman 的描述是「適用範圍沒有例外,覆蓋整個 codebase」——所有 team、所有語言、所有歷史 module。

關鍵是 enforcement 點:design review 階段審「方案是否會違反 rule」、code review 階段審「PR 是否引入違規」、incident analysis 階段審「事故是否暴露需要新 rule」——Codex 因此會增長,不是一次性 spec。

把 Codex 跟「lint 規則」並列比較會錯失重點。 傳統 lint 是 syntactic、對所有專案普適—— 它不知道 11/18 那次事故; 它的規則來源是「業界普遍認為 X 不好」這個泛化判斷。 Codex 的規則來源是 Cloudflare 自家事故—— 每條 rule 都有一個對應的 postmortem, 每條 rule 都有一個「上次出事是因為這個」的真實案例。 這讓 codex 的 leverage 跟團隊規模成正比: 團隊越大、新人越多、code change rate 越高, 把判斷力編譯進 reviewer 的 ROI 越高。 比起「把規則寫進 wiki」, 「把規則寫進 reviewer」對「下個月入職的工程師」是 first-class fail-small 機制。

AI reviewer 在這裡扮演的角色也值得單獨拆。 傳統 lint 用 AST + 規則匹配, 對 Rule A 這種 syntactic 偵測夠用—— 找 .unwrap() 呼叫、檢查路徑是否在 test 樹下; 但對 Rule B 這種 semantic 偵測就力不從心—— 「下游 service 是否驗證了 upstream 狀態」 需要理解 client method 的呼叫鏈、 需要區分「validation 在哪一層做」、 需要對 envelope 結構有 model。 這正是 LLM-based reviewer 比 AST-based linter 多出來的能力: 它能讀懂 code 在做什麼、能從 PR diff 推導 invariant 是否被打破, 不需要每條規則都被翻譯成 grammar pattern。

把 Codex 部署在 LLM-based reviewer 上的另一個好處是 規則本身用自然語言寫—— 「如果你需要 X,請用 Y」這種句式不需要翻譯成 DSL 才能執行。 這降低了 rule 進入門檻: 任何 postmortem 結尾的一句「我們應該……」 都能直接成為 candidate rule。 代價是 reviewer 的判斷會有 false positive 與 false negative—— Cloudflare 對此的應對是「flag 觸發強制走 manual review」, 把 LLM 的判斷當「需要人類複核」的信號、而非 final verdict。 這個 calibration 讓 AI reviewer 在 high-stakes 環境也能用。

Codex 與 Snapstone 的關係值得單獨指出: Snapstone 是 runtime 防線——它假設壞東西已經寫進 code 了, 盡量讓壞東西在 canary 階段被攔下; Codex 是 build-time 防線—— 它阻止那些已知的「會在 runtime 出事」的寫法 merge 進 main。 兩者疊起來才形成完整的 fail-small: build-time 阻止可預防的 bug、 runtime 收斂無法預防的 bug 的 blast radius。 任何一面單獨運作都不夠—— 只有 Snapstone 沒 Codex, 是「邊緣節點都掛了、但只掛 1%」; 只有 Codex 沒 Snapstone, 是「review 都 pass 了、但 reviewer 漏看的 bug 還是會 100% 全網爆」。

2026 年 4 月 7 日,Cloudflare 在 Code Orange 結束前 辦了一場 engineering-wide drill—— 200 多名工程師同時演練「break glass」流程。 Break glass 在 Cloudflare 語境裡的意思是: 當主 control plane 失能時 (dashboard 開不起來、deploy pipeline 中斷、observability 黑掉), 工程師仍要能對 production 採取必要動作。 這次 drill 涵蓋的 18 個關鍵服務 各自有 backup authorization 路徑—— 預設無法使用,但在 break-glass 條件下啟用—— 以及一組事先準備好的 emergency scripts 與 proxies, 能繞過正常 deploy path 直接到 fleet。

18 這個數字本身值得記下。 在 Cloudflare 這種規模的網路裡, 「關鍵服務」可能有上百個; 把 break-glass 限定到 18 個是個刻意的 scoping 動作—— 並非所有服務都需要 backup 授權路徑、 並非所有服務的故障都會升級成 incident response。 真正能讓 incident response team「在 control plane 黑掉時也能對 production 動手」 的最小集合,就是這 18 個。 對其他服務,break-glass 不適用、走正常 deploy path 就好。 這個取捨讓 drill 本身有可執行性—— 200 人演練 18 個 path 是合理範圍, 若是 100 條 path 就會散到沒人能 mentally model 完整流程。

這場 drill 在 Code Orange 故事裡的意義不在「驗證 break-glass 可用」, 而在「公司明確投資 fail-loud 這條」。 Fail small 是讓 blast radius 收斂; fail loud 是讓 failure 在發生的當下被清晰看見、被有效應對。 前者把壞事限制在 1%、 後者讓 incident response team 在那 1% 出問題時不會盲撞。 事後 Cloudflare 把 break-glass drill 變成 monthly cadence—— 不是 Code Orange 期間的特殊事件, 而是「reliability 文化」的日常組成。

Drill 變 monthly 這個決定對工程組的時間預算是真實 cost—— 200 人 × 幾小時 × 12 個月, 每年消耗的工程小時是 four-figure 起跳。 Cloudflare 願意付這個 cost, 說明對「break-glass 路徑必須保持可用」這個 invariant 的重視—— 類似消防演習, 平時的每一次演練都是顯式付出的 maintenance cost, 換來「真的失火時,路徑沒鏽掉」的 confidence。 這個比喻在 reliability 工程裡常被引用, 但實際把它寫進 calendar 而不是停在 best-practice document 的團隊很少。

Code Orange 收口的另一個 communication 層改動是: incident 期間每 30 至 60 分鐘給一次 status update。 這個節奏聽起來瑣碎, 但在過去常見的情況是「incident channel 一個小時沒消息、 客戶不知道你還在處理還是已經放棄」。 30-60 分鐘的固定 cadence 配上「不管有沒有新進展都要發」的紀律, 把「沒消息」這個信號從「公司是不是放棄了」改成「公司還在按節奏處理」。 專門的 communications team「跟 incident responder 同步」也是新的—— 之前 incident responder 自己一邊救火一邊發 status, 現在拆成兩個角色。

這個 communication 拆分的隱含設計判斷是: incident responder 在 cognitive 負荷高峰時不應該還要做 user-facing 措辭, 這兩個任務需要的腦力是不同 mode 的。 把「對外發 status」交給一個只專注 communication 的 role, 讓 responder 全力處理技術側、 讓 communicator 全力處理「客戶現在最想知道什麼」—— 這個分工讓兩邊都做得更好。 很多公司在 incident 響應上沒做這個切分, 結果就是 status update 寫得草率、用詞模糊、 或乾脆延後到 incident 結束才補一份事後報告。

把 Code Orange 收口當教材:對任何 reliability 動員的方法論啟示

把 Snapstone、Codex、break-glass drill、 status update cadence 這幾條疊起來看, Code Orange 的真正交付物不是「修了幾個 bug」、 而是「把『fail small』從口號變成 default 行為」這件事。 Hartman 的描述是「earlier this month, the Cloudflare team finished this work」—— 這個「finished」不是「reliability 問題解決了」、 而是「我們不再需要 emergency-level 投入來推這件事, 現在這些工具與 cadence 是 baseline」。 下一次事故仍會發生(reliability 工程沒有「完成」狀態), 但下一次事故的 shape 應該不再是 11/18 / 12/5 那種 「bad input 一推全網爆」—— 這個 specific failure mode 已經被工具堵住。

對讀這篇 post 的工程師,這場 reliability 工程值得學的 不是「我們也要做一個 Snapstone」 (這個工具的價值跟 Cloudflare 自家的 deployment scale 強耦合), 而是「reliability 不能靠『請大家小心』收口」這個方法論判斷。 任何一個團隊只要還在用 wiki page、 weekly tech talk、code review 邊角討論 這種方式傳遞 reliability 知識, 就一定會在某個下午被新人不小心 merge 出一個 panic。 把判斷力編譯進工具—— 不論是 progressive deploy 系統、AI reviewer、CI gate 或 static analyzer—— 是讓 reliability 跟團隊規模解耦的唯一辦法。

另一個值得學的觀察是 Code Orange 在 11/18 與 12/5 之後才真正加速。 那兩起事故是 disconfirming evidence—— 說明前 5 個月以教育為主的路線不夠用—— 但 Cloudflare 沒有把這個證據解讀成「Code Orange 失敗了」, 而是解讀成「方法要從『教育』升級成『工具強制』」。 對任何長期 initiative 來說, 能在 mid-stream 收到 disconfirming evidence 並調整方向, 比能執行 12 個月不偏離原計畫更難、也更有價值—— 後者經常意味著沒有人在認真量測 initiative 是否真的有效。

這個「中途調整方向」的能力在組織層面有具體支撐物—— Code Orange 之所以能加速, 是因為它本來就在 emergency posture 下運作: 優先級已經被宣告、預算已經被批准、 人員已經被借調,調整方向不需要重新走立項流程。 對 BAU 模式運作的 reliability 工程, 11/18 那種 signal 通常會被「我們已經有 Q4 計畫了、明年再改」吃掉。 Code Orange 這個機制本身就是為了 讓 disconfirming evidence 可以即時反映在資源分配上而存在的。

最後一個觀察:Hartman 的結束公告沒有給出 可量化的「事故減少 X%」這類成果指標。 這在 reliability 領域是合理的—— 事故的基率本來就低,11 個月窗口太短、 無法以統計方法宣告「我們現在更可靠」。 Cloudflare 選擇用「過程交付物」當收口證據 (Snapstone 預設啟用、Codex 跑在每個 PR 上、break-glass drill 變 monthly)—— 這比「事故率下降 30%」這種容易被偶然事件污染的數字更誠實。 承認 reliability 是個沒有完成狀態的工程, 比假裝有完成狀態更可信。

對 senior engineer 與 EM, 這篇 post 的可帶走清單是這樣: 第一,下次 incident postmortem 寫完之後, 要把 root cause 抽象成「未被 enforce 的 invariant」、 再評估這條 invariant 是否能被機器化檢查; 第二,任何 deploy / config change / data publish 的路徑 若沒有 progressive + automated rollback, 就是潛在的 11/18 / 12/5; 第三,break-glass 路徑必須被定期演練, 不演練的 break-glass 等於沒有 break-glass; 第四,reliability 動員若以「請大家小心」為主軸, 在 6 個月後一定會收到 disconfirming evidence—— 準備好那一天的 escalation path。

So what:當你的團隊下一次喊「我們要更可靠」時, 問一句——你準備拆哪一條工具鏈、寫進哪一條 default 行為, 讓「個人小心」不再是這條 reliability 的瓶頸? 答不出來的話,這場 reliability 動員大概率會在六個月後 變成另一份未被讀過的 wiki page。