持續內建是指,及時地将最新開發的且經過測試的代碼內建到主幹分支中。

Continuous Integration
持續內建的優點
● 快速發現錯誤 每次更新都及時內建到主幹分支中,并進行測試,可以快速發現錯誤,友善定位錯誤
● 避免子分支大幅偏離主幹分支 主幹在不斷更新,如果不經常內建,會産生後期內建難度變大,甚至難以內建,并造成不同開發人員間不必要的重複開發
● 為快速疊代提供保障 持續內建為後文介紹的持續釋出與持續部署提供了保證
Spark CI 實踐目前主流的代碼管理工具有,Github、Gitlab等。本文所介紹的内容中,所有代碼均托管于私有的 Gitlab 中。
鑒于 Jenkins 幾乎是 CI 事實上的标準,本文介紹的 Spark CI CD & CD 實踐均基于 Jenkins 與 Gitlab。
Spark 源碼儲存在 spark-src.git 庫中。
由于已有部署系統支援 Git,是以可将內建後的 distribution 儲存到 Gitlab 的釋出庫(spark-bin.git)中。
每次開發人員送出代碼後,均通過 Gitlab 發起一個 Merge Requet (相當于 Gitlab 的 Pull Request)
每當有 MR 被建立,或者被更新,Gitlab 通過 Webhook 通知 Jenkins 基于該 MR 最新代碼進行 build。該 build 過程包含了
● 編譯 Spark 所有 module
● 執行 Spark 所有單元測試
● 執行性能測試
● 檢查測試結果。如果有任意測試用例失敗,或者性能測試結果明顯差于上一次測試,則 Jenkins 建構失敗
Jenkins 将 build 結果通知 Gitlab,隻有 Jenkins 建構成功,Gitlab 的 MR 頁面才允許 Merge。否則 Gitlab 不允許 Merge
另外,還需人工進行 Code Review。隻有兩個以上的 Reviewer 通過,才能進行最終 Merge所有測試與 Reivew 通過後,通過 Gitlab Merge 功能自動将代碼 Fast forward Merge 到目标分支中
該流程保證了
● 所有合并進目标分支中的代碼都經過了單元測試(白盒測試)與性能測試(黑盒測試)
● 每次發起 MR 後都會及時自動發起測試,友善及時發現問題
● 所有代碼更新都能及時合并進目标分支
Spark CD 持續傳遞 CD 持續傳遞介紹持續傳遞是指,及時地将軟體的新版本,傳遞給品質保障團隊或者使用者,以供評審。持續傳遞可看作是持續內建的下一步。它強調的是,不管怎麼更新,軟體都是可随時傳遞的。
這一階段的評審,一般是将上文內建後的軟體部署到盡可能貼近生産環境的 Staging 環境中,并使用貼近真實場景的用法(或者流量)進行測試。
Continuous Delivery
持續釋出的優點
● 快速釋出 有了持續內建與持續釋出,可快速将最新功能釋出出來,也可快速修複已知 bug
● 快速疊代 由于釋出及時,可以快速判斷産品是否符合産品經理的預期或者是否能滿足使用者的需求
Spark CD 持續釋出實踐這裡有提供三種方案,供讀者參考。推薦方案三
方案一:單分支 正常流程如下圖所示,基于單分支的 Spark 持續傳遞方案如下
● 所有開發都在
spark-src.git/dev
(即 spark-src.git 的 dev branch) 上進行
● 每周一将目前最新代碼打包,放進
spark-bin.git/dev
的
spark-${ build # }
(如圖中第 2 周的 spark-72)檔案夾内
● spark-prod 指向目前 spark-dev 指向的檔案夾(如圖中的 spark-71 )
● spark-dev 指向
spark-${ build # }
(如圖中的 spark-72)
● 自動将 spark-bin.git 最新内容上線到 Staging 環境,并使用 spark-dev 進行測試
● spark-prod 比 spark-dev 晚一周(一個 release 周期),這一周用于 Staging 環境中測試
Continuous Delivery Solution 1
注:
● 藍色圓形是正常 commit
● 垂直虛線是釋出時間點,week 1、week 2、week 3、week 4
● 最上方黑色粗橫線是源碼時間線
● 下方黃色粗橫線是 release 時間線
● 綠色方框是每周生成的 release,帶 build #
● 藍色方框是開發版本的 symbolic
● 橘色方框是線上版本的 symbolic
bug fix在 Staging 環境中發現 spark-dev 的 bug 時,修複及內建和傳遞方案如下
● 如果在 Staging 環境中發現了 spark-dev 的 bug,且必須要修複(如不修複,會帶到下次的 spark-prod 的 release 中),則送出一個 commit,并且 commit message 包含 bugfix 字樣(如圖中黑色圓形 commit 9 所示)
● 該 bugfix 被 Merge 後,Jenkins 得到通知
● Jenkins 發現該 commit 是 bugfix,立即啟動建構,生成
spark-${ build # }
(如圖中的 spark-73)
spark-${ build # }
(如圖中的 spark-73 )
Continuous Delivery Solution 1 bug fix
hot fix生産環境中發現 bug 時修複及傳遞方案如下
● 如果發現線上版本(即 spark-prod)有問題,須及時修複,則送出一個 commit,并且 commit message 包含 hotfix 字樣 (如圖中紅色圓形 commit 9 所示)
● 該 hotfix 被 Merge 後,Jenkins 得到通知
● Jenkins 發現該 commit 是 hotfix,立即啟動建構,生成
spark-${ build # }
● spark-dev 與 spark-prod 均指向
spark-${ build # }
Continuous Delivery Solution 1 hotfix
Pros.● spark-src.git 與 spark-bin.git 都隻有一個分支,維護友善
● spark-prod 落後于 spark-dev 一周(一個 release),意味着 spark-prod 都成功通過了一周的 Staging 環境測試
Cons.● 使用 spark-prod 與 spark-dev 兩個 symbolic,如果要做灰階釋出,需要使用者修改相應路徑,成本較高
● hotfix 時,引入了過去一周多(最多兩周)未經 Staging 環境中通過 spark-dev 測試的 commit,增加了不确定性,也違背了所有非 hotfix commit 都經過了一個釋出周期測試的原則
方案二:兩分支如下圖所示,基于兩分支的 Spark 持續傳遞方案如下
●
spark-src.git
與
spark-bin.git
均包含兩個分支,即 dev branch 與 prod branch
● 所有正常開發都在
spark-src.git/dev
上進行
● 每周一(如果是 weekly release)從
spark-src.git/dev
打包出一個 release 放進
spark-bin.git/dev
spark-${ build # }
檔案夾内(如圖中第 2 周上方的的 spark-2 )。它包含了之前所有的送出(commit 1、2、3、4)
spark-bin.git/dev
的 spark 作為 symbolic 指向
spark-${ build # }
檔案夾内(如圖中第 2 周上方的的 spark-2)
spark-src.git/prod
通過 fast-forward merge 将
spark-src.git/dev
一周前最後一個 commit 及之前的所有 commit 都 merge 過來(如圖中第 2 周需将 commit 1 merge 過來)
● 将
spark-src.git/prod
spark-bin.git/prod
spark-${ build # }
檔案夾内(如圖中第 2 周下方的的 spark-1 )
spark-bin.git/prod
的 spark 作為 symbolic 指向
spark-${ build # }
Continuous Delivery Solution 2
在 Staging 環境中發現了 dev 版本的 bug 時,修複及內建和傳遞方案如下
● 在
spark-src.git/dev
上送出一個 commit (如圖中黑色的 commit 9),且 commit message 包含 bugfix 字樣
● Jenkins 發現該 commit 為 bugfix 後,立即建構,從
spark-src.git/dev
打包生成一個 release 并放進
spark-bin.git/dev
spark-${ build # }
檔案夾内(如圖中第二周與第三周之間上方的的 spark-3 )
spark-bin.git/dev
中的 spark 作為 symbolic 指向
spark-${ build # }
Continuous Delivery Solution 2 bugfix
在生産環境中發現了 prod 版本的 bug 時,修複及內建和傳遞方案如下
spark-src.git/dev
上送出一個 commit(如圖中紅色的 commit 9),且 commit message 包含 hotfix 字樣
● Jenkins 發現該 commit 為 hotfix 後,立即将
spark-src.git/dev
打包生成 release 并 commit 到
spark-bin.git/dev
spark-${ build # }
(如圖中上方的 spark-3 )檔案夾内。 spark 作為 symbolic 指向該
spark-${ build # }
● 通過 cherry-pick 将 commit 9 double commit 到
spark-src.git/prod
(如無沖突,則該流程全自動完成,無需人工參與。如發生 ● 沖突,通過告警系統通知開發人員手工解決沖突後送出)
spark-src.git/prod
spark-bin.git/prod
spark-${ build # }
(如圖中下方的 spark-3 )檔案夾内。spark作為 symbolic 指向該spark-${ build # }
Continuous Delivery Solution 2 hotfix
● 無論是 dev 版還是 prod 版,路徑都是 spark。切換版對使用者透明,無遷移成本
● 友善灰階釋出
● hotfix 不會引入未經測試的 commit,穩定性更有保障
● prod 版落後于 dev 版一周(一個 release 周期),即 prod 經過了一個 release 周期的測試,穩定性強
● hot fix 時,使用 cherry-pick,但
spark-src.git/dev
(包含 commit 1、2、3、4、5) 與
spark-src.git/prod
(包含 commit 1) 的 base 不一樣,有發生沖突的風險。一旦發生沖突,便需人工介入
● hot fix 後再從
spark-src.git/dev
合并 commit 到
spark-src.git/prod
時需要使用 rebase 而不能直接 fast-forward merge。而該 rebase 可能再次發生沖突
● bug fix 修複的是目前
spark-bin.git/dev
的 bug,即圖中的 commit 1、2、3、4 後的 bug,而 bug fix commit 即 commit 9 的 base 是 commit 5,存在一定程度的不一緻
● bug fix 後,第 3 周時,最新的
spark-bin.git/dev
包含了 bug fix,而最新的
spark-bin.git/prod
未包含該 bugfix (它隻包含了 commit 2、3、4 而不包含 commit 5、9)。隻有到第 4 周,
spark-bin.git/prod
才包含該 bugfix。也即 Staging 環境中發現的 bug,需要在一周多(最多兩周)才能在 prod 環境中被修複。換言之,Staging 環境中檢測出的 bug,仍然會繼續出現在下一個生産環境的 release 中
spark-src.git/dev
spark-src.git/prod
中包含的 commit 數一緻(因為隻允許 fast-forward merge),内容也最終一緻。但是 commit 順序不一緻,且各 commit 内容也可能不一緻。如果維護不當,容易造成兩個分支差别越來越大,不易合并
方案三:多分支如下圖所示,基于多分支的 Spark 持續傳遞方案如下
● 正常開發在
spark-src.git/master
● 每周一通過 fast-forward merge 将
spark-src.git/master
最新代碼合并到
spark-src.git/dev
。如下圖中,第 2 周将 commit 4 及之前所有 commit 合并到
spark-src.git/dev
spark-src.git/dev
打包生成 release 并送出到
spark-bin.git/dev
spark-${ build # }
(如下圖中第 2 周的 spark-2) 檔案夾内。spark 作為 symbolic,指向該
spark-${ build # }
spark-src.git/master
一周前最後一個 commit 合并到
spark-src.git/prod
。如第 3 周合并 commit 4 及之前的 commit
● 上一步中,如果 commit 4 後緊臨有一個或多個 bugfix commit,均需合并到
spark-src.git/prod
中,因為它們是對 commit 4 進行的 bug fix。後文介紹的 bug fix 流程保證,如果對 commit 4 後釋出版本有多個 bug fix,那這多個 bug fix commit 緊密相連,中間不會被正常 commit 分開
spark-src.git/prod
spark-bin.git/prod
spark-${ build # }
spark-${ build # }
Continuous Delivery Solution 3
● 如下圖中,第 2 周與第 3 周之間在 Staging 環境中發現 dev 版本的 bug,在
spark-src.git/dev
(包含 commit 1、2、3、4) 上送出一個 commit(如圖中黑色的 commit 9),且 commit message 中包含 bugfix 字樣
● Jenkins 發現該 bugfix 的 commit 後立即執行建構,将
spark-src.git/dev
spark-bin.git/dev
spark-${ build # }
(如圖中的 spark-3) 檔案夾内,spark 作為 symbolic,指向該
spark-${ build # }
● 通過
git checkout master
切換到
spark-src.git/master
,再通過
git rebase dev
将 bugfix 的 commit rebase 到
spark-src.git/master
,如果 rebase 發生沖突,通過告警通知開發人員人工介入處理沖突
● 在一個 release 周期内,如發現多個 dev 版本的 bug,都可按上述方式進行 bug fix,且這幾個 bug fix 的 commit 在
spark-src.git/dev
上順序相連。是以它們被 rebase 到
spark-src.git/master
後仍然順序相連
Continuous Delivery Solution 3 bugfix
spark-src.git/prod
中送出一個 commit,且其 commit message 中包含 hotfix 字樣
● Jenkins 發現該 commit 為 hotfix,立即執行建構,将
spark-src.git/prod
spark-bin.git/prod
spark-${ build # }
spark-${ build # }
git checkout master
spark-src.git/master
,再通過
git rebase prod
将 hotfix rebase 到
spark-src.git/master
● 在一個 release 周期内,如發現多個 prod 版本的 bug,都可按上述方式進行 hot fix
Continuous Delivery Solution 3 hotfix
灰階釋出本文介紹的實踐中,不考慮多個版本(經實踐檢驗,多個版本維護成本太高,且一般無必要),隻考慮一個 prod 版本,一個 dev 版本
上文介紹的持續釋出中,可将
spark-bin.git/dev
部署至需要使用最新版的環境中(不一定是 Staging 環境,可以是部分生産環境)進而實作 dev 版的部署。将
spark-bin.git/prod
部署至需要使用穩定版的 prod 環境中
復原機制本文介紹的方法中,所有 release 都放到
spark-${ build \# }
中,由 spark 這一 symbolic 選擇指向具體哪個 release。是以復原方式比較直覺
● 對于同一個大版本(dev 或者 prod)的復原,隻需将 spark 指向 build # 較小的 release 即可
● 如果是将部分環境中的 prod 版遷至 dev 版(或者 dev 版改為 prod 版)後,需要復原,隻需将 dev 改回 prod 版(或者将 prod 版改回 dev 版)即可
spark-src.git/master
上進行,Staging 環境的 bug fix 在
spark-src.git/dev
上進行,生産環境的 hot fix 在
spark-src.git/prod
上進行,清晰明了
● bug fix 送出時的 code base 與 Staging 環境使用版本的 code 完全一緻,進而可保證 bug fix 的正确性
● bug fix 合并回
spark-src.git/master
時使用 rebase,進而保證了
spark-src.git/dev
spark-src.git/master
所有 commit 的順序與内容的一緻性,進而保證了這兩個 branch 的一緻性
● hot fix 送出時的 code base 與 生産環境使用版本的 code 完全一緻,進而可保證 hot fix 的正确性
● hot fix 合并回
spark-src.git/master
spark-src.git/dev
spark-src.git/master
所有 commit 的順序性及内容的一緻性,進而保證了這兩個 branch 的一緻性
● 開發人員隻需要專注于新 feature 的開發,bug fix 的送出,與 hot fix 的送出。所有的版本維護工作全部自動完成。隻有當 bug fix 或 hot fix rebase 回
spark-src.git/master
發生沖突時才需人工介入
●
spark-bin.git/dev
spark-bin.git/prod
将開發版本與生産版本分開,友善獨立部署。而其路徑統一,友善版本切換與灰階釋出
● 在本地
spark-src.git/master
送出時,須先 rebase 遠端分支,而不應直接使用 merge。在本方案中,這不僅是最佳實踐,還是硬性要求
● 雖然 bug fix 與 hot fix commit 都通過 rebase 進入
spark-src.git/master
。但發生沖突時,需要相應修改
spark-src.git/master
上後續 commit。如上圖中,送出紅色 commit 9 這一 hot fix 後,在 rebase 回
spark-src.git/master
時,如有沖突,可能需要修改 commit 2 或者 commit 3、4、5。該修改會造成本地解決完沖突後的版本與遠端版本沖突,需要強制 push 回遠端分支。該操作存在一定風險
Spark CD 持續部署持續部署是指,軟體通過評審後,自動部署到生産環境中
Continuous Deploy
上述 Spark 持續釋出實踐的介紹都隻到 "将 *** 送出到
spark-bin.git
" 結束。可使用基于 git 的部署(為了性能和擴充性,一般不直接在待部署機器上使用 git pull --rebase,而是使用自研的上線方案,此處不展開)将該 release 上線到 Staging 環境或生産環境
該自動上線過程即是 Spark 持續部署的最後一環
原文釋出時間為:2018-10-9
本文作者:郭俊 Jason Guo
本文來自雲栖社群合作夥伴“
大資料架構”,了解相關資訊可以關注“
”。