1. 程式人生 > >基於Docker的Jenkins持續交付實踐

基於Docker的Jenkins持續交付實踐

葉峰

講師介紹葉峰

有容雲資深前端開發工程師

  • 現負責有容雲容器雲平臺Web架構設計和CI(持續整合)產品的研發
  • 擁有豐富的Web前端開發經驗。

主題簡介:

1.Jenkins pipeline基礎概念

2.Jenkins pipeline如何帶來工作便利

3.基於容器的Jenkins CI流程

4.Jenkins、Docker、Kubernetes整合的整合部署

傳統交付方案

傳統我們的專案開發模式是產品調研提出需求,開發團隊研究決定開發方案選型。然後開始一個週期的開發,模組開發完成之後開始模組間的聯調。聯調結束之後打包交付給測試團隊。測試團隊,系統測試或自動化測試,然後提交bug,開發團隊修復bug,周而復始。傳統的模式中,存在著較多的不確定因素。例如,開發環境、編譯環境、測試環境、生產環境,等不確定因素。人為介入打包中的不確定因素,缺乏單元測試和自動化測試的整合。從而導致的結果是,開發-測試-修復的週期較長,而且很多小的問題完全可以由單元測試進行覆蓋。

持續交付

持續交付並不是某個特定的軟體,而是一個結果。這個結果要求團隊可以隨時的釋出一個新的準確版本,而且要求在編譯釋出的過程中進行自動化測試,通過自動化測試可以及時地發現並定位存在的bug,修復bug之後再進行快速的釋出到測試環境,測試團隊直接進行測試。與傳統模式的區別在於持續交付可以提前發現bug的存在和快速修復而不必等到測試人員的介入之後才發現。持續交付分解出來就是“持續”和“交付”。

持續:持續要求任何時,候任何情況都能進行準確的釋出,做到準確的釋出需要注意以下幾個關鍵點。

1.持續應該是一個週期性的,可以是每天的某個時間點,也可以是某次程式碼的提交,或者某次人為觸發。所以人工進行構建是不可能的,需要自動化的構建,自動化要求構建的任何一個流程都必須以指令碼的形式執行,程式碼檢出、程式碼構建、各模組程式碼單元測試、整合測試、UI自動化測試等。

2.釋出的程式版本不允許是各個模組在開發環境編譯出一個版本作為交付,而要求在一個純淨的編譯環境中進行構建。

3.構建的過程應該要求最大可能的固化,例如作業系統的版本,構建環境的版本,相關的依賴等。

4.避免從網路獲取相關的檔案,這點以nodejs為開發或編譯的專案尤其重要,安裝node的依賴包總是一個漫長的過程,就算有國內的源,一般的專案也需要一兩分鐘的node依賴包,這不符合快速構建。

交付 :在持續編譯的過程,使用自動化已經可以避免大多數的錯誤了。但是還是需要人為介入的系統測試,畢竟自動化的測試一般只能覆蓋到70%左右。

根據我們團隊內部推廣這種工作方式的效果來看,持續整合確實讓我們工作便利了許多, 每次程式碼的構建和自動化測試讓我們及時發現存在的bug。好的工作模式也需要團隊成員的遵守,團隊成員應該積極的擁抱這種工作方式,團隊成員需要做好以下幾點。

1.使用版本工具例如git。git有強大的版本回溯,成員每次完成一個小的功能點進行程式碼提交。合併到master分支,持續交付工具應該配置為程式碼更新觸發。團隊內部應該等到持續交付流程結束之後,確認編譯、自動化測試通過之後方可進行下一個版本的提交,這樣容易定位bug。而不會導致這次bug影響團隊內其他成員的工作。

2.主分支的程式碼bug不應該存留時間過長,避免團隊內其他成員合併程式碼的時候引入其他問題。

3.測試驅動開發,任何一個新的功能開發都應該先寫好單元測試指令碼,並積極更新自動化測試指令碼。並且積極地擁抱測試,雖然你明白這個測試不通過的問題並不會引起很大的系統性問題 ,但是還是應該進行修復而不是想方設法的跳過這個自動化測試。

4.臨近下班的時候不要提交程式碼,這主要是因為遵守第2點。

一個解決方案

使用Docker

Docker已經越來越火,CICD和Devops也是Docker一個重要的場景。在持續交付中使用Docker有一下優點。

  1. Docker強大的環境隔離性可以將環境和程式打包在一起,測試、運維,人員無需知道我們的程式是如何配置的,只需要一條docker 的命令就可以將我們的程式執行起來,這也更加容易實現持續部署。
  2. 減少編譯環境的汙染,因為Docker天然的隔離性,也避免了傳統編譯環境難以配置多套編譯環境的問題。在基於Docker的持續釋出中,我們可以在同一臺宿主機上同時編譯不同版本的Java專案,不同版本的Python專案,而無需任何配置,映象也只是從docker hub中獲取。

持續整合

在持續整合方面,我們選擇Jenkins。Jenkins是一款開源軟體,擁有眾多優秀的外掛,依靠這些外掛,我們可以完成一些週期、繁瑣、複雜的任務。例如我們今天分享的持續釋出,雖然Jenkins解決了我們繁瑣複雜週期性的操作,但是沒有解決我們在多種環境下編譯構建的需求。而這個場景正是Docker的強項。通過Jenkins的pipeline我們可以實現程式碼檢出、單元測試、編譯、構建、釋出、測試等流程的自動化,而最終通過Jenkins的Docker外掛將產出物構建成映象,方便部署到Docker環境。

持續部署

持續整合讓我們新的程式碼源源不斷的構建成了映象,這些映象經歷了單元測試,自動化測試,但還沒有接受過測試團隊的嚴格測試。Jenkins是一個強大的持續整合工具,然而持續部署並不是Jenkins的強項,但是Jenkins擁有很多強大的外掛。而且我們持續整合產出的是映象,所以持續的部署,我們只需要將映象執行起來,或者利用第三方的容器管理平臺提供的API進行部署。

1.本地部署應用到Docker。

本地部署到Docker容器可以使用Jenkins的docker外掛,下面會介紹。

2.部署到遠端主機的Docker、Appsoar。

Docker和Appsoar都支援開啟API呼叫。通過現有的API我們可以執行我們生成映象版本。從而達到持續的部署最新版本。

3.部署到kubernetes。

kubernetes除了可以通過API呼叫還可以在jenkins中配置kubectl的方式建立或更新deployments。

Docker中執行Jenkins

Docker部署Jenkins的方式簡單方便,下面我們介紹用Docker的方式執行Jenkins。

  1. 這裡將docker.sock和docker的可執行檔案掛載到Jenkins容器中,這樣我們就可以在容器中使用docker了。
  2. Jenkins容器,預設的使用者是Jenkins。因為我們需要使用Docker,所以我們需要使用root使用者。
  3. /var/jenkins_home的掛在卷是可選的,Jenkins_home存放了所有任務、日誌、認證、外掛等jenkins執行後的檔案。可做資料恢復使用。

配置Jenkins

  • 解鎖jenkins

解鎖的密碼在容器的log中可以檢視,或者直接檢視jenkins_home指定檔案

jenkins

  • 選擇外掛

Docker

建立Pipeline

下面我們建立一個的Jenkins的Pipeline完成簡單的cicd流程。

  1. 新建pipeline,在左側新建選擇pipeline。
  2. 在左側的Credentials中新建git和映象倉庫的credentials
  3. 配置pipeline,例如定時觸發,程式碼更新觸發,webhook觸發等。
  4. 在pipeline script中填入下面的demo.

以下是虛擬碼,僅提供思路

虛擬碼

Jenkins pipeline的指令碼語法是groovy的語法,其中docker 、Git是外掛提供的能力。程式碼的執行流程如下:

  1. 通過Git外掛獲取最新程式碼到jenkins的工作區,例如`/var/jenkins_home/workspace/pipelineDemo。
  2. docker.image().inside是如何編譯我們的程式碼呢,通過檢視Jenkins的console 可以看到如下log.
  3. [Pipeline] withDockerContainer

  4. 熟悉Docker命令的朋友應該很容易理解了,原來是docker.image().inside啟動的時候會將當前的目錄掛在到容器中,然後在容器中執行./script/build.sh,這樣我們就完成了利用容器中存在的環境做單元測試或構建編譯了。
  5. 通過docker外掛提供的能力構建映象,Dockerfile存放在程式碼目錄中。構建映象後push到映象倉庫,私有倉庫需要自行配置映象倉庫。
  6. 映象構建完成之後就可以刪掉舊版本,並重新執行一個新的版本。

通過簡單的例子,可見Jenkins和Docker的結合給CICD帶來了足夠的便利和強大。我們需要準備的只是一個編譯的指令碼,在編譯指令碼中可以使用任何的環境和任何的版本。

Pipeline 介紹

Jenkins的任務兩個主要版本。

free style只是一個自動化的指令碼,指令碼型別為shell。所有的指令碼在一臺機器上執行,需要的環境需要提前準備。配置不集中,混亂。但是一般情況下還是夠用的。

pipeline 是jenkins2的版本使用了一個基於groovy指令碼的任務型別,通過一系列的stage將構建的不同部分組合成一個pipline。而且配合step可以完成非同步操作。因為基於groovy可程式設計性更加強大,而且指令碼可以存放在原始碼中,指令碼的更改不需要直接到jenkins中修改。

pipeline的一些使用經驗和技巧

1.Jenkins的資料較少,官網可以檢視的內容也不多,一般的需求Jenkins內建的pipeline-syntax裡面就有常用的命令生成器。可以滿足大多數需求。

2.在pipeline指令碼除錯完成之後應該將指令碼以檔案的形式放在原始碼目錄中,這樣子方便修改。和多分支需要編譯的情況下進行互相隔離。

3.應該多查詢下相應的外掛,而不是使用sh用執行指令碼的方式來解決問題。

4.應該將jenkins_home目錄掛在出來,如果遇上了Jenkins崩潰了可以及時的恢復資料。

5.應該新建一個定時的pipeline用來清理生成的映象,減少硬碟資源的佔用。

6.頁面新建的pipeline,在頁面刪除之後,jenkins_home/workspace中對應的專案檔案並不會被刪除。

總結

持續釋出很多團隊想有這樣的工具達到這個效果,有些團隊覺得不需要。任何工具、流程都需要符合自身團隊的實際。從我開始參與團隊內的這個和持續釋出有關的專案,查看了許多資料,結合團隊專案內的實踐。給出的一些經驗的和見解和大家一起分享,如有錯誤或者建議,歡迎大家及時溝通。

Q&A

Q1:請問Kubernetes怎麼結合Jenkins做持續整合呢?

A1:部署到Kubernetes。Kubernetes除了可以通過API呼叫還可以在Jenkins中配置kubectl的方式建立或更新deployments。

Q2:必須通過pipeline才能實現Jenkins把程式碼構建成Docker映象麼?

A2:不一定,使用Docker主要是方便進行編譯環境的隔離,也可以配置好NFS,構建完成之後複製到固定的伺服器上,這個我們一般叫製品庫。

Q3:Pipeline如何通過Docker容器部署應用到不同的節點上去?釋出遇到問題如何回滾版本的?

A3:Jenkins的能力更多的是做持續整合(CI)的功能,部署和回滾都需要容器管理平臺並不是Jenkins的強項,特別是回滾單依靠Jenkins很難做到完美的方案。但是部署到不同的Docker的節點上,可以使用第三方的管理平臺,例如Appsoar和k8s提供的API能力,可以進行部署。Jenkins直接呼叫curl命令執行容器管理平臺提供的API。

Q4:pipeline的每個環節的報告如何快速獲取?比如程式碼靜態檢查,工程構建,測試報告等等。

A4: http://jenkins:8080/job/clearImages/86/wfapi/,通過Jenkins這個API,可以獲取一些狀態和時間資訊,至於詳細的程式碼靜態檢查,每種語言都有不同的語法檢查。需要自行配置。當然詳細的需要檢視輸出日誌。

Q5:關於測試驅動開發,在開發之前寫好的用例一定要是自動化的嗎?為什麼?

A5:一個系統由若干的方法組成,單元測試就是測試你寫的方法是否符合你的業務要求。所以先寫合理的單元測試,只要你的方法通過了這個單元測試就表示你寫的這個方法是正確的,單元測試程式碼是需要開發人員編寫的,每種語言有不同的單元測試框架例如Nodejs的mocha,Golang 的go test。自動化測試由測試人員編寫.單元測試應該需要脫離外部因素,不依賴資料庫、不依賴外部API。

Q6:怎麼觸發工作流的?

A6:Jenkins pipeline 提供了三種方式(如果安裝了SCM的外掛可能有其他的方式觸發),進入到pipeline的設定頁面中的分別有。wbhook(觸發遠端構建 (例如,使用指令碼))、定時觸發(Build periodically)、程式碼更新觸發(Poll SCM)。

Q8:Jenkins的編譯環境是怎麼處理的,實際使用者的編譯需求和環境都不一樣?

A8:使用者需要清楚你使用的編譯環境的基本情況,例如golang的編譯環境,容器中的GOPATH是在什麼位置。你需要將你的程式碼ln到什麼目錄才能進行編譯,等這些細節都是需要使用者提前知曉。

Q9:Jenkins裡的有使用者許可權管理嗎,貴公司的CI/CD是怎麼實現使用者隔離的,每個使用者只能看到自己的專案。

A9:Jenkins當中並沒有使用者許可權。公司在研發的產品中,有一個虛擬的概念叫使用者組,對應的是k8s中的一個或多個namespaces。管理員將成員使用者新增到這個使用者組中,組內成員建立的資源(pipeline、叢集、服務,等)在組內是可見,使用者組來進行邏輯概念上的隔離

Q10:貴公司Jenkins和Kubernetes是怎麼結合使用的?是什麼的部署形式?如何回滾?

A10:我看到很多朋友都提問了,Jenkins如何跨主機部署或者如何部署到Kubernetes叢集,如何回滾。Jenkins對這方面的能力比較弱,僅僅能夠支援kube-api-server的呼叫而已,如果完全依靠Jenkins是很難完成需求,所以我們的產品當中有一個專門對接kubernetes的deploy的模組,一個應用商店的模組,一個封裝了Jenkins的uflow模組,uflow模組嚮應用商店獲取模板並根據當前編譯構建出來的映象tag號替換模板,並交付給deploy模組建立。回滾和升級都由deploy模組負責。這樣各自分開,各司其職。

Q11:多個PHP專案,在Docker 應用中,需要逐個拆分嗎?一個專案對應一個映象管理?還是使用資料夾對映的方式構建映象?

A11:多個專案服務是放在一個容器中還是分開容器中,這個並沒有強制的限定。但是建議還是分為多個容器進行部署。Docker的理念就是一個容器完成一個單獨的事情。

Q12:Jenkins PIpeline input指令可以複雜的引數化麼?

A12:input是一個比較強大的指令,可以在pipeline的執行過程中確認操作,字元輸入,檔案上傳等功能。詳細的可以看下jenkins的pipeline-syntax有使用說明和指令碼的生成。

Q13:Jenkins自動觸發job到build docker image,自動觸發是怎麼實現的,wedhook 定時觸發有沒遇到過問題?不能正常觸發的?

A13:自動觸發的原理的原理是,我們在pipeline中配置一個定時器,這個定時器是用cron表示式表示。例如你設定了 “* * * * * ”就表示每分鐘檢查一次,那麼檢查什麼呢,檢查每次提交的ID,例如git的commit ID 。只要檢測到了這個ID和上一次的不一致就會觸發pipeline的構建。從目前使用並沒有出現過不能觸發的情況。如果出現了請檢查是否是配置的錯誤。

Q14:CD過程中,重造的輪子和開源元件是一個什麼樣的比例?個人推崇哪個?

A14:自己重複造輪子和開源元件應該如何選擇,這個是很有意思的一個問題。因為開發者都說不要重複造輪子,這是因為很多輪子經過了很多專案考驗和眾多開發者提交程式碼和fix的bug。這些專案肯定是比自己從頭開始造一個輪子更加有效率而且使用風險低,畢竟所有人都想完成工作上的任務早點下班。但從個人發展來說,有些輪子還是值得自己去製造一次的,這樣子你才會瞭解到這個元件的工作原理、底層的東西。所以我個人的推崇的是,假如你找到了合適接近完美的輪子那就直接用,如果找到了一個可用但總覺得用起來不太爽的元件,那你就把輪子造起來吧。

文章來自微信公眾號:DBAplus社群