字節(jié)研發(fā)設(shè)施下的 Git 工作流到底是怎么做的?
Git 提供了豐富的分支策略和工作流方式,我們?cè)谏钊雽W(xué)習(xí)業(yè)界 Git 工作流時(shí),每種工作流都設(shè)計(jì)的非常好,似乎都能運(yùn)用到團(tuán)隊(duì)實(shí)踐。但在引入 Git 工作流規(guī)范開(kāi)發(fā)時(shí)要留意:Git 工作流僅僅是整個(gè)研發(fā)流程中的一環(huán)。上游項(xiàng)目管理/缺陷追蹤系統(tǒng)虎視眈眈,下游 CD (Continuous Delivery) 嗷嗷待哺,還得考慮團(tuán)隊(duì)規(guī)模、產(chǎn)品形態(tài)、發(fā)版方式等等因素。因此,在團(tuán)隊(duì)中落地 Git 工作流規(guī)范并不是一件能輕松決定的事。
字節(jié)跳動(dòng) Git 倉(cāng)庫(kù)有效的 CR (Code Review) 覆蓋率 70%,仍有提升空間,通過(guò)調(diào)研,團(tuán)隊(duì)中又以 GitHub Flow 模式居多。隨著字節(jié)研發(fā)效能建設(shè)愈發(fā)完善,GitHub Flow 已無(wú)法充分利用研發(fā)設(shè)施進(jìn)行提效并保障工程質(zhì)量,很多團(tuán)隊(duì)均意識(shí)到這點(diǎn)并著手建設(shè)流程規(guī)范。
本文通過(guò)介紹業(yè)界 Git 工作流和公司研發(fā)設(shè)施現(xiàn)狀,力求從倉(cāng)庫(kù)形態(tài)、部署流程等多角度進(jìn)行分析,給出一些制定工作流規(guī)范的建議。
業(yè)界 Git 工作流介紹
Git Flow

初級(jí) Git 開(kāi)發(fā)者,面對(duì)這滿(mǎn)圖的分支和 merge 指向,簡(jiǎn)直想手撕作者。高級(jí) Git 開(kāi)發(fā)者要將這個(gè)流程運(yùn)用實(shí)踐也大感頭疼。
Git Flow 有不少優(yōu)點(diǎn):
? 分支各司其職,覆蓋大部分開(kāi)發(fā)場(chǎng)景。
? 預(yù)期 master 分支中任何 commit 都是可部署的。
? 嚴(yán)格按照流程執(zhí)行,出現(xiàn)重大事故的情形會(huì)大大降低。
缺點(diǎn)也不少:
? 過(guò)于繁瑣,無(wú)法要求所有團(tuán)隊(duì)成員按照這個(gè)流程嚴(yán)格執(zhí)行。
? 違反 git 提倡的 short-lived 分支原則。
? master 分支歷史記錄并不干凈,只能通過(guò)打 Tag 標(biāo)記哪些是 master 真正要部署的。
? 對(duì)持續(xù)部署和 monorepo 倉(cāng)庫(kù)不友好。
GitHub Flow
GitHub Flow 是一個(gè)基于分支的輕量級(jí)工作流。它突出了 CR 的重要性,有助于我們掌握 CR 的開(kāi)發(fā)模式,但它沒(méi)有解答部署、環(huán)境、發(fā)布、集成等問(wèn)題。

GitLab Flow
GitLab Flow 并不像 Git Flow, GitHub Flow 一樣具有明顯的規(guī)范,它更多是在 GitHub Flow 基礎(chǔ)上,綜合考慮環(huán)境部署、項(xiàng)目管理等問(wèn)題而得出的一種實(shí)踐。
基于環(huán)境:


基于發(fā)布計(jì)劃:

Trunk-based Flow
和“基于發(fā)布計(jì)劃”的 GitLab Flow 類(lèi)似,有一個(gè)主干分支接受所有開(kāi)發(fā)者的 commit,并為后續(xù) CI/CD 提供關(guān)鍵助力。
按照官方文檔描述:「你可以選擇直接向主干分支提交代碼的方式(適用于小團(tuán)隊(duì))或者采用 Pull-Request 的方式,只要保證特性分支不能長(zhǎng)期存在,并且產(chǎn)品是獨(dú)立存在的。(the product of a single person.)」,trunk 分支提交是比較隨意的(不一定可部署),但也需要走 CR,可以采用 Fast-forward 形式的 merge 保證主干是一條線(xiàn),到了合適的時(shí)間點(diǎn),checkout release-* 分支,執(zhí)行正式上線(xiàn)操作。
一旦發(fā)現(xiàn) release 分支有 hotfix 需求,則先在 trunk 分支上進(jìn)行 fix 開(kāi)發(fā),測(cè)試完成后,cherry-pick 到 release-_ 分支,確保修復(fù)代碼即在 release-_ 中上線(xiàn),又能被下一個(gè) release 周期包含。

Aone Flow
按阿里云開(kāi)發(fā)者社區(qū)描述:Aone Flow「基礎(chǔ)玩法是將每條發(fā)布分支與具體的環(huán)境相對(duì)應(yīng),比如 release/test 分支對(duì)應(yīng)部署測(cè)試環(huán)境,release/prod 分支對(duì)應(yīng)線(xiàn)上正式環(huán)境」,這種發(fā)布方式可保證每個(gè) feature 都被測(cè)試,但不能保證 release/test CI 通過(guò)的 feature,能在 release/prod 環(huán)境也通過(guò)(feature pick 組合不同)。
「進(jìn)階點(diǎn)的玩法是將一個(gè)發(fā)布分支對(duì)應(yīng)多個(gè)環(huán)境,比如把灰度發(fā)布和正式發(fā)布串在一起,中間加上人工驗(yàn)收的步驟」。實(shí)質(zhì)是將基礎(chǔ)玩法中的“release/test”,“release/prod” 改成 “release/combine-feature”,固定了 feature pick 組合,保證 features 在各個(gè)環(huán)境測(cè)試的一致性。
Aone Flow 的 pick 模式,適合復(fù)雜倉(cāng)庫(kù)大團(tuán)隊(duì)持續(xù)上線(xiàn),避免了 Trunk-based Flow 引入未完成 feature 的問(wèn)題。但似乎不適合周期發(fā)版的要求。一個(gè)發(fā)版周期內(nèi)會(huì)創(chuàng)建多個(gè) feature ,上一個(gè)發(fā)版周期可能遺留若干 feature,隨著時(shí)間推移,feature 數(shù)越來(lái)越多,最終發(fā)版人在 pick feature 過(guò)程中瘋掉。

公司實(shí)踐
字節(jié)跳動(dòng)的 Web 服務(wù)都跑在私有云 CE (Compute Engine) 中,部署產(chǎn)物則由統(tǒng)一的代碼編譯發(fā)布和版本管理平臺(tái)分發(fā),每個(gè)構(gòu)建產(chǎn)物都有一個(gè) AR (Artifact Repository) 管理。
多環(huán)境部署現(xiàn)狀
服務(wù)端視角
服務(wù)端微服務(wù)跑在 CE 上,代碼編譯由 AR 完成,CE 和 AR 是 1:N 的關(guān)系,一個(gè)應(yīng)用的運(yùn)行依賴(lài)多個(gè) AR,在進(jìn)行環(huán)境管理時(shí),需要以 CE 為緯度來(lái)區(qū)分。從 CE 視角來(lái)看,公司有 5 類(lèi)環(huán)境(以國(guó)內(nèi)產(chǎn)品為例):

通過(guò) headers -H 'x-env-tag:{env}' 將流量導(dǎo)向不同環(huán)境,滿(mǎn)足“開(kāi)發(fā)測(cè)試”、“QA 測(cè)試”、“預(yù)發(fā)測(cè)試”、“小流量測(cè)試”、“全量上線(xiàn)” 各階段的測(cè)試需求。
CE 測(cè)試環(huán)境服務(wù)示例:

前端視角
前端和服務(wù)端有差異,一個(gè) URL path 訪(fǎng)問(wèn)的資源通常由一個(gè) AR 產(chǎn)出,URL paths 和 AR 是 N:1 關(guān)系,所以前端部署以 AR 版本來(lái)區(qū)分環(huán)境:

前端部署可為測(cè)試環(huán)境和產(chǎn)品預(yù)覽環(huán)境生成獨(dú)立的域名進(jìn)行測(cè)試,也可通過(guò)設(shè)定 headers -H 'x-env-tag:{env}' 進(jìn)行環(huán)境導(dǎo)流。
團(tuán)隊(duì)實(shí)踐的 Git 工作流
結(jié)合前后端的環(huán)境現(xiàn)狀,可整理三類(lèi)研發(fā)流程:
功能測(cè)試流程(測(cè)試環(huán)境) QA 提測(cè)流程(測(cè)試環(huán)境) 上線(xiàn)發(fā)布流程(測(cè)試、預(yù)發(fā)、灰度、線(xiàn)上環(huán)境)
公司內(nèi)目前有三種 Git 工作流與之對(duì)應(yīng):
? 小步快跑:?jiǎn)沃鞲?/p>
? 周期發(fā)版:雙主干

? 周期發(fā)版:三主干

對(duì)比“雙主干”和“單主干”,
有聯(lián)系:
處于 MR 狀態(tài)的迭代分支 ≈≈ 研發(fā)主干 Dev 單主干 Master ≈≈ 發(fā)布主干 Master
也有區(qū)別:
單主干的“研發(fā)分支”不存在一個(gè)固定的測(cè)試環(huán)境(相較于雙主干 dev 分支) 多個(gè) feature 同時(shí)發(fā)測(cè)試環(huán)境時(shí)需要組合成新分支,管理不便 單主干迭代分支在 MR /非 MR 狀態(tài)下的 CI 流水線(xiàn)有差異
單主干實(shí)踐
前端微服務(wù)管理平臺(tái)
字節(jié)前端微服務(wù)平臺(tái)屬于成熟業(yè)務(wù),只需做少量 feature/fix 開(kāi)發(fā),在工作流上采用單主干模式。

本地測(cè)試后,不再經(jīng)過(guò)功能測(cè)試環(huán)境測(cè)試。發(fā)起 Merge Request,CR 通過(guò)合碼后,上測(cè)試基準(zhǔn)環(huán)境進(jìn)行測(cè)試,如發(fā)現(xiàn)問(wèn)題,回滾,進(jìn)入下一輪 CR。
雖然小修小改影響風(fēng)險(xiǎn)小,但流程缺乏進(jìn)入功能測(cè)試環(huán)境的流程,還是存在隱患,有可能影響測(cè)試環(huán)境的業(yè)務(wù)方使用,不是很好的實(shí)踐。
單主干只適應(yīng)于業(yè)務(wù)規(guī)模小,成熟度高無(wú)大改動(dòng)的項(xiàng)目。但無(wú)論業(yè)務(wù)規(guī)模如何,執(zhí)行上線(xiàn)發(fā)布流程前,都必須先經(jīng)過(guò)線(xiàn)下環(huán)境驗(yàn)證。
雙主干實(shí)踐
私有云平臺(tái)
云平臺(tái)的 Git 工作流是由繁入簡(jiǎn)的過(guò)程。最開(kāi)始為每個(gè)部署環(huán)境設(shè)定了一個(gè)部署分支。feature 分支的 commit 點(diǎn)需要和三個(gè)環(huán)境的部署分支發(fā)生 merge,起不到串聯(lián)測(cè)試的目的。

經(jīng)過(guò)改進(jìn)后,采用標(biāo)準(zhǔn)的 Trunk-based Flow,仍能滿(mǎn)足 online/sandbox/boe 三個(gè)環(huán)境的部署要求。

云平臺(tái)參與業(yè)務(wù)方有上百個(gè)(類(lèi)似阿里云平臺(tái)),雖然圖中僅展示了三個(gè)環(huán)境,但實(shí)際上 5 大環(huán)境的使用都融入了日常開(kāi)發(fā)中。
某業(yè)務(wù)中臺(tái)
某業(yè)務(wù)中臺(tái)的 Git 工作流重點(diǎn)闡述了同一個(gè)項(xiàng)目多人協(xié)作開(kāi)發(fā)時(shí)會(huì)遇到的問(wèn)題:
多個(gè) feature 各自獨(dú)立提測(cè), 臨近上線(xiàn)合碼時(shí)有較多沖突, 可能導(dǎo)致線(xiàn)上 bug 提測(cè)前和提測(cè)中, 如果 master 更新了, 可能沒(méi)有及時(shí)同步下來(lái), 上線(xiàn)前合入 master 可能會(huì)導(dǎo)致沖突或 bug 在流程設(shè)計(jì)上,master 作為發(fā)布分支,release-* 為提測(cè)分支,結(jié)合了單主干的便捷(hotfix 直接和 master 交互)和雙主干對(duì) feature 的管理 和 Trunk-based Flow 剛好相反,主分支是發(fā)布分支,提測(cè)分支是短期的 另一個(gè)比較有特點(diǎn)的是,在 release 測(cè)試過(guò)程中,發(fā)現(xiàn)某個(gè) feature 的 bug, 直接從 release 分支 checkout 出來(lái)進(jìn)行修復(fù),并再次合入 release

Jupiter 工作流規(guī)范
Jupiter 是字節(jié) Web 開(kāi)發(fā)引擎,覆蓋 Web、組件庫(kù)、BFF、SSR 等前端開(kāi)發(fā)領(lǐng)域。Jupiter 推薦 Trunk-based Flow 類(lèi)似的 Flow,并從 CI/CD 角度出發(fā),在開(kāi)發(fā)新需求、hotfix 等時(shí)機(jī)給出 Git 操作建議。
有重疊人員參與的各項(xiàng)目之間有一致的流程和模式,避免增加認(rèn)知負(fù)擔(dān),避免同一個(gè)人在不同項(xiàng)目之間切換時(shí)混淆和迷惑,也能集中力量做實(shí)踐和改進(jìn),共享經(jīng)驗(yàn)和基礎(chǔ)建設(shè)。 上線(xiàn)節(jié)奏要靈活。既照顧早期的項(xiàng)目,也照顧規(guī)?;涞氐捻?xiàng)目,考慮到項(xiàng)目在追求不同里程碑時(shí),上線(xiàn)頻率會(huì)有變化。所以每周上線(xiàn)一次、每天上線(xiàn)一次(適合人多、穩(wěn)定性要求高的項(xiàng)目)、一天內(nèi)分多次上線(xiàn)多個(gè) feature,都有可能,不能限定一個(gè)固定的節(jié)奏。任何人,在任何時(shí)候都可以發(fā)起上線(xiàn)。 支持 monorepo。不同方向不同人參與的項(xiàng)目,可能會(huì)共用一個(gè)倉(cāng)庫(kù),方便復(fù)用代碼和基礎(chǔ)設(shè)施。所以倉(cāng)庫(kù)中不同項(xiàng)目可能有不一樣的上線(xiàn)節(jié)奏和上線(xiàn)需求。 鼓勵(lì)持續(xù)集成(CI),集成不等同于部署,發(fā) MR 集成代碼的時(shí)候不用有壓力,不會(huì)在不知情的情況下被上線(xiàn)。也鼓勵(lì)持續(xù)部署(CD),部署不等于發(fā)布,不能發(fā)布的代碼,在正式上線(xiàn)前有機(jī)會(huì)關(guān)掉。 上線(xiàn)過(guò)程必須是固定、重復(fù)、能統(tǒng)一改進(jìn),能逐步增加自動(dòng)化的,不能每次上線(xiàn)時(shí)重新、臨時(shí)規(guī)劃或修改上線(xiàn)方法,增加負(fù)擔(dān)和成本。 CI 是 CD 的前提,沒(méi)經(jīng)過(guò) CI 的修改不能進(jìn)入 CD 環(huán)節(jié)。 不能有任何修改不經(jīng)過(guò) staging(預(yù)發(fā)布,盡可能跟線(xiàn)上一致)測(cè)試,直接上線(xiàn)。 CI 和 CD 的歷史記錄要絕對(duì)可靠、可追溯,只能增加,不能減少和修改。 盡可能減少手動(dòng)操作環(huán)節(jié),避免在特定的個(gè)人機(jī)器上做上線(xiàn)操作。
三主干實(shí)踐
億級(jí) App
App 發(fā)版應(yīng)該是目前為止最為復(fù)雜的分支管理場(chǎng)景了??蛻?hù)端安裝包一旦下發(fā)到渠道被用戶(hù)下載,如果無(wú)法通過(guò)熱更新修復(fù),將嚴(yán)重影響 App 用戶(hù)留存。App 發(fā)版具有更規(guī)范的發(fā)版規(guī)律,F(xiàn)eature Toggle 必不可少,當(dāng)我們覺(jué)得 CR,CI/CD 麻煩時(shí),對(duì)比開(kāi)發(fā)上線(xiàn)一個(gè)影響上億用戶(hù)的 App feature,是不是感覺(jué)做 Web 的 CI/CD 簡(jiǎn)單多了?

總結(jié)
本文盡可能從多角度闡述 Git 工作流的使用姿勢(shì),希望對(duì)大家有幫助。Git 工作流是為了上線(xiàn)有保障,上線(xiàn)過(guò)程中充分測(cè)試必不可少,良好的 Git 工作流能保障測(cè)試是漸進(jìn)且可靠的。環(huán)境管理和 Git 工作流結(jié)合在頭條內(nèi)部也形成了很多規(guī)范,包括「環(huán)境部署」、「流量調(diào)度」、「連通性測(cè)試」等使用規(guī)范;「限定場(chǎng)景允許」、「暫時(shí)場(chǎng)景允許」、「限定流程允許」等環(huán)境約束規(guī)范。再結(jié)合 CI/CD,我們就可以全鏈路保障業(yè)務(wù)的快速迭代、安全上線(xiàn)。
參考資料
Trunk-based Development vs. Git Flow
( https://www.toptal.com/software/trunk-based-development-git-flow )
Please stop recommending Git Flow!
( https://georgestocker.com/2020/03/04/please-stop-recommending-git-flow/ )
Understanding the GitHub flow
( https://guides.github.com/introduction/flow/index.html )
Introduction to GitLab Flow
( https://docs.gitlab.com/ee/topics/gitlab_flow.html )
https://cn.trunkbaseddevelopment.com
在阿里,我們?nèi)绾喂芾泶a分支?
( https://developer.aliyun.com/article/573549 )
谷歌的代碼管理
( http://www.ruanyifeng.com/blog/2016/07/google-monolithic-source-repository.html )
