Parity before cutover

Alex O'Callaghan

切換前,先求一致

原文由 Alex O'Callaghan 發布,訂閱此部落格

我們維運著一個被好幾個團隊與服務依賴的服務。多年來它以兩種形式並存:正式環境中的 Python 2 版本,部署在地端伺服器上,以及現代化後的 Python 3 版本,放在另一個分支上,等待雲端遷移完成後才取代原版。

許多老舊系統的升級都是如此。最終目標顯而易見——新的語言版本、新的基礎架構、舊系統退場——因此團隊往往直奔終點。工作在一個長期存在的分支或「v2」重寫專案上進行,計畫是在遠離正式環境的地方完成艱難的工作,最後再一次性切換。

遲遲無法落地的重寫

這個計畫很少能兌現承諾。新版本擱置未發佈期間,風險並不會消失,只會不斷累積。

每一個緊急修正都還是得先上到舊版本,再移植到新版本——否則就會被遺漏。兩者之間每一個行為上的差異,無論是有意還是無意,都是等著在切換後被下游團隊發現的 bug,而那正是最難追溯歸因的時候。

長期存在的分支看似有效率,因為它把所有事情打包成單一事件。但實際上,它是把風險集中在同一個時間點,並延後了回饋,而那些回饋本可以及早告訴你哪裡出錯了。

為了前進,先退一步

當我們接手這次遷移時,直覺是直接衝向終點:讓現代化版本在雲端上跑起來,然後開始把呼叫方遷移過去。我主張先退一步:先讓現代化版本在舊的基礎架構上於正式環境運行,這樣在其他任何東西搬動之前,新舊兩邊就是同一份程式碼。

舊主機上的作業系統已經老舊到 Python 支援最高只到 3.6,而現代化版本目標是 3.7。我們本可以繞過去——從原始碼編譯 Python、在原本就不是為容器設計的主機上疊加容器——但每一個繞道方案都會為這場本已夠複雜的遷移再增加複雜度。

因此,我們把服務降版到 3.6,並還原了地端的部署設定,依執行環境在運行時選擇對應的參數。一份程式碼,同時可部署到舊主機與 Kubernetes。

版本降級看起來像是開倒車。卻正是它,讓後續的遷移變得單純。

一次只動一個變數

有了對等一致的基礎後,每次發佈都會同時部署到兩個環境。新舊之間的行為差異會立刻、以小幅增量的方式浮現,而不是等到切換時才一次爆發。兩個版本之間多年累積的分歧,就在舊基礎架構仍可作為備援的期間逐步償還。

等到我們真正切換到雲端時,那已經是一個純粹的基礎架構變更。我們不必同時追問「平台遷移成功了嗎?」和「重寫成功了嗎?」——如果出了問題,我們知道一定是平台的問題,因為程式碼完全相同。切換過程平淡無奇,而平淡正是你想要的。這背後的原則,與透過微前端逐步升級 React相同:將變數排好順序,讓每個版本只改變一件事。

codeinfrastructureversionbig bang: everything at oncePython 2on-premPython 3.6on-premPython 3.6KubernetesPython 3.10Kubernetes

交付的是路徑,而非終點

遷移完成後,那個卡了多年的 Python 升級,作為一次例行發佈就順利上線了——舊主機的版本天花板已不存在,而且只需要升級一份程式碼。終點反而更快到來,正因為我們不再直瞄著它衝刺。

如果一個分支已經開了好幾個月,或是一次重寫正等待其他團隊遷移,那通常代表這次變更想一次性做太多事。試著找出一個現在就能發佈的中間狀態,即使它看起來像是退了一步。終點從來不是困難之處——困難的是路徑,而最安全的路徑,就是能讓你一次只發佈一步的路徑。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言