devops-2:Jenkins的使用及Pipeline語法講解
DevOps-Jenkins
Jenkins簡介
Jenkins是一個開源軟體項目,是基於Java開發的一種持續集成工具,用於監控持續重複的工作,旨在提供一個開放易用的軟體平台,使軟體項目可以進行持續集成。
什麼是持續集成(CI)?
CI(Continuous integration,中文意思是持續集成)是一種軟體開發實踐。持續集成強調開發人員提交了新程式碼之後,立刻進行構建、編譯、(單元)測試等這個過程,每次提交新程式碼都要進行此類重複操作,為了提高工作效率,避免重複工作及重複工作導致差別化問題。
什麼是持續部署(CD)?
CD(Continuous Delivery, 中文意思持續交付)是在持續集成的基礎上,將集成後的程式碼部署到更貼近真實運行環境中,也就是說它是CI後緊跟的一個環節,當程式碼審核完畢打包成成品後,需要部署到真實的環境中去,這個過程也會根據程式碼的跟新持續的進行。
Jenkins安裝
採用docker形式安裝,首先安裝好docker服務,然後在安裝機器中對要存儲Jenkins數據的目錄進行賦權:
# groupadd jenkins -g 1000 && useradd jenkins -u 1000 -g jenkins
# chown -R 1000:1000 /jenkins
uid 和 gid必須是1000,和Jenkins鏡像中的對應
開始安裝:
# docker network create jenkins
# docker run \
--name jenkins-docker \
--detach \
--privileged \
--network jenkins \
--network-alias docker \
--env DOCKER_TLS_CERTDIR=/certs \
--volume /jenkins/certs:/certs/client \
--volume /jenkins/data:/var/jenkins_home \
--publish 8080:8080 \
--publish 50000:50000 \
jenkins/jenkins:latest
## 執行此命令獲取初始密碼
# docker exec -it jenkins-docker cat /var/jenkins_home/secrets/initialAdminPassword
這個時候就可以通過 serverip:8080 來訪問到Jenkins。
流水線(pipeline)概括
想要更好的使用Jenkins,必須掌握其流水線(pipeline)的使用。
流水線概述
- 默認文件名為 Jenkinsfile
- 採用 Groovy 語法
- 其可以實現對程式碼的整合、編譯、品質檢測或部署等一系列操作,相當於我們說的「腳本」
流水線腳本的分類包括:腳本式、聲明式
-
腳本式語法結構
node { stage('Example') { try { sh 'exit 1' } catch (exc) { echo 'Something failed, I should sound the klaxons!' throw } } }
-
聲明式語法結構
pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' script { def browsers = ['chrome', 'firefox'] for (int i = 0; i < browsers.size(); ++i) { echo "Testing the ${browsers[i]} browser" } } } } } }
我們應該用哪種方式來編寫 Jenkinsfile?
推薦使用聲明式語法結構來編寫 Jenkinsfile,聲明式語法結構的靈活性及可讀性都要比腳本式語法結構要高,並且聲明式結構中可以包含腳本式結構,可以說更強大一些,並且便於學習。
如何快速上手Jenkinsfile?
- 多看Jenkinsfile示例文件,及官方的語法介紹:Pipeline Syntax (jenkins.io),對語法有一個基礎掌握。
- 學會使用腳本生成器
聲明式常用語法介紹
-
最外層的pipeline,整條流水線的開始,裡邊定義了流水線所有內容
pipeline{ }
-
agent,指定了流水線的執行節點,可以在pipeline的下行指定,也可以在stages的下行指定,agent有四個可選參數:
- any:在任意節點執行pipeline
- none:未定義,當頂層的agent none時,必須在每個stage下再單獨定義agent
- label:指定運行節點的label
- node:自定義運行節點配置,例如指定label或customWorkspace,agent { node { label ‘labelName’ } } 和 agent { label ‘labelName’ }相同, 但 node支援其他選項 (比如customWorkspace).
pipeline{ agent{ node{ label "container" customWorkspace '/some/other/path' //指定自定義的工作空間,其實就是指定自定義路徑 } } }
-
stages(階段):包含一個或多個stage的序列,大部分執行操作都在這裡,將每個離散部分串接起來,比如構建、測試和部署。
-
stage:包含在stages中,pipeline完成的所有實際工作都需要包含到stage中,每一個階段都要有一個名字。
-
steps(步驟):1個stage中,一般都要運行1個steps,裡邊可以直接運行linux命令。相當於3-5點是一個連續的、互相關聯的,例如:
pipeline { agent any stages { stage('Example') { agent { //選擇執行節點,可以在此層級指定 label 'nginx' } steps { //執行的步驟 echo 'Hello World' } } } }
-
post:定義Pipeline或stage運行結束時的操作有以下多個參數可選:
-
always:無論Pipeline運行的完成狀態如何都會運行
-
changed:只有當前Pipeline或steps運行的狀態與先前完成的Pipeline的狀態不同時,才能運行
-
failure:僅噹噹前Pipeline或steps處於「失敗」狀態時才運行
-
success:僅噹噹前Pipeline或steps具有「成功」狀態時才運行
-
unstable:只有當前Pipeline或steps具有「不穩定」狀態才能運行
-
aborted:只有當前Pipeline或steps處於「中止」狀態時才能運行
-
unsuccessful:只有當前Pipeline或steps運行為「未成功」狀態才能運行
-
cleanup:無論Pipeline或steps運行後為什麼狀態,只要前邊定義的狀態都沒匹配到,則運行
示例:
pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' } } } post { always { echo 'I will always say Hello again!' } } }
-
-
environment:環境變數,可以定義全局變數和特定的階段變數,取決於其在流水線的位置,該指定支援一個特殊的方法credentials(),此方法可用於在Jenkins環境中通過標識符訪問預定義的憑證,其有很多種類型:
- Secret Text:指定的環境變數將設置為密文內容
- Secret File:指定的環境變數將設置為臨時創建的文件的位置
- Username and password:指定的環境變數將設置為username:password,並將自動定義為兩個環境變數:MYVARNAME_USR和MYVARNAME_PSW
- SSH with Private Key:指定的環境變數將設置為臨時創建的SSH密鑰文件的位置,並且可以自動定義為兩個環境變數:MYVARNAME_USR和MYVARNAME_PSW
示例1:
pipeline { agent any environment { CC = 'clang' } stages { stage('Example Secret Text') { environment { // 需要提前將憑據創建好Secret Text類型的憑據 AN_ACCESS_KEY = credentials('my-predefined-secret-text') } steps { sh 'printenv' } } } }
示例2:
pipeline { agent any stages { stage('Example Username/Password') { environment { // 提前創建好Username and Password類型的憑據 SERVICE_CREDS = credentials('my-predefined-username-password') } steps { sh 'echo "Service user is $SERVICE_CREDS_USR"' sh 'echo "Service password is $SERVICE_CREDS_PSW"' } } stage('Example SSH Username with private key') { environment { // 提前創建好SSH Username with private key類型的憑據 SSH_CREDS = credentials('my-predefined-ssh-creds') } steps { sh 'echo "SSH private key is located at $SSH_CREDS"' sh 'echo "SSH user is $SSH_CREDS_USR"' sh 'echo "SSH passphrase is $SSH_CREDS_PSW"' } } } }
-
options:此指令允許從Pipeline本身中配置一些特定的選項。Pipeline提供了許多這樣的選項,例如buildDiscarder,也可以由插件提供,例如timestamps。分享一些參數:
-
buildDiscarder:保持構建的最大個數,示例:
options { buildDiscarder(logRotator(numToKeepStr: '1')) }
-
checkoutToSubdirectory:在workspace的子目錄中執行源程式碼自動管理切換,示例:
options { checkoutToSubdirectory('foo') }
-
disableConcurrentBuilds:不允許同時執行管道。可用於防止同時訪問共享資源等,示例:
options { disableConcurrentBuilds() }
-
disableResume:如果控制器重新啟動,則不允許恢復pipeline,示例:
options { disableResume() }
-
newContainerPerStage:使用docker或dockerfile agent時。每個stage將在同一節點上的新容器實例中運行,而不是在同一容器實例中運行的所有stages。
-
overrideIndexTriggers:
-
retry:失敗時,按指定次數重試整個管道,可以針對Pipeline或者Stage,示例:
options { retry(3) }
-
skipStagesAfterUnstable:一旦構建狀態變得不穩定,就跳過各個stages。示例:
options { skipStagesAfterUnstable() }
-
timeout:設置管道運行的超時時間,此參數可以針對Pipeline或者Stage,例如:
options { timeout(time: 1, unit: 'HOURS') }
-
timestamps: 所有控制台輸出前加上線路發出的時間,此參數可以針對Pipeline或者Stage,示例:
options { timestamps() }
pipeline { agent any options { timeout(time: 1, unit: 'HOURS') timestamps() buildDiscarder(logRotator(numToKeepStr: '10')) } stages { stage('Example') { options { timeout(time: 1200, unit: 'SECONDS') } steps { echo 'Hello World' } } } }
-
-
parameters:為Pipeline運行前提供參數,有幾種參數類型供選擇:
- string:字元串類型,示例:
parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }
- text:文本類型,可以包含多行,示例:
parameters { text(name: 'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '') }
- booleanParam:布爾類型,示例:
parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }
- choice:選擇類型,多個選項任選其一,示例:
parameters { choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '') }
- password:密碼類型,可以提前設置密碼,示例:
parameters { password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'A secret password') }
pipeline { agent any parameters { string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?') text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person') booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value') choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something') password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password') } stages { stage('Example') { steps { echo "Hello ${params.PERSON}" echo "Biography: ${params.BIOGRAPHY}" echo "Toggle: ${params.TOGGLE}" echo "Choice: ${params.CHOICE}" echo "Password: ${params.PASSWORD}" } } } }
使用parameters參數有一個bug,首次構建時不會讓你選擇參數,第二次才可以選擇。
- string:字元串類型,示例:
-
triggers:觸發器,定義了Pipeline自動化觸發的方式,可觸發的方式有:
-
cron:計劃任務定期觸發,示例:
triggers { cron('H */4 * * 1-5') }
-
pollSCM:與cron方式類似,但是必須發現有源碼的變化,才會觸發,示例:
triggers { pollSCM('H */4 * * 1-5') }
pollSCM觸發器僅在Jenkins 2.22或更高版本中可用。
-
upstream:接受以逗號分隔的作業字元串和閾值。當字元串中的任何作業以最小閾值結束時,將會觸發,示例:
triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }
pipeline { agent any triggers { cron('H */4 * * 1-5') } stages { stage('Example') { steps { echo 'Hello World' } } } }
Jenkins cron syntax
Jenkins cron語法遵循cron公共的語法(略有不同),每行由5個欄位組成,由製表符或空格分隔。
分鐘 小時 日 月 周 Minutes within the hour (0–59) The hour of the day (0–23) The day of the month (1–31) The month (1–12) The day of the week (0–7) where 0 and 7 are Sunday. 要為一個欄位指定多個值,可以使用以下運算符。按優先順序展示:
*
指定所有的值M-N
指定值得範圍M-N/X
or*/X
在指定的範圍或者整個範圍內,按照X
的值為間隔步長A,B,…,Z
指定多個值
為了允許定期調度的任務在系統上產生均勻負載,應儘可能使用符號H(表示「哈希」),使其執行pipeline的時間分散,更好的利用資源。
此外,支援使用
@yearly
,@annually
,@monthly
,@weekly
,@daily
,@midnight
, and@hourly
作為別名,他們都等同於使用哈希自動的進行均衡,例如@hourly
與H * * * *
相同,表示1小時內任意時間執行即可。舉一些cron例子:
- 每15分鐘執行一次,可以是 at :07, :22, :37, :52:
triggers{ cron('H/15 * * * *') }
- 在每小時的前30分鐘,每10分鐘執行一次:
triggers{ cron('H(0-29)/10 * * * *') }
- 每周的周一到周五的,9點-16點之間的第45分鐘每兩個小時執行一次:
triggers{ cron('45 9-16/2 * * 1-5') }
- 1-11月的每個1號和15號都要隨機執行一次:
triggers{ cron('H H 1,15 1-11 *') }
-
-
tools:工具,目前僅支援三種工具:maven、jdk和gradle。工具的名稱必須在系統設置—>全局工具配置中定義。定義好後即可開始調用:
pipeline { agent any tools { maven 'apache-maven-3.0.1' } stages { stage('Example') { steps { sh 'mvn --version' } } } }
第一次運行此工具時,如果沒有會去下載,或者會根據你指定的獲取方式去獲取,比如直接從其他機器打包過來解壓等方式。
-
input:stage 的 input 指令允許你使用 input step提示輸入。 在應用了 options 後,進入 stage 的 agent 或評估 when 條件前, stage 將暫停。 如果 input 被批准, stage 將會繼續。 作為 input 提交的任何參數都將在環境中用於其他 stage 使用。可選配置:
- message:必需的。 呈現給用戶的資訊。
- id:可選的, 默認為 stage 名稱。
- ok:可選的,表單中 ok 按鈕的描述文本。
- submitter:可選的,以逗號分割的用戶列表,只有列表用戶才可以提交input內容。
- parameters:可選參數列表,與前文介紹的相同。
示例:
pipeline { agent any stages { stage('Example') { input { message "Should we continue?" ok "Yes, we should." submitter "alice,bob" parameters { string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?') } } steps { echo "Hello, ${PERSON}, nice to meet you." } } } }
-
when:邏輯判斷,可以給定條件決定要不要執行此stage,when指令必須包含至少一個條件,如果when包含多個條件,則必須所有的條件都返回true,stage才會運行。內置條件有:
- brance:分支匹配,當正在構建的分支與預設給定的分支匹配時,執行此stage,例如:
when { branch 'master' }
,此參數僅適用於多分支流水線。 - environment:當指定的環境變數是給定的值時,執行此stage,例如:
when { environment name: 'DEPLOY_TO', value: 'production' }
- expression:當指定的Groovy表達式評估為true時,執行此stage,例如:
when { expression { return params.DEBUG_BUILD } }
- not:當嵌套條件是錯誤時,執行這個stage,必須包含一個條件,例如:
when { not { branch 'master' } }
- allOf:當所有的嵌套條件都正確時(當when中有多個條件時,默認就是此策略),執行這個此stage,必須包含至少一個條件,例如:
when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
- anyOf:當至少有一個嵌套條件為真時,執行這個階段,必須包含至少一個條件,例如:
when { anyOf { branch 'master'; branch 'staging' } }
- beforeAgent:默認為false,默認情況下,如果在stage中包含agent,則會在執行when之前會先執行agent,如果將beforeAgent設置為true,則表示先執行when條件,when條件判斷為執行stage後,才執行agent。
示例:
pipeline { agent any parameters { choice(name: 'BRANCH_NAME', choices: ['production', 'staging'], description: 'Pick something') string defaultValue: 'production', name: 'DEPLOY_TO', trim: true } stages { stage('Example Build') { when { expression { BRANCH_NAME ==~ /(production|staging)/ } } steps { echo "Start building ${BRANCH_NAME}" } } stage('Example Deploy') { when { anyOf { environment name: 'DEPLOY_TO', value: 'production' environment name: 'DEPLOY_TO', value: 'staging' } } steps { echo 'Deploying' } } } }
- brance:分支匹配,當正在構建的分支與預設給定的分支匹配時,執行此stage,例如:
-
Parallel:並行,聲明式流水線的
stage
可以在他們內部聲明多個嵌套stage
, 它們將並行執行。注意,一個stage必須只有一個steps
或parallel
,嵌套的stage本身不能再進入parallel
階段,任何包含parallel
的stage
不能包含agent
或tools
,因為它們沒有相關的step
。另外,可以添加
failFast true
到包含parallel
的stage
中,表示當其中一個進程失敗時,所有的parallel
階段都將被終止。示例:
pipeline { agent any options { parallelsAlwaysFailFast() //表示後續所有的parallel階段中都設置為failFast true timestamps() } stages { stage('Build Stage') { steps { echo 'This stage is Build.' } } stage('Parallel Stage --> Deploy') { when { not{ branch 'prod' } } parallel { stage('Branch dev') { agent { label "dev" } steps { echo "On Branch dev deploy" } } stage('Branch int') { agent { label "int" } steps { echo "On Branch int deploy" } } stage('Branch test') { agent { label "test" } stages { stage('Nested 1') { steps { echo "In stage Nested 1 within Branch test" } } stage('Nested 2') { steps { echo "In stage Nested 2 within Branch test" } } } } } } } }
此示例中:只要當branch不是prod時,則將同時在dev/int/test三個環境中deploy,test環境的deploy又分為兩個stage,此兩個stage並不是並行執行,而是串列執行。
-
script:腳本式語法,可以在聲明式流水線中使用,包含在step里,調用script { }塊,示例如下:
pipeline { agent any stages { stage('Example') { steps { echo 'Hello World' script { def browsers = ['chrome', 'firefox'] for (int i = 0; i < browsers.size(); ++i) { echo "Testing the ${browsers[i]} browser" } } } } } }
腳本式語法
腳本式pipeline也是建立在底層流水線上的,與聲明式不同的是, 腳本化流水線實際上是由 Groovy構建的通用 DSL, Groovy 語言提供的大部分功能都可以用於腳本化流水線。
-
腳本化流水線從
Jenkinsfile
的頂部開始向下串列執行, 就像 Groovy 或其他語言中的大多數傳統腳本一樣。 因此,提供流控制取決於 Groovy 表達式, 比如if/else
條件, 例如:node { stage('Example') { if (env.BRANCH_NAME == 'master') { echo 'I only execute on the master branch' } else { echo 'I execute elsewhere' } } }
-
另一種方法是使用Groovy的異常處理支援來管理腳本化流水線流控制。當 步驟 失敗 ,無論什麼原因,它們都會拋出一個異常。處理錯誤的行為必須使用Groovy中的
try/catch/finally
塊 , 例如:node { stage('Example') { try { sh 'exit 1' } catch (exc) { echo 'Something failed, I should sound the klaxons!' throw } } }