做者:郝闊君
GrowingIO QA Leader,曾任職於中國惠普、奇虎 360。帶領 QA 團隊負責 GrowingIO 全產品線質量保證工做,目前專一於 DevOps 實踐,幫助團隊提高質量和效率。
本文主要描述 GrowingIO 過去在 SaaS 產品線上 CI/CD 的一些實踐。因爲歷史緣由公司使用的部分工具鏈比較小衆,當前的 CI/CD 的流程還有很大的改進空間 ,可是一些實踐經驗仍是有必定借鑑意義的。前端
CI/CD 是一種經過在應用開發階段引入自動化來頻繁向客戶交付應用的方法。CI/CD 的核心概念是持續集成(Continuous Integration)、持續交付(Continuous Delivery)和持續部署(Continuous Deployment),業界對 CI/CD 理解以下。git
持續集成是一種開發實踐,在持續集成環境中,開發人員將會頻繁地向主幹提交代碼,這些新提交的代碼在最終合併到主幹前,須要通過編譯和自動化測試流進行驗證。github
持續集成是在源代碼變動後自動檢測、拉取、構建和(在大多數狀況下)進行單元測試和靜態質量分析的過程。api
持續集成的目標是快速確保開發人員新提交的變動是好的,而且適合在代碼庫中進一步使用。CI 的流程執行和理論實踐讓咱們能夠肯定新代碼和原有代碼可否正確地集成在一塊兒。安全
完成 CI 中構建及單元測試和集成測試的自動化流程後,持續交付可自動將已驗證的代碼發佈到存儲庫。爲了實現高效的持續交付流程,務必要確保 CI 已內置於開發管道。持續交付的目標是擁有一個可隨時部署到生產環境的代碼庫。服務器
在持續交付中,每一個階段(從代碼更改的合併,到生產就緒型構建版本的交付)都涉及測試自動化和代碼發佈自動化。在流程結束時,運維團隊能夠快速、輕鬆地將應用部署到生產環境中或發佈給最終使用的用戶。架構
對於一個成熟的 CI/CD 管道(Pipeline)來講,最後的階段是持續部署,持續部署能夠自動將應用發佈到生產環境。運維
持續部署意味着全部的變動都會被自動部署到生產環境中,可是出於業務考慮,能夠選擇不部署。若是要實施持續部署,必須先實施持續交付。微服務
持續交付並非指軟件每個改動都要儘快部署到產品環境中,它指的是任何的代碼修改均可以在任什麼時候候實施部署。持續交付表示的是一種能力,而持續部署表示的則是一種方式。持續部署是持續交付的最高階段。工具
實現一套 CI/CD 流程主要有如下幾種途徑:
不少成熟的產品都提供完善的 DevOps 功能,如 Atlassian、微軟的 Azure DevOps、阿里的雲效、coding.net 等等。若是是全新的初創公司能夠選擇購買合適的產品和服務,能夠藉助 DevOps 產品提供的工具和最佳實踐,快速搭建起一套完善的 DevOps 體系。不少 DevOps 服務都是跟雲廠商集成的,若是已經使用了對應的雲服務產品,再使用對應的 DevOps 服務其成本並不高。
這是目前多數公司最多見的一種作法,將公司內部的項目管理,代碼管理,製品管理等工具集成起來,或作一些簡單的二次開發實現一套完整的 CI/CD 流程。這種方式整體成本可控,利用現有的基礎設施進行改造,方便靈活,能夠根據公司本身的流程來定製。
多數公司都是採用這一種方式,由於多數公司創立之初,必然是先生存後發展,並不會把工程效率放在第一位,會選用一些單獨的工具(如代碼管理)解決某一領域的問題。隨着公司發展纔會更多的考慮工程效率,此時將現有工具集成起來顯然是最多見的選擇,GrowingIO也不例外。
目前也有一些開源產品,已經基於各類開源產品作了二次開發集成,造成了完整的 DevOps 平臺,如豬齒魚。若是不想購買雲服務,又不想作二次開發,能夠嘗試此類工具。
不過值得注意的是,使用開源工具的資源投入並不必定會少,尤爲是出現問題須要解決,或者說工具不能知足自身須要時須要二次改造,都須要對工具進行深刻的研究和了解,因此選擇這類產品也要考慮是否與公司當前的技術棧匹配,是否有能力進行維護。
本身新造輪子,重頭開始設計開發實現一套。顯然這種方式是投入成本最高的,但也是最靈活,最能符合企業自身須要的,通常都是一些特大型的企業纔會花費資源來研發。好比上面第一種提到的一些雲服務產品起初都是企業內部本身研發使用,成熟之後做爲產品對外提供服務,從而獲取更多經濟回報。
工欲善其事必先利其器,要實現一個經濟高效的 CI/CD 流程選擇合適的工具能夠實現事半功倍的效果。一個典型的 CI/CD 流程建設至少須要具有如下功能的工具。
GrowingIO 成立之初就選擇了私有化部署 Phabricator 做爲開發協做工具,Phabricator 是一款與 GitHub、GitLab 等相似的強大的軟件開發協做工具,其支持Git、Mercurial、Svn 三種代碼倉庫託管、代碼 Review、命令行工具、任務管理、看板、Wiki、自動規則,WebHook 和 API 等多種功能。
目前能夠實現持續集成的自動化工具備不少,其中最流行開源工具莫過於 Jenkins 。Jenkins 支持多種任務類型,1000+ 插件,社區和生態都比較完善。Jenkins 2.0 版本提供了 Pipeline as Code 的特性,能夠將 CI/CD Pipeline 的定義歸入版本管理,支持 GitOps。此外,Jenkins 支持多種方式實現的主從架構,其中藉助 Kubernetes 強大的編排和調度能力而實現的主從模式,能夠實現從節點的動態建立和銷燬,極大的提高了 CI 執行效率和資源利用率。
質量管理平臺 SonarQube
SonarQube 是一款代碼質量管理平臺,經過 SonarQube 能夠檢測出項目中潛在的 Bug、安全漏洞、代碼規範、重複代碼、缺少單元測試等代碼質量問題,並提供了 UI 界面進行查看。能夠經過控制相關代碼質量指標的閾值,保持和提高代碼質量。使用 SonarQube 免費的社區版,就已經能夠知足絕大多數的項目需求,並且它還很容易跟第三方的 CI/CD ,Code Review 工具集成。
Nexus 是 Sonatype 開發的一款製品倉庫管理工具,支持 Maven,NPM,PyPI,Docker,Helm 等數十種製品的管理,此外他還支持 Webhook,REST API 能夠很方便的與第三方工具集成。Nexus 的免費的開源版本(即 Nexus Repository OSS)所提供的功能已經能夠知足咱們的絕大多數需求。
Capistrano 是一款 Ruby 語言實現的,免費開源的遠程服務器自動化管理工具。Capistrano 是基於 SSH 的免代理模式運行的,只須要安裝一個客戶端便可輕鬆的實現多臺服務的管理。Capistrano 提供了一套用於部署和回滾的 DSL 和工做流,能夠很容易的實現服務的遠程自動部署和回滾。同時還能夠很容易經過自定義插件或腳本擴展功能,實現個性化需求。
此外除了以上工具還使用了 Kubernetes,Docker 等工具。這裏提到工具所提供的功能都有不少其餘的替代方案,須要根據公司具體的技術棧,當前使用工具,工程實踐,部署方式等多種因素選擇。
源代碼管理做爲 CI/CD 的起點,會與 CI/CD Pipeline 深度的集成,不一樣的代碼分支策略會影響 CI/CD Pipeline 的設計實現,因此在開始設計 CI/CD 以前必須根據公司當前的協做流程設計好分支管理策略。
Git 分支管理的方案有不少,他們各有優缺點,都有各自的試用場景,其中比較出名的有 git-flow 、trunk-based 、github-fow,各個企業也會自定製本身的分支策略,如阿里巴巴的 AoneFlow 。
GrowingIO 傾向於使用 Trunk-based 的分支策略,其最理想狀態的是「主幹開發,主幹發佈」,要作到這一步對代碼開發的質量要求至關高。
目前公司的開發實踐還沒法達到這樣的要求,因此退而求其次使用「分支開發,主幹發佈」,當分支生命週期很短時,基本不會產生代碼合併衝突,就基本等同於主幹開發。在主幹分支出現 Bug 影響發佈的時候也會臨時採用分支發佈的策略。
Master 分支是最新的代碼集成主幹分支,同時是發佈(Release)分支,通常當代碼經過充分測試後才能進入到 Master 分支,Master 分支是隨時可發佈狀態。
該分支禁止直接 git push,必須經過提交修改 Diff (等同於 Github 的 PR,Gitlab 的 MR,是 Phabricator 中進行代碼 Review 的單元) ,經過 Code Review 和測試驗收後,才能合併。
Release 分支是一個臨時分支,主要是爲了解決有些狀況下 Master 分支不知足上線條件可是又須要緊急上線的時候使用。
例如上線過程當中發現某一個修改引入了比較嚴重的 Bug,爲了避免影響其餘修改的正常發佈,一般會將有問題的 Commit 剔除掉,而後建立一個臨時分支繼續發佈。
功能開發分支或集成測試分支,是開發過程當中最活躍的分支,每次提交到該分支的代碼都會被集成。在微服開發模式下,開發人員很難在本地搭建完整的集成環境,所以須要雲端的集成測試環境來幫助開發完成聯調測試。
若是在代碼進入主幹分支以前經過集成測試,QA 驗收,和產品驗收,無疑會大大下降代碼進入主幹分支的風險。固然要保證 Feature 分支的生命週期足夠短,這樣能夠大大的避免代碼合併衝突。
Trunk-based 反對使用長生命週期的 Feature 分支,鼓勵使用特性開關和抽象分支技術來儘快的合併代碼到主幹分支,同時又不影響主幹分支功能。
開發者本地分支,之因此是本地分支是由於 Phabricator 容許在不建立遠程分支的狀況下提交 Diff 進行代碼 Review 和部署。
開發分支的代碼通過本地調試和單元測試後會基於主幹(Master 分支)或 特性分支(Feature 分支)提交 Diff 進行 Code Review ,Review 經過後進入對應的 Base 分支。
一般要實現一個比較大的 Feature 須要數據端多個組件,服務端多個微服務以及前端協同開發才能實現。EM (工程經理,Engineer Manger)在 Sprint 計劃時將 Feature Ticket 拆分紅多個子任務,交由不一樣的開發來實現。
TIPS:每次提交 Diff 以前都應該與遠程 base 分支進行 Rebase 或 Merge 操做,防止代碼合併時衝突。
以上流程看似複雜,不過藉助 Phabricator 的命令行工具 Arcanist 能夠很輕鬆的實現。
這裏有個問題是 Feature 分支與主幹分支會有必定差別,如上圖 Feature 1 分支建立之後,Master 分支新的變動並不會進入到 Featrue 1,這個能夠經過 Featrue 1 分支在 CI 流程中自動 Merge 主幹分支(在本地不會推送到遠程),若是合併失敗則 CI 流程失敗,開發人員手工更新 Feature 1 分支,同步 Master 代碼。
Hotfix 開發流程與 Feature 相似,直接基於 Master 分支建立本地開發分支,開發經過後提交 Diff 進行,代碼靜態檢查,單元測試,Code Review 和 QA 測試,經過後合併進入 Master 分支,發佈上線。
有些狀況下當 Master 分支已經合併了新的 Feature ,這時有緊急的 Hotfix 須要發佈時,須要用到 Release 分支。能夠基於上次上線的 Commit 建立一個 Release 分支,而後將 Hotfix 從 Master cherry-pick 到 Release 分支發佈。
這時要保留 Release 分支,以防有新的 Hotfix 須要發佈,直到下次 Master 分支發佈後就能夠刪除 Release 分支。由於咱們的 SaaS 服務,發佈頻率很高,這種狀況較少發生。
在 GrowingIO 老是存在多個 Feature Team 在並行開發不一樣的功能,每一個 Feature Team 都有本身獨立的開發聯調環境(對應到 Feature 分支)。
開發提交代碼到 Feature 分支,Jenkins 自動監測到對應代碼倉庫的代碼分支變動,而後啓動對應的任務進行代碼的靜態檢查,單元測試,編譯,打包,並部署到開發聯調環境。
對於前端和服務端應用是打包成 Docker 鏡像進行部署在對應分支的 K8S 環境,對於數據端應用是打包成 zip 包部署到基於 VM 的環境。
同一個 Feature 的代碼都提交後,開發在開發環境進行聯調測試。其關鍵過程:Git Push → Code Check → Unit Test → Build → Deploy → Integrated Testing。聯調測試經過後提交 Diff ,觸發下面流程。
開發提交 Diff 之後經過 Phabricator 定義的自動規則,自動調用 Jenkins 的 Webhook 觸發對應的 Jenkins 任務進行與主幹分支合併(並不提交到遠程分支,只是檢查有沒有衝突)、代碼靜態檢查、單元測試,Sonar 掃描、並經過 Jenkins 的 Phabricator 插件,將單測覆蓋率等信息自動的添加到對應 Diff 的評論中。
若是上面的自動檢查步驟失敗或單測覆蓋率不達標,則須要對應的開發修改,直到經過自動檢查(固然有時候有一些緊急的 Bugfix 須要快速發佈,也會放寬這一限制)。
當自動檢查經過之後便可通知組內的其餘開發人員進行人工代碼審覈,若是審覈經過則經過 Phabricator 將 Diff 標記爲 Accepted,此時 Phabricator 中定義的自動規則會自動的將 QA Team 添加到 Diff 的 Blocked Reviewer (若是 Blocked Reviewer 尚未 Accepted ,Diff 是沒法合併到主幹分支的) ,並經過郵件通知 QA 人員進行測試。
QA 人員根據須要會將對應的 Diff 經過手動運行 Jenkins 任務部署到對應的 QA 環境進行測試,測試經過之後會再次在 Phabricator 將 Diff 標記的 Accepted,並通知對應的開發 Land 代碼(Land 是 Phabricator 的一個術語,即將代碼合併到主幹分支,並將 diff 關閉,等同於 Github/Gitlab 的 PR/MR merge),固然 QA 也能夠直接 Land 代碼。其關鍵過程:Create Diff → Auto Check → Peer Review → QA Review → Land。
下面是上述過程的簡單流程架構圖:
通過上面的 CI 流程之後,理論上已經進入到主幹分支的代碼是隨時能夠部署的狀態。但在實際實踐中出於對質量保證和發佈成本的考慮,會控制發佈節奏。
咱們採用的是基於固定發佈週期的敏捷發佈火車模式(Agile Release Train, ART),正常每週一個正式版本(區別於 Hotfix 版本)發佈,通常在週二晚上部署。
上面也提到了,GrowingIO 是多個 Feature Team 並行開發,雖然每一個 Feature 提交的代碼都是通過測試才進入到主幹分支,可是在一個發佈週期內多個團隊可能對同一個代碼倉庫做出修改,多個 Diff 相互影響可能引入新的缺陷。
還有就是在 Diff 測試的時候通常會關注在 Diff 自身的修改可能影響到的部分,雖然開發、測試會盡量的分析其影響範圍,但受制於每一個人的知識以及系統的複雜性,一個 Diff 修改可能引發徹底想不到的地方出現缺陷。
因此,在發佈前必須對要發佈的功能進行比較全面的迴歸測試(在這方面咱們不止一次的犯錯 ̄□ ̄||)。這一回歸過程大概須要整個 QA Team 半天到一天的工做量,迴歸測試中發現的缺陷要求當即修復。
此外,迴歸測試期間會進入 Code Freeze 階段,全部涉及發佈的代碼倉庫禁止開發 Land 代碼到主幹分支,只有 QA 有權限 Land 代碼到主幹分支。
這樣作的主要目是避免已經迴歸測試過的功能,由於新代碼引入 Bug;同時又保證迴歸測試的 Bugfix 可以進入主幹分支。若是迴歸測試中發現的缺陷沒法短期內修復須要將有問題的 Diff 在發佈分支剔除,定位到引入問題的 Diff 而後 Revert。
TIPS:迴歸測試發現缺陷之後爲何不延期發佈?
上面提到了,每次正式版本發佈須要作全面的迴歸測試,若是頻繁發佈必然會致使大量的人力耗費,再者 QA 都去作迴歸測試了,誰來作 Diff 測試?
這個問題解決的方案是提高迴歸測試的效率。一是加大回歸測試的自動化測試覆蓋率,二是採用一些精準測試策略,下降沒必要要的迴歸測試執行。但這些都須要一些資源投入來慢慢建設,短時間內較難達到,因此選擇合理的發佈節奏是最簡單有效的方式。
有些時候,已經進入主幹分支的某個新功能並不但願用戶看到,多是出於市場宣傳的考慮,也多是功能還在迭代中還不夠完善等緣由。這種狀況可使用特性開關(Feature Toggle)控制。
目前 GrowingIO 的 Feature 開關能夠實現,按照客戶組織 ID,客戶項目 ID,用戶郵箱,用戶郵箱後綴,以及自定義腳本規則進行各類精細化的 Feature 控制,這些配置能夠經過修改配置文件實現熱更新。
迴歸測試經過之後,QA 團隊會進行發佈前準備工做,主要包括如下幾方面:
2.生成工程 Release Notes
TIPS:工程 Release Notes 主要經過腳本自動的檢查全部要發佈的代碼倉庫,自動生成,而後有必要時進行簡單的人工調整。
發佈清單準備就緒後 QA 團隊會經過釘釘羣向 SRE 團隊提交上線申請,SRE 團隊收到上線申請後,會檢查上線清單的步驟是否清晰明確,並熟悉發佈內容。若是確認無誤會提早經過 Jenkins 編譯本次要發佈的代碼,提早作好部署準備。
東風速遞,使命必達 🚀,到達預約的發佈時間點之後,SRE 會開始部署。爲了保證部署過程當中服務不中斷,GrowingIO 目前採用的服務端部署方式是藍綠滾動部署方式。
咱們線上有兩個類似的服務端集羣,這兩個集羣中的微服註冊在獨立的 Goup 中,流量相互隔離。部署過程當中會先將 Prod0 集羣下線,此時客戶流量所有進入到剩下的集羣 Prod1,SRE 會按照部署清單更新 Prod0 中的服務,當部署完成後會通知 QA 在 Prod0 集羣作驗證。
QA 驗證經過之後會通知 SRE 團隊,繼續發佈。此時 SRE 團隊會將 Prod0 集羣上線,而後下線 Prod1 集羣,並更新 Prod1 集羣。
Prod1 集羣更新完畢之後會再次通知 QA 在 Prod1 集羣驗證。QA 驗證經過之後會再次通知 SRE 團隊,繼續發佈。SRE 會將 Prod1 集羣上線,並通知部署完成。
這裏須要說明的是數據端的應用發佈採用的是滾動發佈策略,因此是會隨着 Prod0 發佈一塊兒發佈的。
固然,雖然通過了各類前期的準備和測試,可是意外仍是可能發生,不過概率很小。若是在上線過程當中 QA 驗證發現嚴重問題,或者 SRE 經過監控告警發現異常會終止上線,並回滾相關服務。
整個部署和回滾操做都是由 Jenkins 執行 Capistrano 腳本完成的,具體的部署細節並不須要人工干預,多數狀況下,整個部署過程會在 30 分鐘內完成。
部署成功後 QA 團隊會發送 Release 郵件,通知公司全體成員本次發佈的版本中包含的詳細內容,即部署前準備工做中的 「Jira Release Notes」 和「工程 Release Notes」。
前者面向業務人員閱讀,後者面向工程人員,這樣整個公司的人員均可以及時瞭解到本身關注的新功能或者 Bugfix 是否上線了。
上述 CI/CD 流程還有不少不足,最重要的問題有如下幾點。
目前 GrowingIO SaaS 服務的發佈,不管是部署到 Dev, QA, Staging 仍是 Prod 環境都是基於大代碼分支從新編譯而後部署。每次基於源代碼發佈的問題在於:
這個問題比較容易解決,例如在 GrowingIO 私有化部署產品線實現的 CI 流程,已經實現了基於二進制包的交付,從而實現了一次編譯,多套環境部署,固然,這裏有個基本前提是代碼與配置分離。SaaS 沒有當前進行改造的主要緣由是打算等生產環境採用基於 Kubernetes 容器雲部署方式的時候一併解決。
高效的 CI/CD 流程離不開自動化測試的支持,目前 GrowingIO 整個 SaaS 產品的單元測試,API 自動化測試,UI 自動化測試覆蓋率還不夠完善,致使整個流程對手工檢查的依賴太高,效率偏低。這個依賴於公司在在質量保證方面的持續投入,來逐步改善,沒有捷徑可走。
目前整個 CI/CD 的流程分紅了 Feature CI 流程,Diff CI 流程,部署流程,三個流程經過郵件通知,釘釘消息,不一樣的 Jenkins 任務串聯起來。隨着公司規模的擴大,這樣的協做流程會變得愈來愈混亂低效,並且很難作到數據的收集度量。
解決這個問題的有效方法是開發一個工具平臺將流程和工具集成起來,提供統一的管理入口、流程規範,並實現數據的自動採集分析。
上面提到爲了保證發佈過程用戶服務不中斷,採用了滾動藍綠部署的方式。這種方式部署時是將一半集羣全部微服下線,此時線上提供的最大服務能力減半,在用戶使用高峯期,這種操做顯然風險極高,限制了發佈時間窗口的選擇。
此外,每次升級單個微服務的時候都要將整個一半集羣下線,顯然也是不夠合理的。目前的計劃是在生產環境使用基於 Kubernetes 的容器雲部署方式來解決這一問題。
本文主要介紹了 CI/CD 的概念,以及 GrowingIO SaaS 產品在內部採用的各類工具,以及在 CI/CD 上的具體實踐。正如上面所述,咱們目前距離成熟的 CI/CD 還有必定差距。甚至有些地方並無遵照最佳實踐,好比基於源代碼的發佈。
可是這些實踐都是根據公司的實際狀況一步步創建完善起來的,但願對你們有一些啓發。另外本文主要描述了整個 CI/CD 的宏觀流程,後續的文章會繼續介紹一些具體的工具配置使用方法,以及 GrowingIO 在私有化部署產品中 CI/CD 的一些改進。
關於 GrowingIO
GrowingIO 是國內領先的一站式數字化增加總體方案服務商。爲產品、運營、市場、數據團隊及管理者提供客戶數據平臺(CDP)、廣告分析、產品分析、智能運營等產品和諮詢服務,幫助企業在數字化轉型的路上,提高數據驅動能力,實現更好的增加。
點擊「此處」,獲取 GrowingIO 15 天免費試用!