閱讀631 返回首頁    go 阿裏雲 go 技術社區[雲棲]


基於Docker的Jenkins持續交付實踐

講師介紹  20170503094528987.jpg

葉峰

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

 

  • 現負責有容雲容器雲平台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指定文件

 

20170503094540182.jpg

 

  • 選擇插件

 

20170503094548988.jpg

 

創建Pipeline

 

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

 

  1. 新建pipeline,在左側新建選擇pipeline。

  2. 在左側的Credentials中新建git和鏡像倉庫的credentials

  3. 配置pipeline,例如定時觸發,代碼更新觸發,webhook觸發等。

  4. 在pipeline script中填入下麵的demo.

 

以下是偽代碼,僅提供思路

 

node{   
  // 代碼檢出
  stage('get Code') {
    git credentialsId: 'git-credentials-id', url: 'https://192.168.19.250/ufleet/uflow.git'
  }
 
    // 鏡像中進行單元測試
  stage('unit testing'){
    // 啟動golnag:1.7並在golang內編譯代碼
    docker.image('golang:1.7').inside {
      sh './script/unittest.sh'
    }
  }
 
  // 鏡像中代碼構建
  stage('Build'){   
    // 將配置文件中的運行模式由"dev"改成"prod"
    def confFilePath = 'conf/app.conf'
    def config = readFile confFilePath
    config = config.replaceFirst(/runmode = dev/, "runmode = prod") 
    writeFile file: confFilePath, text: config
   
    // 啟動golnag:1.7並在golang內編譯代碼
    docker.image('golang:1.7').inside {
      sh './script/build.sh'
    }
  }
 
  // 編譯鏡像並push到倉庫
  def imagesName = '192.168.18.250:5002/ufleet/uflow:v0.9.1.${BUILD_NUMBER}' 
  stage('Image Build And Push'){
    docker.withRegistry('https://192.168.18.250:5002', 'registry-credentials-id') {
      docker.build(imagesName).push()
    }
  }
 
  // 啟動剛運行的容器
  stage('deploy iamegs'){   
    // 需要刪除舊版本的容器,否則會導致端口占用而無法啟動。
    try{
      sh 'docker rm -f cicdDemo'
    }catch(e){
        // err message
    }
    docker.image(imagesName).run('-p 9091:80 --name cicdDemo')
  }
}

 

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: https://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。這些項目肯定是比自己從頭開始造一個輪子更加有效率而且使用風險低,畢竟所有人都想完成工作上的任務早點下班。但從個人發展來說,有些輪子還是值得自己去製造一次的,這樣子你才會了解到這個組件的工作原理、底層的東西。所以我個人的推崇的是,假如你找到了合適接近完美的輪子那就直接用,如果找到了一個可用但總覺得用起來不太爽的組件,那你就把輪子造起來吧。

原文發布時間為:2017-07-03

本文來自雲棲社區合作夥伴DBAplus

最後更新:2017-05-17 14:01:50

  上一篇:go  sicp習題2.2節嚐試解答
  下一篇:go  Flex is open sourced!