在阿裏,我們如何管理代碼分支?
引言
在阿裏內部,流行著許多有意思的工程實踐。有些實踐通過工具和流程嵌在集團的大環境裏,外界不容易復制,有些實踐則是流露在大家的日常習慣裏,被默默的遵守。比如分支管理這件事,其實屬於工具和習慣各占一半,並且頗有阿裏特色的成分,適合作為一個例子。阿裏有很多的研發團隊,不同事業部使用的發布流程、分支策略並非整齊劃一,但總體上看是比較規整的。其中有一種主流的發布模式以及對應的分支使用方式,稱為“AoneFlow”。這套工作模式思路獨特,在阿裏以外的地方並不多見。本文圍繞這些實踐,聊一聊分支管理的話題。
細數分支模式
說到分支管理模式,我們最耳熟能詳的莫過於 TrunkBased 和 GitFlow。
TrunkBased 模式是持續集成思想所崇尚的工作方式,它由單個主幹分支和許多發布分支組成,每個發布分支在特定版本的提交點上從主幹創建出來,用來進行上線部署和 Hotfix。在 TrunkBased 模式中,沒有顯性的特性分支。當然實際上 Git 的分布式特征天生允許每個人有本地分支,TrunkBased 也並非排斥短期的特性分支存在,只不過在說這種模式的時候,大家通常都不會明確強調它罷了。
雖然近年來有許多不錯的案例,但 TrunkBased 模式並沒有一統天下。它的缺點比較明顯,太多的團隊同時工作在主幹上,到發布的時候就可能出現災難(尤其是多版本並行開發的情況)。彌補的措施是 FeatureToggle 以及頻繁的集成和足夠的測試覆蓋,這對開發團隊的能力提出了比較高的要求。目前 TrunkBased 模式主要用在不需要同時維護多個歷史版本的 SaaS 型項目,特別是經過微服務改造的各種小型服務上。
TrunkBased 模式有兩種常見演進版本。OneFlow 模式參考了 TrunkBased 的許多思想,對操作流程做了更嚴格的定義,增加了 Hotfix 分支等內容。多主幹模式(通常是雙主幹,固定的開發分支和固定的發布分支),算是 TrunkBased 采用固定發布分支的特例,在提升團隊的微服務落地能力這篇文章裏介紹過,不再贅述。
GitFlow 模式是若幹模式的集大成者,包含一個主幹分支、一個開發分支、許多的特性分支、許多的發布分支和 Hotfix 分支,以及許多繁瑣的合並規則。它有一個 Git 插件,不過早就沒人維護了。由於對每個階段的每項操作定義十分明確,它曾經是很多重視流程的企業眼裏的香饃饃。但它使用起來並不是很容易,大量的合並沖突和對集成測試不友好也是它被詬病最多的地方。
對,還有 GithubFlow 模式,不過這種策略無非是在 TrunkBased 的基礎上,增加了個人倉庫和 Pull Request 合並代碼的操作,與在同一個倉庫裏增加個人分支的做法類似,從實用的意義來說,它更合適分布式團隊。GithubFlow 也有演進版本,例如強調了多環境部署和將倉庫或分支與環境關聯的 GitlabFlow 模式。
要麽簡單粗暴如 TrunkBased,要麽繁瑣復雜如 GitFlow。難到真沒有其他選擇了嗎?
另辟蹊徑的 AoneFlow
在 AoneFlow 上你能看到許多其他分支模式的影子。它基本上兼顧了 TrunkBased 的“易於持續集成”和 GitFlow 的“易於管理需求”特點,同時規避掉 GitFlow 的那些繁文縟節。
看一下具體套路。AoneFlow 只使用三種分支類型:主幹分支、特性分支、發布分支,以及三條基本規則。
規則一,開始工作前,從主幹創建特性分支。
AoneFlow 的特性分支基本借鑒 GitFlow,沒有什麽特別之處。每當開始一件新的工作項(比如新的功能或是待解決的問題)的時候,從代表最新已發布版本的主幹上創建一個通常以feature/前綴命名的特性分支,然後在這個分支上提交代碼修改。也就是說,每個工作項(可以是一個人完成,或是多個人協作完成)對應一個特性分支,所有的修改都不允許直接提交到主幹。
規則二,通過合並特性分支,形成發布分支。
AoneFlow 的發布分支設計十分巧妙,可謂整個體系的精髓。GitFlow 先將已經完成的特性分支合並回公共主線(即開發分支),然後從公共主線拉出發布分支。TrunkBased 同樣是等所有需要的特性都在主幹分支上開發完成,然後從主幹分支的特定位置拉出發布分支。而 AoneFlow 的思路是,從主幹上拉出一條新分支,將所有本次要集成或發布的特性分支依次合並過去,從而得到發布分支。發布分支通常以release/前綴命名。
這條規則很簡單,不過實際的玩法就相當豐富了。
首先,發布分支的用途可以很靈活。基礎玩法是將每條發布分支與具體的環境相對應,比如release/test分支對應部署測試環境,release/prod分支對應線上正式環境等等,並與流水線工具相結合,串聯各個環境上的代碼質量掃描和自動化測試關卡,將產出的部署包直接發布到相應環境上。進階點的玩法是將一個發布分支對應多個環境,比如把灰度發布和正式發布串在一起,中間加上人工驗收的步驟。高級的玩法呢,要是按叠代計劃來關聯特性分支,創建出以叠代演進的固定發布分支,再把一系列環境都串在這個發布分支的流水線上,就有點經典持續集成流水線的味道了。再或者做一個將所有特性分支都關聯在一起的發布分支,專門用於對所有提交做集成測試,就玩出了 TrunkBased 的效果。當然,這些花哨的高級玩法是我臆想的,阿裏的發布分支一般都還是比較中規中矩。
其次,發布分支的特性組成是動態的,調整起來特別容易。在一些市場瞬息萬變的互聯網企業,以及采用“敏捷運作”的乙方企業經常會遇到這種情況,已經完成就等待上線的需求,隨時可能由於市場策略調整或者甲方的一個臨時決定,其中某個功能忽然要求延遲發布或者幹脆不要了。再或者是某個特性在上線前發現存在嚴重的開發問題,需要排除。按往常的做法,這時候就要來手工“剔代碼”了,將已經合並到開發分支或者主幹分支的相關提交一個個剔除出去,做過的同學都知道很麻煩。在 AoneFlow 的模式下,重建發布分支只是分分鐘的事,將原本的發布分支刪掉,從主幹拉出新的同名發布分支,再把需要保留的各特性分支合並過來就搞定。這一系列動作能夠在很大程度上實現自動化,而且不會在倉庫留下一堆剔除代碼的記錄,幹凈無汙染。
此外,發布分支之間是松耦合的,這樣就可以有多個集成環境分別進行不同的特性組合的集成測試,也能方便的管理各個特性進入到不同環境上部署的時機。松耦合並不代表沒有相關性,由於測試環境、集成環境、預發布環境、灰度環境和線上正式環境等發布流程通常是順序進行的,在流程上可以要求只有通過前一環境驗證的特性,才能傳遞到下一個環境做部署,形成漏鬥形的特性發布流。阿裏有統一平臺來自動化完成特性組合在發布分支間的遷移,在下面講工具的部分裏會再介紹。
規則三,發布到線上正式環境後,合並相應的發布分支到主幹,在主幹添加標簽,同時刪除該發布分支關聯的特性分支。
當一條發布分支上的流水線完成了一次線上正式環境的部署,就意味著相應的功能真正的發布了,此時應該將這條發布分支合並到主幹。為了避免在代碼倉庫裏堆積大量歷史上的特性分支,還應該清理掉已經上線部分特性分支。與 GitFlow 相似,主幹分支上的最新版本始終與線上版本一致,如果要回溯歷史版本,只需在主幹分支上找到相應的版本標簽即可。
除了基本規則,還有一些實際操作中不成文的技巧。比如上線後的 Hotfix,正常的處理方法應該是,創建一條新的發布分支,對應線上環境(相當於 Hotfix 分支),同時為這個分支創建臨時流水線,以保障必要的發布前檢查和冒煙測試能夠自動執行。但其實還有一種簡便方法是,將線上正式環境對應的發布分支上關聯的特性分支全部清退掉,在這個發布分支上直接進行修改,改完利用現成的流水線自動發布。如果非得修一個歷史版本的 Bug 怎麽辦呢?那就老老實實的在主幹分支找到版本標簽位置,然後從那個位置創建 Hotfix 分支吧,不過由於阿裏的產品大多是線上 SaaS 業務,這樣的場景並不多見。
正是這些簡單的規則,組成了 AoneFlow 獨樹一幟的核心套路。
AoneFlow 中每一個看似簡單的步驟都並非憑空臆造,而是經歷大量產品團隊反復磨礪後積累下來的經驗。接下來,我會說說 AoneFlow 的技術門檻以及阿裏內部的應對之道。
AoneFlow 的體驗優化
諳熟武俠之道的人都懂得,掌握一個門派的看家武藝,除了要會招式,還得有深厚的內功和趁手的兵器。否則拿了辟邪劍譜,也只能望譜興嘆。
阿裏團隊的內功和兵器,實際上是良好的代碼習慣和齊全的配套工具。
這裏說的習慣,除了開發流程和代碼分支的管理方式以外,還包括日常開發中的一些約定俗成的規約。阿裏的許多開發規約是有“文獻”記載的,主要收錄在 《阿裏巴巴 Java 開發手冊》 裏面。它的內容現在已經公開了,所以早就不算是秘密。
舉一個具體的例子。在 AoneFlow 的流程中,每次重建發布分支的時候都會重新合並然後編譯代碼,產生新的部署包。然而,即使代碼的內容是一樣的,如果工程中依賴了一些會改變的第三方軟件包,依然可能導致打包出的產品行為不完全一致。因此,在阿裏的代碼規約中就明確地指出了,用於線上發布的代碼,不可以使用包含“SNAPSHOT 版本”(即未正式發布版本)的依賴包,從而確保每次構建出的產物都是一致的。類似這樣的細節還有很多,好的開發習慣是確保軟件質量的必要前提。
工具可以使得團隊協作更加平滑。雖然只要弄懂原理,AoneFlow 中每個分支創建、合並、更改步驟使用單純的 Git 命令就能玩轉。但其中的一些操作(比如為每個發布分支選出恰當的特性分支組合進行合並)手工執行極易出錯,而且讓團隊的個人重復這些日常瑣事的命令操作,並不是令人愉悅的事情。
在阿裏內部,使用 AoneFlow 流程的團隊基本上不用自己運行 Git 來處理分支的事情,而是由阿裏巴巴集團內部名叫 Aone 的協同研發平臺(以下簡稱平臺)接管。這個承擔集團 80% 產品從需求和用戶故事提出到部署上線完整研發流程的平臺,內置了許多以服務組件的形式嵌入的研發提效工具,其中的發布組件為 AoneFlow 的用戶體驗添色不少。比較顯著的輔助“功效”包括以下幾個方面。
首先是整體流程的自動化。
由於是內部工具,平臺的功能高度內聚。對於項目而言,從提出原始需求,將需求拆分為任務,然後根據任務在線創建特性分支,再聚合生成發布分支,同時根據模板自動創建測試環境,直到後期的運維保障都可以一站式的搞定。
這個流程已經遠遠超出了代碼分支管理的範疇。但正是因為如此,平臺對於 AoneFlow,向前做到了將特性分支和需求項關聯起來,確保了特性分支的命名規範性;向後做到了將發布分支與部署行為關聯起來,確保了各環境版本來源的可靠性。打通了端到端交付的任督二脈。
其次是發布分支的流水線。
作為一種流程自動化的手段,CI/CD 流水線是許多現代交付團隊中常見的標配實踐。在 AoneFlow 的代碼生命周期裏涉及許多分支,當這些分支被創建或更新時,往往需要伴隨其他的一系列行為。流水線能夠將這些日常開發過程中的代碼分支與其所表達的深層意圖(比如提交代碼即進行集成測試)聯系起來。特別是發布分支,AoneFlow 的每個發布分支通常關聯具體的部署環境,當有新代碼合並進分支時,就應該及時對代碼進行檢查和部署。
理想情況下,每條不同的分支都應該有與其作用相匹配的一條流水線來為它服務。AoneFlow 的發布分支是相對固定的,因此相比 GitFlow 更易於進行持續集成。理論上任何流水線工具都能夠配合 AoneFlow 使用,不過,阿裏的統一平臺提供流水線對代碼評審、安全檢查、在線部署等功能的整合,還是為 AoneFlow 在內部團隊的使用優化增色不少。
還有一項很有用的輔助是分支關聯的管理。
特性分支與發布分支的關聯關系維護是一個 AoneFlow 特有的問題。記住每個發布分支分別來自哪些特性分支對於需要基於現有特性組合進行改變的時候十分有意義。比如當需要將某個特性從特定發布分支退出時,通常會將除了該特性以外的其他特性所在分支進行一次合並,以替換原有的發布分支。人為的記錄這些信息並不輕松,要是通過平臺進行展示和輔助就會方便許多。
當某些功能組合在一個低級別的發布環境(如集成測試環境)驗證完成後,我們希望將它的內容直接遷移到高級別的環境(如預發布環境)對應的發布分支上。這樣可以確保線上的版本一定是經過預發驗證的,預發的版本一定是經過集成驗證的,以此類推,使得各個發布分支形成串聯。同樣的,使用普通的 Git 命令就能實現這個操作,只不過用可視化工具會讓流程更加直觀。
除此以外,平臺提供代碼倉庫各個分支狀況的統一展示,包括分支所對應部署環境的機器信息、操作記錄等全都一覽無余。正是這些“高附加值”的輔助,使得 AoneFlow 得以揚長避短,成為阿裏團隊支撐復雜項目首選的利器。
寫在最後
代碼分支模式的選擇並沒有絕對的正確和錯誤之分,關鍵是與項目的規模和發布節奏相匹配。阿裏協同研發平臺在經過眾多實踐歷練後,總結出了一套獨創的分支管理方法,通過兼備靈活高效與簡單實用的流程,保障阿裏旗下眾多產品的交付。當你還在猶豫於琳瑯滿目的分支模式,既舍不得 GitFlow 的並行特性開發,又放不下 TrunkBased 的持續集成友好時,AoneFlow 也許是一個值得考慮的選擇。
原文鏈接
閱讀更多幹貨好文,請關註掃描以下二維碼:
在阿裏,我們如何管理代碼分支?