Continuous Delivery#

為什麼速度決定一切#

在快速變動的技術環境中,產品的競爭優勢取決於其上市速度。組織的 velocity 是維持產品品質、適應新法規、以及與競爭對手抗衡的關鍵因素,而這一切的瓶頸在於 部署所需的時間。部署不是只發生在初次發布——正如教育界的一句話:「沒有任何教案能在第一次面對學生時存活。」軟體也是如此,首次發布後唯一可以確定的是你必須不斷更新它,而且要快。

軟體產品的長期生命週期涉及快速探索新想法、快速回應市場變化或使用者問題、以及在規模化下維持開發者速度。從 Eric Raymond 的《The Cathedral and the Bazaar》到 Eric Ries 的《The Lean Startup》,組織長期成功的關鍵始終在於盡快將想法執行並交到使用者手中,然後快速回應回饋。Martin Fowler 在《Continuous Delivery》中指出:

任何軟體專案最大的風險,是你最終做出了沒用的東西。越早、越頻繁地將可運作的軟體交到真實使用者手中,就能越快得到回饋,了解它真正的價值。

長時間處於進行中狀態卻遲遲未交付使用者價值的工作,是高風險、高成本的,甚至可能侵蝕團隊士氣。在 Google,團隊奉行 「盡早發布、頻繁發布」(launch and iterate) 的策略。程式碼的價值不是在提交時實現,而是在功能交付給使用者的那一刻。縮短「程式碼完成」到「使用者回饋」之間的時間,可以最大限度降低半成品工作的成本。

發布從來不是終點,而是學習循環的起點:修正最重要的問題、衡量結果、再修正下一個——永遠不會「完成」。——David Weekly,前 Google 產品經理

在 Google,本書描述的實踐方法讓數百(甚至數千)名工程師能夠快速排除問題、獨立開發新功能而不必擔心發布排程,以及透過 A/B 實驗了解新功能的成效。本章聚焦於快速創新的關鍵槓桿:管理風險、在規模化下維持開發者速度,以及理解每個功能發布時的成本與價值取捨。

Continuous Delivery 在 Google 的核心理念#

Continuous Delivery(CD)與 Agile 方法論的核心信條是:隨著時間推移,更小批量的變更會帶來更高的品質——換句話說,更快就是更安全(faster is safer)。這對許多團隊來說乍聽之下似乎極具爭議,尤其是當 CD 的前提條件(例如 CI 和測試)尚未到位時。因為不是所有團隊都能立刻實現 CD 的理想狀態,Google 著重在逐步發展各個面向,使它們能獨立帶來價值。

以下是 Google 在實踐 CD 時逐步培養的關鍵面向:

  • Agility(敏捷):頻繁發布,且每次只包含小批量變更
  • Automation(自動化):減少或消除頻繁發布帶來的重複性開銷
  • Isolation(隔離):追求模組化架構,隔離變更並簡化問題排查
  • Reliability(可靠性):監測 crash 率、延遲等關鍵健康指標並持續改善
  • Data-driven decision making(資料驅動決策):透過 A/B 測試確保品質
  • Phased rollout(分階段推出):先向少數使用者推出,再逐步擴展至全體

頻繁發布乍看之下風險更高,但 CD 的核心價值正在於此:每次發布之間的變更極少,問題排查變得輕而易舉。理想情況下,每個變更都經過 QA pipeline 並自動部署到 production。即使團隊尚未準備好實際持續部署,也可以先建立「隨時可以部署」的能力,藉此累積信心,為未來更頻繁的發布做準備。

速度是團隊運動:拆分部署的策略#

當團隊規模較小時,變更以一定速率進入 codebase。但隨著團隊成長或分裂為子團隊,常見的反模式(antipattern)就會出現:子團隊分支出自己的程式碼以避免踩到彼此的腳,最終卻在整合與 culprit finding 上付出慘痛代價。

Google 的做法是讓團隊持續在共享 codebase 的 head 上開發,並搭配 CI 測試、自動 rollback 與 culprit finding 機制來快速識別問題(詳見第 23 章)。

YouTube 的教訓#

YouTube 是一個龐大的 monolithic Python 應用程式。其發布流程繁重,需要 Build Cops、release managers 和其他志工參與。幾乎每次發布都有多次 cherry-pick 和重新構建(respin),外加一個由遠端 QA 團隊執行、長達 50 小時的手動回歸測試週期。

當發布的操作成本如此之高時,惡性循環就會形成:你等著多測試一些才推出更新,同時又有人想加入「快完成了」的新功能,最終發布流程變得既緩慢又容易出錯。更糟的是,上次負責發布的專家已經精疲力竭離開團隊,沒人知道如何處理那些奇怪的 crash,光是想到要按下那個發布按鈕就讓人恐慌。

當發布成本高且具有風險時,直覺反應是降低發布頻率、延長穩定期。但這只會帶來短期穩定,長期則拖慢速度、挫傷團隊與使用者。正確的做法是降低成本、提高紀律、將風險增量化。必須抵抗那些顯而易見的操作性修補,轉而投資長期的架構改變。

那些顯而易見的操作性修補往往導向幾種傳統做法:

  • 回歸到缺乏學習與迭代空間的傳統計畫模型
  • 為開發流程增加更多治理和監督層級
  • 實施風險審查並獎勵低風險(通常也是低價值)的功能

最佳投資回報的策略是遷移到 微服務架構(microservice architecture),讓大型產品團隊在保持創新的同時降低風險。在某些情況下,Google 選擇從頭重寫應用程式而非單純遷移,將模組化設計融入新架構中。雖然這些做法短期內可能耗時數月且過程痛苦,但在應用程式數年的生命週期中,操作成本和認知簡化帶來的價值將遠超初始投入。

隔離評估變更:Feature Flag#

可靠的持續發布仰賴工程師對所有變更進行 flag guard。隨著產品成長,一個 binary 中會有多個處於不同開發階段的功能共存。Flag guard 可以逐功能控制程式碼在產品中的包含或表現,並且可以針對 release build 和 development build 有不同的表現。

Feature flag 的運作方式:

  1. 新功能程式碼與舊程式碼路徑共存於同一個 binary 中——兩者都可以執行,但新程式碼受 flag 保護
  2. 新程式碼在開發構建中啟用,在正式構建中停用,保護使用者免於接觸未完成的功能
  3. 已經穩定並交付給客戶的功能,可以同時在開發和正式構建中啟用
  4. 如果語言支援,被停用的 flag 應允許構建工具在構建時剝離該功能的程式碼
  5. 如果新程式碼運作良好,後續版本移除舊路徑並完整推出功能
  6. 如果出現問題,可以透過動態設定更新(獨立於 binary 發布)關閉 flag

Feature flag 帶來的核心能力是 將特定功能的命運從整體產品發布中解耦,這對應用程式的長期永續性是強大的槓桿。

Flag 與公關時機同步#

在傳統的 binary 發布模式中,新聞稿必須與 binary rollout 緊密配合——必須在 rollout 成功後才能發布消息,這意味著功能在正式宣布前就已經在外面,提前被發現的風險非常真實。

有了 flag guard,可以在新聞稿發布前一刻開啟功能,大幅降低功能提前曝光的風險。

Flag 並非萬能的安全網。程式碼仍可能被爬取和分析(如果沒有良好的混淆),且不是所有功能都能在不增加大量複雜度的情況下藏在 flag 後面。此外,flag 設定變更本身也需謹慎推出——一次對 100% 使用者開啟 flag 並不是好主意,投資一個管理安全 configuration rollout 的服務是值得的。

追求敏捷:建立 Release Train#

Google Search binary 是最早也最複雜的產品之一。其 codebase 可以追溯到 Google 的起源,搜尋 codebase 仍能找到至少 2003 年甚至更早寫的程式碼。隨著智慧型手機興起,一個又一個行動端功能被硬塞進原本為伺服器部署設計的「毛線球」程式碼中。儘管搜尋體驗越來越豐富和互動,部署一個可行的構建卻越來越困難。一度,Search binary 每週只發布一次到 production,且經常達不到這個目標,能否成功往往靠運氣。

當時每個發布週期需要一組工程師花費數天完成。他們構建 binary、整合資料、然後開始測試。每個 bug 都必須手動分類,確保不會影響搜尋品質、使用者體驗(UX)和/或收入。這個過程繁瑣且耗時,無法隨變更的量和速度擴展。結果是開發者永遠不知道自己的功能何時會被發布到 production,讓新聞稿和公開發布的時間安排充滿挑戰。

發布不在真空中發生,可靠的發布讓相依因素更容易同步。經過數年的改進,專門的工程師團隊建立了持續發布流程——自動化能自動化的部分、為功能提交設定截止日期、簡化 plugin 和資料整合到 binary 的過程——將 Search binary 的發布頻率提升至 每隔一天一次

背後的核心取捨歸結為兩個原則:

沒有完美的 Binary#

對於整合數十甚至數百位開發者獨立開發數十項主要功能的構建而言,不可能修復每一個 bug。團隊需要不斷權衡:某行文字左移兩個像素是否會影響廣告顯示和潛在收入?某個框的色調微調是否會讓視障使用者難以閱讀?

關鍵績效指標(KPI)搭配明確閾值 可以讓功能在不完美的情況下仍能發布,也能為爭議性的發布決策提供客觀依據。

書中分享了一個故事:有個 bug 涉及菲律賓某座島嶼上的一種罕見方言。如果使用者用這種方言搜尋,會得到空白頁面而非答案。團隊奔走於各辦公室間,試圖確認有多少人使用這種語言、是否每次都會觸發、這些人是否經常使用 Google。最終數據到手後,問題被提交給搜尋部門的資深副總裁——結果是:無論你的島嶼多小,都應該得到可靠準確的搜尋結果。 發布被延遲,bug 被修復。

參照 SRE 的 error budget 概念:完美很少是最佳目標。理解可接受的錯誤空間有多大、近期已消耗多少預算,以此調整速度與穩定性之間的取捨。

趕上你的發布截止時間#

「如果你遲到了,release train 不會等你。」正如一句諺語所說:「截止日期是確定的,人生不是。」在發布時程的某個節點,團隊必須劃下界線,拒絕開發者和他們的新功能。一般來說,無論如何懇求,截止後都無法再將功能塞入當次發布。

書中描述了一個典型的例外場景:週五深夜,六位軟體工程師衝進 release manager 的隔間,慌張地說他們與 NBA 簽了合約,功能剛完成,但必須在明天的大賽前上線。Release engineer 搖頭說重新切割和測試一個新 binary 要四個小時,而且今天是他小孩的生日,他還得去拿氣球。

頻繁的定期發布意味著錯過一班車的代價很小——開發者可以在數小時(而非數天)後搭上下一班。這大幅減少了開發者的恐慌,也極大改善了 release engineer 的工作與生活平衡。

品質與使用者導向:只交付真正被使用的功能#

軟體膨脹(bloat)是大多數開發生命週期中不幸的副作用,產品越成功,codebase 通常就越臃腫。高效 release train 的一個缺點是這種膨脹往往被放大。

對於 client-side 應用(如行動 App),使用者的裝置要為從未使用的功能支付儲存空間、下載流量和資料成本;開發者則承受更慢的構建、更複雜的部署和罕見的 bug。部分產品是 web-based 且運行在雲端,但許多是使用使用者裝置共享資源的 client 應用。這本身就展現了一個取捨:原生 App 可以更高效且更耐受不穩定的網路連線,但也更難更新且更容易受到平台層級問題的影響。

反對頻繁部署原生 App 的常見論點是使用者不喜歡頻繁更新,且必須為資料成本和中斷付費。此外還可能有其他限制因素,例如網路存取的可用性或更新所需重啟次數的上限。

即使在更新頻率上需要取捨,目標是讓這些選擇是有意識的。有了順暢運作的 CD 流程,可以部署的頻率使用者實際收到更新的頻率可以分離。你可以達成每週、每天甚至每小時部署的能力,但不一定要真的這麼做。應在使用者的特定需求和更大的組織目標的背景下,有意識地選擇發布流程,並決定最能支持產品長期永續性的人力配置和工具模型。

解決膨脹之道在於 動態部署(dynamic deployment)

  • 搭配模組化架構,讓 App 維持小體積
  • 只將對該使用者有價值的程式碼送達其裝置(不再為不需要的翻譯或非該裝置架構的程式碼買單)
  • 透過 A/B 實驗在功能成本與使用者價值之間做出有意識的取捨

在 Google,這通常意味著配置專門的團隊來持續改善產品的效率。建立這些流程有前期成本,識別並消除阻礙發布頻率提升的摩擦是一個費力的過程。但長期在風險管理、開發者速度和快速創新方面的回報,使這些初始投資變得非常值得。

左移決策:更早做出資料驅動的決定#

如果你的產品面向所有使用者,客戶端可能包括智慧螢幕、智慧音箱、Android 和 iOS 手機與平板,你的軟體可能還需要足夠的彈性讓使用者自訂體驗。即使只針對 Android,超過二十億台 Android 裝置的多樣性也足以讓發布驗證的前景令人望而卻步。而且隨著創新的步伐,等到有人讀到這一章時,可能已經冒出了全新的裝置品類。

一位 release manager 分享的智慧扭轉了局面:客戶端市場的多樣性不是一個問題,而是一個事實。接受了這一點之後,發布驗證模型可以做出以下轉變:

  • 代表性測試取代全面測試:既然全面測試不可行,就瞄準具代表性的測試覆蓋
  • 分階段 rollout:逐步增加使用者比例,允許快速修復
  • 自動化 A/B 發布:透過統計顯著性結果驗證發布品質,無需疲憊的人類盯著 dashboard 做決策

在 Android 部署方面,Google 的 App 使用專門的 testing track 和逐步增加使用者流量比例的分階段 rollout,仔細監控這些通道中的問題。因為 Play Store 提供無限的 testing track,團隊可以在每個計畫發布的國家設立 QA 團隊,實現關鍵功能的全球隔夜測試循環。

A/B 測試部署本身#

團隊注意到一個現象:僅僅推送更新本身就會導致使用者指標出現統計上的顯著變化。即使產品沒有任何改變,推送更新也可能以難以預測的方式影響裝置和使用者行為。因此,雖然 canary 部署到小比例使用者流量能提供 crash 和穩定性問題的資訊,但它無法告訴我們新版本是否真的優於舊版本。

Google 的部分大型 App 因此對 部署本身 做 A/B 測試——同時推出兩個版本:

  • 實驗組:包含目標更新的新版本
  • 對照組(placebo):再次推送舊版本

透過對足夠多的同質使用者同時 rollout,可以比較兩個版本,在數天甚至數小時內得到統計顯著的結果,判斷最新版本是否確實優於前一版。自動化的 metrics pipeline 可以在收集到足夠資料確認 guardrail metrics 不受影響後,立即推進 release 到更多流量,實現最快的發布速度。

對於使用者量體不夠大的應用,建議的最佳做法是追求 change-neutral release:所有新功能都以 flag 保護,rollout 時唯一測試的是部署本身的穩定性。

改變團隊文化:在部署中建立紀律#

雖然「Always Be Deploying」有助於解決影響開發者速度的多項問題,但也有某些實踐專門針對規模化帶來的挑戰。最初發布產品的團隊可能不到十人,每個人輪流負責部署和 production 監控。隨著時間推移,團隊可能成長到數百人,子團隊各自負責特定功能。隨之而來的是每次部署中的變更數量和風險都在增加。

隨著組織規模擴大,每次部署中的變更數量和風險呈 超線性增長(superlinearly)。每次發布包含數月的心血。讓發布成功變成了高接觸度、勞力密集的工作。開發者經常面對一個艱難的抉擇:

  • 放棄一個包含一整季新功能和 bug 修復的版本
  • 或者推出一個對品質沒有信心的版本

在規模化階段,複雜度增加通常表現為 release latency 增加。即使你每天發布,一個 release 可能需要一週或更長時間才能安全地完全 rollout,這意味著在除錯時你落後了一週。這正是「Always Be Deploying」能讓開發專案回到高效狀態的地方。頻繁的 release train 使得偏離已知良好狀態的幅度最小化,變更的新近性有助於快速解決問題。

在 Google Maps 團隊的觀點中:功能很重要,但很少有功能重要到需要為它延遲一次發布。 如果發布頻率夠高,錯過一次發布帶給某個功能的痛苦,遠小於延遲帶給所有新功能和使用者的痛苦——尤其是當一個尚未準備好的功能被倉促趕入發布時,使用者所承受的痛苦。

發布的職責之一是保護產品不受開發者的影響。做取捨時,開發者對新功能的熱情和急迫感,絕不能凌駕於使用者對既有產品的體驗之上。新功能必須透過強合約的介面與其他元件隔離、關注點分離、嚴格測試、及早且頻繁的溝通,以及新功能驗收的慣例。

結論#

跨越 Google 所有軟體產品,多年經驗告訴我們一個反直覺的結論:更快就是更安全(faster is safer)

產品健康度與開發速度並不對立,更頻繁、更小批量發布的產品擁有更好的品質成果。它們能更快適應線上 bug 和意外的市場變化。

不僅如此,更快也更便宜(faster is cheaper)——可預測且頻繁的 release train 會迫使你降低每次發布的成本,並使任何被放棄的發布損失極低。

光是建立起持續部署的基礎設施就能產生大部分價值,即使你實際上並不每天推送更新。Google 並非每天都發布截然不同的 Search、Maps 或 YouTube 版本,但要具備這樣的能力需要:

  • 強健且文件完善的持續部署流程
  • 即時且準確的使用者滿意度與產品健康指標
  • 一個有明確策略的協調團隊,對什麼能進、什麼不能進以及原因有清晰的政策
  • 可在 production 中配置的 binary
  • 以程式碼方式管理的 configuration(納入版本控制)
  • 支援 dry-run 驗證、rollback/rollforward 機制與可靠 patching 的工具鏈

Google 並非實際上每天都推送截然不同的版本給使用者,但建立起「能夠這麼做」的能力本身就是最大的價值所在。這種能力要求的不只是技術基礎設施,更包括團隊文化、流程紀律與明確的決策政策。

TL;DRs#

  • 速度是團隊運動:大型協作團隊的最佳工作流程需要架構模組化與近乎持續的整合
  • 隔離評估變更:對功能進行 flag guard,以便及早隔離問題
  • 以現實為基準:使用分階段 rollout 來應對裝置多樣性和使用者群體的廣度;在與 production 環境不相似的合成環境中做發布驗證可能帶來意外
  • 只交付被使用的功能:監控每個功能在線上的成本與價值,確認它是否仍然相關且提供足夠的使用者價值
  • 左移決策:透過 CI 和持續部署,對所有變更更早做出更快、更資料驅動的決策
  • 更快就是更安全:盡早、頻繁且小批量地交付,以降低每次發布的風險並縮短上市時間