知乎容器化構建系統:從0到1支撐日近萬次構建部署
作者:Amyyyyy 原文:
知乎應用平臺團隊基於Jenkins Pipeline和Docker打造了一套持續整合系統。Jenkins Master和Slave基於Docker部署,每次構建也是在容器中進行。目前有三千個Jenkins Job,支撐著整個團隊每日近萬次的構建和部署量。
整個系統的設計目標是具備以下的能力:
較低的應用接入成本,較高的定製能力:寫一個構建系統配置檔案成本要儘可能簡單方便,或者可以透過模板一鍵建立,但又要能滿足應用的各種定製化的需求。
具備語言開放性和部署多樣性:平臺需要能支撐業務技術選型上的多語言,同時,要能滿足應用不同的部署型別,如單純的打包釋出,或者進一步部署到物理機、容器、離線任務平臺等。
構建快和穩定,復現問題成本低:每次構建都在乾淨的容器中,減少非應用本身問題帶來的構建異常。同時,如果構建出現問題,在許可權控制的前提下,要能方便開發者自己除錯和排查。
推動業界標準以及最佳實踐,同時在程式碼合併之前就能更好把控住質量。
整個叢集高可用,可擴充套件,以及具備較低的運維成本。
一、背景
知乎選用Jenkins作為構建方案,因其強大和靈活,且有非常豐富的外掛可供使用和擴充套件。
早期,應用數量較少時,每個開發者都手動建立並維護著幾個Job,各自編寫Jenkins Job的配置,以及手動觸發構建。隨著服務化以及業務型別,開發者以及Jenkins Job數量的增加,我們面臨了以下的問題:
每個開發者都需要去理解Jenkins的基本配置和觸發邏輯,使得配置建立和維護成本高。
構建在物理機上進行,每個應用可能有著不同的版本依賴,構建時會遇到版本衝突,甚至上線之後發現行為不一致導致故障等。
構建一旦失敗,需要開發者能登入Jenkins Slave所在的物理機進行除錯,許可權控制成為了一個問題。
於是,一個能方便應用接入構建部署的系統,成為了必須。
二、完整的生命週期
知乎的構建工作流主要是以下兩種場景:
只有Master分支的程式碼可以用於線上部署,但支援指定任意的分支進行構建。
所有對Master分支的修改必須透過Merge Request來進行。為了避免潛在程式碼衝突導致測試結果不準的情況,對Merge Request上的程式碼進行構建前,會模擬跟Master分支的程式碼做一次合併。
一個Commit從提交到最後部署,會經歷以下的環節:
開發者提交程式碼到GitLab。
GitLab透過Webhook通知到ZAE(Zhihu App Engine,知乎的私有云平臺)。
ZAE將構建的上下文資訊,如GitLab倉庫ID,ZAE應用資訊給到構建系統Lavie。目前只處理使用者提交MR以及合併到Master分支的事件。
構建系統Lavie讀取應用倉庫中的配置檔案後生成配置,觸發一個構建。在構建過程中獲取動態生成的Jenkinsfile,生成Dockerfile構建出應用的映象,並跑起容器,在容器中執行構建,測試等應用指定的步驟。
測試成功之後,分別往物理機部署平臺,容器部署平臺,離線任務平臺上傳Artifact,註冊待發布版本的資訊,並Slack通知使用者結果。
構建結束,使用者在ZAE上可以進行後續操作,如選擇一個候選版本進行部署。
每個應用的拉取程式碼,準備資料庫,處理測試覆蓋率,傳送訊息,候選版本的註冊等通用的部分,都會由構建系統統一處理,而接入構建系統的應用,只需要在程式碼倉庫中包含一個約定格式的配置檔案。
三、達到的目標以及中間遇到的問題
構建系統去理解應用要做的事情靠的是約定格式的yaml配置檔案,而我們希望這個配置檔案能足夠簡單,宣告上必要的部分,如環境、構建、測試步驟就能開始構建。
同時,也要有能力提供更多的定製功能讓應用可以使用,如選擇系統依賴和版本,快取的路徑,是否需要構建系統提供MySQL以及需要的MySQL版本等,以及可以根據應用的類別自動生成配置檔案。
一個最簡單的應用場景:
base_image: python2/jessie build: - buildout test: unittest: - bin/test --cover-package=pin --with-xunit --with-coverage --cover-xml
一個更多定製化的場景:
base_image: py_node/jessie deps: - libffi-dev build: - buildout - cd admin && npm install && gulp test: deps: - mysql:5.7 unittest: - bin/test --cover-package=lived,liveweb --with-xunit --with-coverage coverage_test: report_fpath: coverage.xml post_build: scripts: - /bin/bash scripts/release_sentry.sh artifacts: targets: - docker - tarball cache: directories: - admin/static/components - admin/node_modules
為了儘可能滿足多樣化的業務場景,我們主要將配置檔案分為三部分:宣告環境和依賴、構建相關核心環節、宣告Artifact型別。
宣告環境和依賴:
image,基礎映象,需要指明已提前準備好的語言映象。
deps,dependencies的簡寫,宣告使用的系統依賴以及對應的版本。
構建相關核心環節:
build,構建的步驟,如buildout、npm install、或者執行一個指令碼。
test,測試環節,應用需要宣告構建的步驟,也可以在這裡定製使用的MySQL以及對應的版本。構建系統會每次為其建立新的資料庫,將關鍵資訊export為環境變數。
post build,最後一個環節,如發包、發Slack、郵件通知、或釋出一個Sentry release等。
宣告Artifact型別:
artifact,用於選擇部署的型別,目前支援的有:
tarball:構建系統會將整個應用Workspace打包上傳到HDFS用於後續的物理機部署。
docker:映象會被push到私有的Docker Registry用於容器部署。
static:應用指定的路徑打包後會被上傳到HDFS,用於後續的靜態資源部署。
offline:應用指定的檔案會被上傳到離線平臺,用於離線任務的執行。
早期所有的構建都在物理機上進行,構建之前需要提前在物理機上安裝好對應的系統依賴,而如果遇到所需要的版本不同時,排程和維護的成本就高了很多。
隨著團隊業務數量和種類的增加,技術選型的演進,這樣的挑戰越來越大。於是構建系統整體的最佳化方向由物理機向Docker容器化前進。
如今,所有構建都容器中進行,基礎的語言映象由應用自己選擇。目前映象管理的方式是:
我們會事先準備好系統的基礎映象。
在系統映象的基礎上,會構建出不同的語言映象供應用使用,如Python,Golang,Java,Node,Rust的各種版本以及混合語言的映象。
在應用指定的image語言映象之上,會安裝上deps指定的系統依賴,再構建出應用的映象,應用會在這個環境裡面進行構建測試等。
語言這一層的Dockerfile會被嚴格review,透過的映象才能被使用,以更好了解和支援業務技術選型和使用場景。
快取的設計
最開始構建的快取是落在對應的Jenkins Slave上的,隨著Slave數量的增多,應用構建被分配到不同Slave帶來的代價也越來越大。
為了讓Slave的管理更加靈活以及構建速度和Slave無關,我們最後將快取按照應用使用的映象和系統依賴作為快取的標識,上傳到HDFS。在每次構建前拉取,構建之後再上傳更新。
針對映象涉及到的語言,我們會對常見的依賴進行快取,如eggs、node_modules,.ivy2/cache、.ivy2/repository。應用如果有其他的檔案想要快取,也支援在配置檔案中指定。
依賴獲取穩定性
在對整個構建時間的開銷和不穩定因素的觀察中,我們發現拉取外部依賴是個非常耗時且失敗率較高的環節。
為了讓這個過程更加穩定,我們做了以下的事情:
完善內部不同語言的源。
在不同語言的基礎映象中放入優先使用內部源的配置。
搭建HTTP Proxy,提供給以上覆蓋不到的場景。
更低的排查錯誤的成本
本地開發和構建環境存在明顯的差異,可能會出現本地構建成功但是在構建系統失敗的情況。
為了讓使用者能夠快速重現,我們在專案docker-ssh的基礎上做了二次開發,支援直接ssh到容器進行除錯。由於容器環境與其他人的構建相隔離,我們不必擔心ssh許可權導致的各種安全問題。構建失敗的容器會多保留一天,之後便被回收。
我們希望能給接入到構建系統的提高效率的同時,也希望能推動一些標準或者好的實踐,比如完善測試。
圍繞著測試和測試覆蓋率,我們做了以下的事情:
配置檔案中強制要有測試環節。
應用測試結束之後,取到程式碼覆蓋率的報告並打點。在提交的Merge Request評論中會給出現在的值和主分支的值的比較,以及最近主分支程式碼覆蓋率的變化趨勢。
在知乎有應用重要性的分級,對於重要的應用,構建系統會對其要求有測試覆蓋率報告,以及更高的測試覆蓋率。
對於團隊內或者業界的基礎庫,如果發現有更穩定版本或者發現有嚴重問題,構建系統會按照應用的重要性,從低到高提示應用去升級或者去掉對應依賴。
5、高可用和可擴充套件的叢集
Job排程策略
Jenkins Master只進行任務的排程,而實際執行是在不同的Jenkins Node上。
每個Node會被賦予一些label用於任務排程,比如:mysql:5.6, mysql:5.7, common等。構建系統會根據應用的型別分配到不同的label,由Jenkins Master去進一步排程任務到對應的Node上。
高可用設計
叢集的設計如下,一個Node對應的是一臺物理機,上面跑了Jenkins Slave(分別連Master和Master Standby),Docker Deamon和MySQL(為應用提供測試的MySQL)。
Slave連線Master等待被排程,而當Jenkins Slave出現故障時,只需摘掉這臺Slave的label,後續將不會有任務排程排程上來。
而當Jenkins Master故障時,如果不能短時間啟動起來時,叢集可能就處於不可用狀態了,從而影響整個構建部署。為了減少這種情況帶來的不可用,我們採用了雙Master模型,一臺作為Standby,如果其中一臺出現異常就切換到另一臺健康的Master。
監控和報警
為了更好監控叢集的執行狀態,及時發現叢集故障,我們加了一系列的監控報警,如:
兩個Jenkins Master是否可用,當前的排隊數量情況。
叢集裡面所有Jenkins Node的線上狀態,Node被命中的情況。
Jenkins Job執行時間,是否有不合理的過長構建或卡住。
以及叢集機器的CPU、記憶體、磁碟使用情況。
四、後續的計劃
在未來我們還希望完善以下的方面:
Jenkins Slave能更根據叢集的負載情況進行動態擴容。
一個節點故障時能自動下掉並重新分配已經在上面執行的任務。一個Master down掉能被主動探測到併發生切換。
在Merge Request的構建環節推動更多的質量保證標準實施,如更多的介面自動化測試,減少有問題的程式碼被合併到主分支。
Jenkinsfile 相關文件 :
<Jenkins Logo>:
<Docker Logo>:Brand Guidelines
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555484/viewspace-2629920/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從0到1構建上門護理
- SpringBoot開發案例從0到1構建分散式秒殺系統Spring Boot分散式
- 從0到1搞懂分散式架構:Uber大型支付系統構建經驗總結分散式架構
- 玩轉直播系列之從 0 到 1 構建簡單直播系統(1)
- 如何從 0 到 1 設計、構建移動分析架構架構
- 從0到1構建策略卡牌養成框架框架
- 從 0 開始構建知識圖譜的 5 個啟動建議
- Docker折騰記: (1)構建yapi容器,從構建釋出到可用DockerAPI
- 構建自己知識體系
- StarRocks 容器映象構建
- 如何構建分散式系統的知識體系分散式
- 基於 OPLG 從 0 到 1 構建統一可觀測平臺實踐
- 從0到1構建基於自身業務的前端工具庫前端
- 構建自己的知識體系
- 為什麼建議將安全性構建到系統中?
- 如何從零構建直播系統(後端篇)後端
- C++構建工具-構建系統C++
- OCI 與容器映象構建
- 構建演算法治理落地支撐體系演算法
- 【React 實戰教程】從0到1 構建 github star管理工具ReactGithub
- 從 0 到 1 再到 100, 搭建、編寫、構建一個前端專案前端
- 智慧金融系統的構建
- 根檔案系統構建
- 如何構建推薦系統
- 實踐:GNU構建系統
- 從0到1,馬蜂窩大交通團隊如何構建高效研發流程體系?
- 如何構建自己的知識體系
- 使用LangGraph構建多Agent系統架構!架構
- docker 構建java 部署包DockerJava
- 構建演算法治理落地支撐體系BKQ演算法
- 1. 資訊系統建設-知識結構梳理
- 從資訊到數字,構建企業精益化管理
- Azure Devops實踐(5)- 構建springboot專案打包docker映象及容器化部署devSpring BootDocker
- 0編碼構建AI模型AI模型
- PostgreSQL構建通用標籤系統SQL
- spring+springmvc+mybatis構建系統SpringMVCMyBatis
- 嵌入式Linux系統構建Linux
- AlertManager解析:構建高效告警系統