切換之前,先達成一致
我們維運著一項被多個團隊與服務依賴的服務。多年來,它以兩種形式並存:正式環境的版本使用 Python 2,部署在地端伺服器上,而現代化的 Python 3 版本則存在於另一個獨立的分支上,等待雲端遷移完成後才正式取代舊版。
許多舊系統的升級都是如此。最終狀態顯而易見——新的語言版本、新的基礎架構、舊系統退役——因此團隊便直奔終點。工作在一個長期分支或所謂的「v2」重寫中進行,計畫是在遠離正式環境的地方完成艱難的工作,最後再一次切換完成。
遲遲無法落地的重寫
這樣的計畫很少能兌現承諾。當新版本遲遲未發布時,風險並不會消失,只會不斷累積。
每一次緊急修復都還是得先上到舊版本,然後再移植到新版本——否則就會被遺漏。兩者之間每一個行為上的差異,無論有意還是無意,都是等待切換後被下游團隊發現的錯誤,而那正是最難追溯問題根源的時刻。
長期分支感覺上很有效率,因為它把所有事情打包成一次事件。但實際上,它只是把風險集中在單一時間點,並延後了回饋,而這些回饋本可提早告訴你哪裡出了問題。
以退為進,才能向前交付
當我們接手這次遷移時,直覺是直接衝向最終狀態:讓現代化版本在雲端上運行,並開始將取用端遷移過去。我主張先往後退一步:先讓現代化版本在舊的基礎架構上於正式環境中運行,這樣在遷移其他任何東西之前,舊版與新版就已是同一份程式碼。
舊有主機所運行的作業系統相當老舊,對 Python 的支援最高只到 3.6,而現代化版本則是以 3.7 為目標。我們本可以繞過這個限制——從原始碼自行編譯 Python,或在原本並非為此設計的主機上疊加容器——但每一個變通方案都會為這場本身就已相當複雜的遷移增加額外的複雜度。
因此,我們反而將服務降版至 3.6,並恢復地端的部署設定,讓設定值在執行時期依環境自動選用。同一份程式碼庫,可同時部署到舊有主機與 Kubernetes。
版本降版看起來像是開倒車。正是這個決定,讓後續的遷移變得單純。
一次只變動一個變數
在達成一致後,每一次發布都會同時部署到兩個環境。舊版與新版之間行為上的差異會立刻以小幅增量的方式浮現,而不是等到切換時才一次全部爆發。兩個版本之間多年累積的分歧,就在舊基礎架構仍可作為備援的期間逐步弭平。
到我們切換至雲端時,那已經是一個純粹的基礎架構變更。我們不必同時追問「平台遷移成功了嗎?」以及「重寫成功了嗎?」——如果出了問題,我們知道問題一定出在平台,因為程式碼是完全相同的。切換過程平淡無奇,而平淡正是我們想要的。這與透過 micro-frontends(微前端) 漸進式升級 React背後的原則相同:將變數依序排列,讓每一次發布只改變一件事。
交付的是路徑,而非終點
遷移完成後,卡關多年的 Python 升級便作為一次例行發布順利推出——舊主機的版本上限已不復存在,而且只需要升級一份程式碼庫。最終狀態反而更快到來,正因為我們不再直奔終點。
如果一個分支已經開了好幾個月,或是一次重寫正等待其他團隊先完成遷移,那通常代表這項變更試圖一次做太多事。試著尋找一個現在就能發布的中間狀態,即使它看起來像是倒退了一步。終點從來不是最困難的部分——最困難的是路徑,而最安全的路徑,就是能讓你一次只發布一步的路徑。
隨機一篇部落格