升級 Python3 (下) — 部署策略

Rilak, Zhou
iCHEF
Published in
8 min readDec 28, 2021

延續上集的內容,通過了所有的單元測試後,我們接著面對的問題是要怎麼 Deploy 到 Production 環境?在無法避免 bug 的情況下,要如何降低客戶損失以及客訴進線癱瘓營運與客服能量的風險?

把整個流程分成六個部分,這篇文章講的是 4~6,1~3 在上集。

  1. [Done] 決定 Py3 版本與升級套件。
  2. [Done] 建置 Py3 CI,並讓 Code 可以執行測試 (不用通過測試)。
  3. [Done] 在 Py3 分次通過所有的單元測試。
  4. 測試與部署 Py3 service。
  5. 使用 Load Balancer (AWS ALB) 分次切換流量上版。
  6. 確認流量都導向 Py3 後,關閉 Py2 service。

嘗試與錯誤

一開始我想擴大測試的範圍,於是嘗試了幾種方法。

補齊單元測試

前一篇文有提過,幫前人的 Code 補測試,或者想要補到 100% 幾乎是不可能的事情,成本過於高昂。

跪下來請品質工程師 (或全公司) 一次測完

當時公司固定每兩週發一個新版本,也許就挑個良辰吉日,服務全部改成 Python3 執行,然後請整個團隊一起投入測試。
除了最大的人力問題,如果被驗到很嚴重的錯誤,修不完就要退回 Python2 再讓品質工程師重測一次,很可能會影響到本來的 Release 流程,修完又要從頭再來,太勞師動眾了。

不、不然我自己測?

我:「可以讓我看以前 Project 要驗的項目記錄嗎?我想在 Py3 Service 上自己測過一次。」

後來自己勸退自己了。為什麼我當時會說出這種話呢?

整合性自動化測試

每個功能除了後端自己的單元測試,品質工程師也會寫自動化測試,透過腳本實際執行 App 和 Web 並驗證結果。
所以我建立了一個 Python3 Server 並請他們在上面執行自動化測試。過程需要的工時主要都在 set up 測試資料:因為腳本需要固定的帳號資料,從其他資料庫複製資料花了一點點功夫 (後來才想到直接複製資料庫最方便,在 AWS RDS 上只要按幾下按鈕就好了…),當然品質工程師也要微調腳本的內容。

錯誤的數量比團隊預期的少,也很快地被修復了。

第一次確認了 Py3 service 可以平安健康的執行整套基本服務,信心++,但還不足以說服我們一次直上 Py3。

最後的防守: 切流量

後來在每週的團隊會議上同事想到了解法:用 ALB 按照服務切流量。

ALB 簡介

下圖是 AWS ALB (Application Load Balancer) 示意圖。

Image source: AWS Application Load balancer

所有的 request 進來都會先經過 ALB ,根據設定的規則,按照 url, path, headers 等等資訊來判斷要轉交給哪組 service 來處理這個 request。

每組 service 都由很多相同的 task 組成,方便針對不同種類的 request 做不一樣的設定
AWS ALB 後台規則設定頁面

建立 Py3 Service

接下來我們為每一種 service 建立 Py3 版本的 service,例如有 invoice service 就建立 py3_invoice service。
每個新版本我們都用 ALB 規則把部分 API 流量切新的 Py3 service 上,反覆直到所有 API 都到 Py3 service 後就把舊的 service 刪除。

避免驗證範圍過大,我們會保守一些只切出部分 API 到 py3 service。

另外為了方便驗證和除錯,我們不會只切部分比例流量 (例如 30% 到舊服務,70% 到新服務),而是用 API 路徑來切分,發生錯誤時才能迅速確定「是不是因為 Py3 導致的錯誤」。

開工

建立 Py3 service 與修改 ALB 規則

為了在測試環境、驗證環境、正式環境執行相同的修改,我們會寫 Cloudformation 腳本並發 Pull Request 請同事 review。

切流量與測試切流量

每個版本我們都多加一些新 Rule 把流量切過去 Py3 service,並且遵守測試原則 每個版本依序執行:

  • 確認下個 (下下個、下下下個版本) 要切到 Py3 的 API 有哪些,發 PR 修改腳本並請同事 Review。
  • 開卡記錄並通知測試工程師要切到 Py3 的 API 有哪些,請他們注意自動化測試結果,並討論是否需要額外手動驗證,確保不會有嚴重的錯誤。
  • 透過腳本依序更新測試環境 (testing)、驗證環境 (RC)、正式環境的 ALB。
  • 如果發生錯誤,手動把發生錯誤的 API 流量切回 Py2 service,並開始修復,(隔一兩個版本) 修復完成後把流量再切到 Py3 service。

切流量的考量

每個版本要切哪些 API 都要經過討論,基本上可以把切 API 當作進新功能:

  • 相近的 API 一起進,方便確定測試內容以及通知大家注意範圍。
  • 關鍵的服務不要同時進,降低災難風險。
  • 如果其他團隊要上功能,應該避開相近的 API。如果是重大功能可以考慮跳過一版不切任何 API。

我們的順序大略分成: 網頁下載報表、網頁設定 API、iPad API、GraphQL (這個真的不太好細切)、Celery。

漫長的旅程

可以看得出來這些工作不難、量也不大,但時間會拖的很長,並且有不同版本要同時處理的狀況:例如某一週要發 PR 寫 2.123 的 ALB rule,要更新測試環境 ALB 到 2.122,要更新驗證環境 ALB 到 2.121,要更新正式環境 ALB 到 2.120…。

每週都要跟團隊溝通哪些 API 會搬到 Py3

也要非常感謝品質工程師的努力,在人力與自動化的測試能量都越來越充足的前提下,不斷縮短版本週期,每週穩定釋出一個版本。我們才能一直切 API 上 Py3,一路從 2.69 拆到 2.77,用了 9 個版本、2個多月順利完成 (後來又花了 3 個版本把 Py2 service 拆光光。)

終究逃不過的 Bug

這個方案最大的優點是出錯時可以馬上切回 Py2,不需要退版或上 Hotfix。
(至於切回去也會壞掉的 Bug…只是普通的 Bug?就照平常的流程修吧。)
整個轉移過程只發現 3、4 個 bug,都在切回 Py2 之後正常運作,沒有給客服與營運造成負擔 (吧)。

Migration

因為 Py2、Py3 產出的 Migration 字串會不一樣 (Py3 有前綴 b’’),我們先保持用 Py2 產 ORM Migration file 和跑 migrate db,等 Py3 都上了之後再選個良辰吉日,請團隊一起改用 Py3 跑 migration。
當然事前也要測試 Py3 ORM 可以正確操作 DB。

良辰吉日

感謝農民曆。

後來覺得農民曆太重要了,還寫成查詢農民曆的 Script 跟大家分享

再見 Py2

在 Py3 正常運轉三個版本之後,也分次的把 Py2 Service 拆掉。
(拆掉前除了確認 ALB rules,也要記得看 Log 確認完全沒有流量了。)

更新行事曆

這是我沒做到的事,我覺得做了會更好:產品團隊的大家都很忙,發現 Bug 時沒有時間慢慢算哪個 API 是用 Py2 還是 Py3。
為了不要增加 Debug 的複雜度,可以做好風險揭露,早點把每個版本的 ALB rules 寫清楚,還有需要特別注意的服務與 API、每個環境什麼時候會搬到什麼版本。
定期發訊息更新,讓大家一眼就知道會發生什麼事。

還是有在做事啦!

Py3 旅途的後半段大部分時間都不用寫 Code,只要定期改 rule、觀察錯誤報告就好,所以會空出很多時間能做事。
像是從每一季都會更新的「Infra team todo-list」當中挑一些 task 出來做,還有例行的 performance 報告、改善 AWS 費用、套件升級、優化 CI 等等,零碎的工作塞滿了整整半年。

感恩的心

非常感謝大家的耐心閱讀,希望這些分享能幫助到你,這個方法不只適用於 Python 升級,最近團隊在升級 Django 時也參考了一部分流程來確保迅速退版。
現在想想整件事並不困難,只是很多瑣事第一次遇到時沒有想清楚,每天都過得挺緊張的。
特別感謝同事們的擔待,以及一起確認很多細節的 Mentor 和 QE。

--

--