攜程持續交付與構建平臺實踐之路
作者簡介
周光明
攜程旅行網
高階技術經理
我的演講主題是《攜程持續交付與構建平臺實踐》。
首先看一下攜程目前持續交付的簡介。我們現在有 8000 多個應用,整體研發人員大概有 3000 多位,每天在各個環境上部署的次數有 6000 多次,因此持續交付對於我們來說是一個非常重要的能力。
1. 攜程持續交付簡介
我們先看一下持續交付對於我們的意義,首先是效率的提升,我們知道部署是一個很麻煩的事情,如果說你有多個環境需要部署,那部署的難度也會直線上升。而這個時候如果我們有一個工具去做這樣的事情,研發人員就可以將更多的精力投入到研發它的功能上面,讓產品的迭代更加迅速。
第二是質量保障,我們在持續交付的過程中穿插了一些程式碼掃描、單測或者整合測試的過程,可以讓整個產品的質量在交付過程中得到很好的保障,也可以讓我們在交付的時候更加有信心。
第三是安全可靠,如果沒有這個機器就要人工跑上去進行部署,就會對線上系統增加很多誤操作的隱患。如果大家知道的話,攜程之前也是有過一段非常慘痛的經歷的,某個運維人員在線上的操作導致網站大面積的癱瘓,我們對這個印象是非常深刻的。
第四是團隊協作,在傳統交付模型從產品討論到上線需要經過很長時間,有可能出現一個現象,在開發階段的時候開發人員可能在悶頭寫程式碼、測試人員沒有什麼事情做,到了測算階段這個現象又會反過來。如果我們採用小步快走的方式可以讓各個團隊之間的協作更加緊密和緊湊。
最後是流程更加透明,因為我們使用的是統一的規範、統一的工具,在交付過程當中的每個細節都可以被暴露出來。不管誰多寫一個 bug 或者少寫一個單測都會被系統記錄下來,作為後面的依據。
這是目前我們簡單的交付流程,首先是研發人員 Push 程式碼,掃描單測整合測試,再將結果反饋,之後建立一個版本,版本是什麼概念待會再說。
建立版本之後進行打包,再部署到測試環境,部署成功之後我們會同志周邊的自動化測試平臺或者效能平臺,專案測試人員、QA根據測試結果進行審批工作,就可以將專案部署到下一個測試框架或者生產環境當中。
對於研發階段來說,我們目前主要推崇的分支管理模型是 Master 分支和 Feature 分支,多個分支同時進行功能的開發,我們是將其視覺化的。我們之所以這樣做的原因,因為可以讓程式碼的衝突在合併之前就暴露出來並提前解決。
如果這個時候線上有一些緊密的 bug 要修復,也可以通過 Master 提交一個程式碼,但是提交的程式碼也會被合併到之前建立的程式碼當中。
接下來解釋一下剛才提到的版本概念,我們知道很多開源軟體是使用Git Tags作為開源版本的,但是一個 Git Tags 可以快速找到準確的內容。如果一些專案比較複雜,可能會有一些其它程式碼的依賴。 因此我們可以將原始碼打包成一個版本,打包的東西就會比使用Git Tags 更加準確。
但是有些程式碼依賴,不是一個特定版本號,可能是一個範圍,上個月打包出來的結果和今天打包出來的結果就會不一致,如果這樣就會被部署增加很多不穩定的因素。
第三點是在一些比較特殊的專案裡面,除了語言依賴之外,還會有環境依賴。
以前容器沒有出現之前,我們將環境依賴的過程寫到專案原碼的指令碼,通過部署執行多個程式安裝那些依賴。 但是在有了容器之後,我們就可以將環境的依賴也作為版本的因素,容器就可以很好的幫我們解決這個問題。因此一個明確版本的概念,對於交付來說也是相當重要的。
下面介紹一下我們這些年的部署模型的演進。
在2015年之前我們使用虛擬機器做單機多應用的部署。
對於運維來說成本相當高,在2015年的時候我們重新做了一個釋出系統,也是主推單機單應用的部署模型。 到了2016年我們開始研究容器,但是將容器作為一個虛擬機器的方式,我們叫它“胖容器”,以這樣的方式部署應用。
我們的整個部署過程,上面寫的是一個生態環境的部署過程,總體來說還是有一些複雜的。因此我將幾個比較關鍵的概念整理出來。
-
首先是Group,一組暴露同一服務的集合,對於單機單應用,我們理解是從一個機器,Group也是我們部署的基本單元。
-
第二是拉入拉出,我們其中的某個成員是否接受流量和請求,流量可能來自SOB或者訊息系統推送的訊息。
-
第三是堡壘機,是指生產環境Group中第一臺被髮布驗證的機器,有點像金絲雀部署模型中金絲雀的角色。
-
第四是點火,是指應用初始化、預熱、載入資料等過程,我們認為點火成功才是應用部署成功的一個最終狀態。
-
第五是分批,我們將同一個Group分成多個批次進行滾動部署,減少線上變更對於線上的影響。
-
第六是降級,剛才也提到降級的事情,我們可以有一個拉入拉出,比如我們的釋出需要對應用進行拉入拉出,如果這個時候出現了故障,有應用出現線上的緊密Bug,我們可以通過降級的方式忽略拉入拉出,雖然會丟失一些線上流量,但是可以保證應用被成功的部署到生產上,因此也減少了線上的損失。
-
第七是剎車,剎車是如果線上的部署失敗的機器大於一個比例之前,我們都會停止部署行為,人工排查到底是什麼原因,是需要回滾還是修改Bug之後打另外一個版本進行修復。
-
第八是回滾,回滾的概念就不用多說了,我們需要穩定的符合預期的回滾邏輯。
這是部署過程,首先是拉出堡壘機,部署堡壘機成功之後需要點火,進行測試驗證後拉入堡壘機,堡壘機會作為一臺正常機器進行工作。
這些都沒問題之後我們再將剩餘批次進行滾動部署,滾動部署的時候發現部署失敗的機器比較多就需要進行剎車判斷。
目前我們的 PaaS 平臺上支援了測試和生產多個環境的資源管理,這些資源當中既有容器,也有虛擬機器,甚至還有物理機的管理,因此我們的後端需要對接 OpenStack、Mesos 等穩定管理平臺。
目前我們可以將資源放在私有云的多個數據中心,也可以將資源放在像 AWS 公有云之上,因為攜程目前也是希望可以走國際化的趨勢。
下面說一下環境管理,對於功能測試我們有一個FAT的環境,FAT又分成多個FAT環境,可以滿足使用者同時進行多個功能測試的需求。在FAT之上有一個FWS環境,它是一個更加穩定的FAT環境。
對於效能測試我們也是有多套效能測試環境,有點像剛才美團點評的泳道的概念。
FAT環境部署成功之後,這個時候需要 QA 人員的測試驗收,才可以將應用釋出到下一個UAT環境,UAT是一個相對更加接近生產的測試環境。最後是生產環境。
2. 統一構建平臺設計
交付我就簡單的介紹到這裡,因為我當時收到演講的主題是跟 Jenkins 相關的,所以我把後面的一些章節都放在 Jenkins 上面。
我們可以看到剛才的流程圖上很大一部分工作是通過統一構建平臺實現的,接下來我們介紹一下統一構建平臺。我相信在座有很多Jenkins使用者或者愛好者,我先說Jenkins。
首先 Jenkins 非常的方便,一個外包就可以輕鬆搞定部署這件事情。Jenkins已經發展了很多年,非常的成熟穩定,外掛非常豐富,基本上滿足各種各樣的需求。當然這些來自社群活躍人員,強大的 Pipeline 可以將配置轉化成程式碼,也是大大增強了我們的生產力。
但是 Jenkins 也不是完美的,它也有一些問題,就是它的單點故障和單機效能的問題。剛才的主題也講到了這一點,我看臺下的很多同學也比較關心這一點,我主要介紹一下我們是怎麼看待這兩個問題和解決這兩個問題的。
首先是單點故障,很多團隊都是採用一組一備的 Jenkins 模式,如果出現故障的時候需要切換的方式將故障轉移,稍微成熟一點的團隊會用Keepalived+Virtual IP。還有可以將Jenkins打包放在Mesos或K8S上面,也可以購買CloudBees服務,也是比較省心一點的。
解決了單點故障的問題,Jenkins Master的上線總歸是有限的,隨著業務的增長每天的數量越來越成為負擔。官方提供了幾個維度拆分Jenkins Master的方式,分別是從環境、組織結構、產品線、外掛可制定性、人員訪問許可權控制、出現故障時的影響等幾個方面,分別分析了它的利弊。當然每個團隊各自的情況不一樣,需要根據我們各自的情況作出決策。
拆分了之後,我們要預估一下 Jenkins Master 的單臺機器的承載能力,我這邊也是提供了一個比較有意思的公式。
根據研發人員的數量預估Job、Master 和 executors 的數量,根據這個公式大概推導有多少個Job和Master,我們每天大概是12000次構建數量,我們現在管理了20000多個Jobs ,這些Jobs 跑在Jenkins Master上。
因為我們團隊條件一般,所以只能選擇自己做一個平臺滿足我們大量的運維工作,自己動手豐衣足食。
首先看一下構建系統的整體架構,也是一個比較簡單、比較傳統的架構模型,我們也是在上層封裝了一層API層,負責各種型別的構建請求,在Worker層,將每種構建型別排程到不同的Jenkins Master上。排程到Jenkins Master之後,就是Jenkins Master發揮自己能力的時候了。
接下來我們稍微看一下Worker層處理了哪些事情,有些人可能會疑惑為什麼我們有這麼多Jobs,我們最早的時候不是按照這樣的方式,是按照每種型別一個Job,這樣的好處是我們可以維護比較少的Job的情況。但是這樣也有一些缺點,比如說我要更新Job配置的時候,影響範圍太大,可能幾千個應用都是依賴Job,可能影響範圍都非常大。
另外大家都共用一個Job,保留也會成問題,因為應用是把之後使用者需要儲存與環境差異,如果都用一個Job space隨時都會被沖掉。其次保留了Worker space之後,我們可以減少程式碼下載的完成度,可以讓每個程序都更快。
當一個Job建立的時候或者每次構建任務進來的時候,我們都會對比當前Job的配置是不是最新的可用的,如果是就將它更新,如果目前線上沒有這個Job,我們就會根據這個模版建立一個新的Job,有了這個機制我們就可以將Jenkins Master作為一個沒有狀態的服務來看待。
接下來我們參考了Labeling模型,可以根據標籤匹配找到滿足條件的Master。我們也可以把Job與Master標籤進行匹配。做了之後下面的工作就會比較容易,我們在系統中同時註冊了多個Master,勢必有多臺Master滿足構建條件。
我們在Master上配置了容量配比,比如說新建Job時按照Master容量可以承接多少數量。最後是故障轉移,當構建進來的時候我們看之前用過的Master是不是健康的,如果健康我們會優先把它轉移到這臺上面,如果不健康我們會選擇另外條件滿足的Master。我們也會做Master的檢測,如果某臺Master不健康會拉出整個Master叢集。
我們在多個維度做了監控和告警,第一個維度是作業系統層面的,也就是一些常規的指標,像CPU之類的。
第二個是應用系統層面的,包括API層的可用性、Worker可用性、Jenkins可用性等等。
第三個是業務邏輯層面的,主要檢測的是比如說每一個構建佇列是否堵塞,系統容量是否達到瓶頸,因為我們對每臺Master都做了容量預估,我們希望當有大面積的構建請求進來的時候,我們可以提早知道進行擴容。
Pipeline關鍵Step是否超時,我們進行容器排程的時候,是不是Step建立時間比我們預期的要長等,接下來我會細講如何進行容器排程。
這是構建系統的簡單介面,這是首頁,包含了目前各種狀態的構建數量,還有一些簡單的統計。這是構建系統中所有Job的列表情況,包括它之前構建了多少次、它是什麼型別的。這是所有構建的一個任務情況,這是現在Jenkins Master線上叢集的情況,包括支援的型別情況、監控指標等等。這是容量配比,因為在使用靜態的時候,可以做一個小的配比。構建平臺的介紹就簡單介紹到這裡。
3. Jenkins on K8s 實踐
接下來是我們如何使用K8S進行Jenkins管理。首先是Jenkins整合的演進,跟我們剛才看到的應用整合演進是類似的,但是時間上面稍微比他們快一些,因為在公司級別技術演進的時候經常會使用Jenkins作為一個試驗田,因為它也比較合適。
2016年的時候我們有大量Windows虛擬機器,我們就會做構建,維護這些機器的成本只有自己冷暖自知。我們開始調研了Windows 的能力,在我們這邊也是有不錯的工作現象,但是在業務那邊不買單。因為當時比較牴觸,所以沒有在業務發展下來,我們也有幾臺機器提供服務。
我主要講以下兩個方面,第一是Slave彈性排程、第二是Workspace的問題,我們看一下為什麼存在這兩個問題、如何處理好。
這是單日構建數量以及容量數量趨勢圖。每一種型別根據呼叫頻繁程度和特性,配置出合適的Podidle Minutes引數,控制Slave保留時間。可以看到構建數量趨勢明顯比容器數量趨勢緩和一些,這個時候我們開始下班,構建數量比較延後和緩和一些。我們沒有應用實時的建立和銷燬,希望不要太過頻繁的建立和消毀,既滿足我們對彈性排程的要求,也不會讓整個系統的效能受到太大的影響。
既然我們實現了彈性排程,對於每個Slave建立的時間我們是特別關心的,因為它不像靜態Slave可以有請求進來直接拿來用,還是需要有一段時間給initialDelay Jenkins和Slave進行連線和建立的。但是我們發現採用彈性排程的方式之後,Slave的建立邏輯並不總是符合我們的預期。
舉個例子,當我在空閒的時候,Jenkins可以建立Slave,但是隔了幾秒鐘又有一個新的任務進來了就需要等一段時間。不知道現場有多少個同學發現過這個問題?有嗎?有,既然有這樣的問題,我們就要去面對它。
我們首先梳理了一遍排程的邏輯,通過改變上面的幾個引數,我們是可以達到目的的。接下來介紹一下幾個引數的邏輯,有些人可能是對這個邏輯比較清楚的。首先是initialDelay,它是一個連線的時間引數,我們的initialDelay Jenkins Master是沒有任何靜態Slave的,它可以隨時被銷燬、隨時被建立,所以我們將引數設定成0。
第二個引數是Decay,它是Jenkins負載統計公式中指數移動平均值中的平滑指數,我剛才說的現象是因為Jenkins在內部維護多個負載情況的序列,這些序列的資料有等待佇列數量、各種狀態的數量等等。它們的值是通過EMA公式計算出來的,比如說history0是前一時刻的值,等待佇列數量就是0。
這個時候來了一個數量就是1,假如說decay是0.2,計算出來的值就是0.8。我們可以觀察到decay的值越到,當前負載越接近實際值。因為我們不希望Jenkins的保守建立邏輯增加整體的構建時間,因此我們讓它負載統計中的值更加接近於當前的實際情況。但是這個值也不能太小,如果太小Jenkins就過於敏感,有一個請求過來就幫你建立,還沒建立好下一次還會建立一個,這樣浪費很多資源。
第三個公式不Jenkins判斷是不是建立Slave的不等式,不等式右邊為什麼是1-m呢?m是作為一個引數來用的,如果根據EMA的值計算,它是永遠不會等於1的,只是會無限接近於1,因此我們需要一個偏移量控制它是不是建立Slave。
m的公式也是上面三個引數有關的,首先看一下最後的totalSnapshot,這是當前可以用的數量,這裡這三個引數的來源也是在外掛的文件裡面有寫到,我們使用這三個引數也是可以比較好的工作。
經過長時間的觀察,我們發現這樣一個調整對於建立引數是比較平穩的,也沒有太大波動。我們建立一個Slave大概是20多秒時間,因為採用的排程方式不是立即建立和消毀,所以每天大概有幾十個建立時間,相當於每天的構建數量是可以被接受的。
長時間執行之後,我們還會發現有個別的Slave的建立時間會超過5分鐘,不知道那位同學有沒有這個現象發生?沒有是嗎?這是為什麼呢?一開始我也不知道,所以我們又重新梳理了一下整個建立流程,也是找到了其中的原因。Jenkins的排程邏輯是通過一個輪訓邏輯做的,遍歷Labels。
如果在系統當中這個Labels是沒有出現過的,它要建立一個新的Labels,它是不會更新Labels集合的。但是Jenkins每隔5分鐘會更新一次Labels集合,最後我們也是使用了建立中Label時主動呼叫reset方法。
具體的細節需要在座的再看一下,才能發現是什麼原因。目前已經運行了差不多一年時間,後來剛剛看到那些問題再也沒有出現過了。
接下來講一下Workspace保留問題,什麼是Workspace保留問題?剛剛已經介紹過了,Workspace對於整個系統來說是非常重要的。首先使用者排障需要現場,另外我們通過複用Workspace可以減少下載原始碼的次數。
但是一旦Workspace消毀之後,之前沒有做任何處理,在這個Slave上面所有資料就跟著一起被消毀了。我們首先想到的方式是將上面的資料掛載到Slave上面,消毀也不會有影響,下一次重新被掛載進去。
但是這樣只解決了一個問題,Slave不會被刪,但是通過Jenkins Master怎麼看?我們目前的做法是讓Slave與Master在同一個Node且共享同一Workspace,通過Master檢視Workspace的能力,看看在Master上面執行的其它Workspace。
但是我們遇到一個問題,一個Job同一時間只能在一個Master上面執行,因為我不可能把一個目錄同時給兩個Master,這樣可能會產生無法預期的結果。
我們解決這個問題的方法是,我們通過上層來做一個排程,將之前Jenkins Master控制併發的這一階段放在系統上面。因為我們的Job已經拆分得很細了,因此對於單個Job的併發需求來說不算大。
接下來再介紹一下我們是如何通過StatefulSet管理Jenkins Master的,我們想是不是可以通過StatefulSet維護Jenkins Master叢集,因為我們希望更加自動化,所以我們解決了以下兩個問題。
第一是Job會對之前成功執行的Jenkins Master有親和性,它會預設跑到之前執行過的Master,因此我們為了儘可能的使用之前的Workspace,需要將Master pod儘可能固定在Node上面。
我們做了一個排程器,我們創建出來的Master pod後面有序號,可以很好的對映到每一臺機器上面。
第二是我們需要將上面的目錄既掛載在Master、又掛載到Slave上面,因此建立叢集的時候需要事先將掛載目錄準備好,所以我們也是開發了一個外掛CHostpath Volume Driver。除了這些之外,我們還將Java依賴的東西也是放在上面,每次建立Slave的時候也可以掛載到Slave上面,可以提高構建的效能。
有了這些之後,我們再看一下整個叢集建立的流程,其實只要維護一個StatefulSet就可以了。首先是StatefulSet,後面建立、更新或者擴容的時候都要建立一個Pod,再創建出Volume,再建立容器,執行Entrypoint。因為剛才提到Jenkins Job的配置,將Jenkins Master的配置放在上面也是支援版本的控制。
下載下來到本地初始化目錄,因為初始化之後有些配置需要根據當前機器的情況做修改,比如說IP,最後啟動Jenkins,向構建系統註冊例項,StatefulSet更新完之後只需要在系統裡面拉入就可以對外配比服務了,中間不需要做過多手工干預。
4. 問題與改進
上面大概介紹了持續交付、構建平臺、Jenkins on K8S 使用實踐,接下來說一下問題與改進。
第一是多環境應用映象問題,我們現在是一個環境有一個映象,為什麼會這樣?因為在早些年沒有配置中心的時候,我們的配置是寫在程式碼裡的,在釋出的時候會根據每個環境將配置重新做修改,再打成包,再打成映象。
當然這樣就會造成比較多的問題,比如環境之間的差異,因為一個應用的釋出最好以映象作為版本,這樣就會作為測試環境與其它測試環境不一致,也會降低釋出效率。
第二是非標準應用容器化,我相信在一個規模比較大的公司可能交付過程不太一樣,構建過程或者打映象過程都不太一樣,我們只能儘可能滿足一些大眾化的需求,有一些比較特殊的我們是無法給出解決方案的,這些人基本上是手工操作申請虛擬機器部署,也沒有人管他們。
另外是支援使用者自定義Dockerfile,執行構建與製作映象,減少運維成本。
第三是資源混部,我剛剛說的整個構建系統的排程,Master是非常必要的,我們未來希望可以構建系統的機器在業務比較緊張的時候,可以使用其它的業務計算資源,也可以提升整個資料中心的資源利用率。
說明:本文為周光明老師在 DOIS 2018 · 深圳站分享整理而成。
多環境一日部署 6000 次不是夢想,JUCC 2019 全國首站深圳舉行~
JUCC 旨在傳播持續交付理念、DevOps 落地實踐
時隔 2 年,Jenkins 創始人 KK 再次來華,敬請期待精彩演講與互動~
更多精彩,點選 閱讀原文