Jenkins 學習筆記

低吟不作語發表於2022-02-02

目錄

本學習筆記參考《Jenkins 2.x實踐指南》。

1. Jenkins 簡介

Jenkins 是一款自動化的任務執行工具。通常用於持續整合/持續交付領域。

可以通過介面或Jenkinsfile告訴Jenkins執行什麼任務,何時執行。理論上,我們可以讓它執行任何任務,但是通常只應用於持續整合和持續交付。

持續整合將軟體生產過程從手工模式帶入流水線模式,軟體生產的某個環節都對應流水線上的每個環節。

流水線模式還能幫助我們將知識固化到自動化流水線中,在一定程度上解決了知識被人帶走的問題。(手工模式對於整個軟體生產流程很少有人全部知道,使用流水線模式整個軟體生產的過程都以pipeline as code的形式保留下來,每個人都能看)

2. pipeline介紹

什麼是pipeline

  • 學術的講:軟體從程式碼版本庫到使用者手中這一過程的自動化表現;
  • 通俗的講:主要是指程式碼的自動構建、測試、打包和部署等過程;

Jenkins 1.x只能通過介面手動操作來“描述”部署流水線。Jenkins 2.x終於支援pipeline as code了,可以通過“程式碼”來描述部署流水線。

  • 更好地版本化:將pipeline提交到軟體版本庫中進行版本控制。
  • 更好地協作:pipeline的每次修改對所有人都是可見的。除此之外,還可以對pipeline進行程式碼審查。
  • 更好的重用性:手動操作沒法重用,但是程式碼可以重用。

什麼是JenkinsFile

Jenkinsfile就是一個文字檔案,也就是部署流水線概念在Jenkins中的表現形式。像Dockerfile之於Docker。所有部署流水線的邏輯都寫在Jenkinsfile中。

JK File 定義了流水線的部署邏輯

Jenkins預設不支援pipeline,需要安裝pipeline外掛。

pipeline語法的支援

pipeline支援以下兩種語法:

  • Groovy語法(node為根節點的是指令碼式語法);
  • 宣告式語法(2.5版本開始支援,pipeline為根節點的是宣告式語法,推薦使用)

3. pipeline 語法

Groovy語法

這裡Groovy語法不是重點,但是還是需要了解必要的知識。如果想要詳細瞭解,可以參考Groovy教程

// 一般用def定義變數
// 語句末尾不新增分號
def x = "abs"

// Groovy中的方法呼叫可以省略括號
println x
println "chensongxia"

// 支援命名引數
def m1(String givenName, String familyName) {
    return givenName + " " + familyName
}
// 呼叫時可以這樣
println m1("chen", "songxia")
s1 = m1 familyName = "ru", givenName = "zhao"
println s1

// 支援預設引數
def m2(String name = "hi") {
    return name
}
// 括號不能省略
println m2()
println m2("chen")

// 支援單引號、雙引號。雙引號支援插值,單引號不支援
println '------支援單引號、雙引號。雙引號支援插值,單引號不支援 ---------'
println ''
def name = "world";
println "hello ${name}"
println 'hello ${name}'


// 支援三引號。三引號分為三單引號和三雙引號。它們都支援換行,區別在於只有三雙引號支援插值。
println ''
println '-----支援三引號。三引號分為三單引號和三雙引號。它們都支援換行,區別在於只有三雙引號支援插值。-----'
println """line one
line two
line three
${name}
"""

println '''line one
line two
line three
${name}
'''

// 支援閉包
println '-------支援閉包---------'
def codeBlock = { println "hello closure" }
// 閉包可以被當成函式直接呼叫
codeBlock();
// 閉包可以被當成一個引數傳遞給另外一個方法
def pipeline(closure){
    closure()
}
pipeline(codeBlock)

def stage(String name,closure){
    println name
    closure()
}
stage("jenkins",codeBlock)

pipeline組成

Jenkins pipeline其實就是基於Groovy語言實現的一種DSL(領域特定語言),用於描述整條流水線是如何進行的。流水線的內容包括執行編譯、打包、測試、輸出測試報告等步驟。

從軟體版本控制庫到使用者手中這一過程可以分成很多階段,每個階段只專注處理一件事情,而這件事情又是通過多個步驟來完成的,這就是軟體工程的pipeline。Jenkins對這個過程進行抽象,設計出一個基本的pipeline結構。

pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                echo 'Hello World'
            }
        }
    }
}
  • pipeline:代表整條流水線,包含整條流水線的邏輯。
  • stage部分:階段,代表流水線的階段。每個階段都必須有名稱。本例中,build就是此階段的名稱。
  • stages部分:流水線中多個stage的容器。stages部分至少包含一個stage。
  • steps部分:代表階段中的一個或多個具體步驟(step)的容器。steps部分至少包含一個步驟,本例中,echo就是一個步驟。在一個stage中有且只有一個steps。
  • agent部分:指定流水線的執行位置(Jenkins agent)。流水線中的每個階段都必須在某個地方(物理機、虛擬機器或Docker容器)執行,agent部分即指定具體在哪裡執行

以上每一個部分(section)都是必需的,少一個,Jenkins都會報錯。

Jenkins的很多外掛可以被直接拿來當作一個步驟使用。只要安裝了這些適配了Jenkins pipeline的外掛,就可以使用其提供的pipeline步驟。

Jenkins官方還提供了pipeline步驟參考文件(https://jenkins.io/doc/pipeline/steps/)。

post部分

根據pipeline或階段的完成狀態,post部分分成多種條件塊,包括:

• always:不論當前完成狀態是什麼,都執行。
• changed:只要當前完成狀態與上一次完成狀態不同就執行。
• fixed:上一次完成狀態為失敗或不穩定(unstable),當前完成狀態為成功時執行。
• regression:上一次完成狀態為成功,當前完成狀態為失敗、不穩定或中止(aborted)時執行。
• aborted:當前執行結果是中止狀態時(一般為人為中止)執行。
• failure:當前完成狀態為失敗時執行。
• success:當前完成狀態為成功時執行。
• unstable:當前完成狀態為不穩定時執行。
• cleanup:清理條件塊。不論當前完成狀態是什麼,在其他所有條件塊執行完成後都執行。post部分可以同時包含多種條件塊。

下面給出一個列子:

pipeline支援的指令

基本結構滿足不了現實多變的需求。所以,Jenkins pipeline通過各種指令(directive)來豐富自己。指令可以被理解為對Jenkins pipeline基本結構的補充。

Jenkins pipeline支援的指令有:

• environment:用於設定環境變數,可定義在stage或pipeline部分。
• tools:可定義在pipeline或stage部分。它會自動下載並安裝我們指定的工具,並將其加入PATH變數中。
• input:定義在stage部分,會暫停pipeline,提示你輸入內容。
• options:用於配置Jenkins pipeline本身的選項,比如options {retry(3)}指當pipeline失敗時再重試2次。options指令可定義在stage或pipeline部分。
• parallel:並行執行多個step。在pipeline外掛1.2版本後,parallel開始支援對多個階段進行並行執行。
• parameters:與input不同,parameters是執行pipeline前傳入的一些引數。
• triggers:用於定義執行pipeline的觸發器。
• when:當滿足when定義的條件時,階段才執行。

在使用指令時,需要注意的是每個指令都有自己的“作用域”。如果指令使用的位置不正確,Jenkins將會報錯。

配置pipeline本身(option指令)(用到的時候可以來參考這塊)

options指令用於配置整個Jenkins pipeline本身的選項。根據具體的選項不同,可以將其放在pipeline塊或stage塊中。以下例子若沒有特別說明,options被放在pipeline塊中。

• buildDiscarder:儲存最近歷史構建記錄的數量。當pipeline執行完成後,會在硬碟上儲存製品和構建執行日誌,如果長時間不清理會佔用大量空間,設定此選項後會自動清理。

參考這個文件:https://weread.qq.com/web/reader/12f320007184556612f32b6k1ff325f02181ff1de7742fc

這節比較有用,可以參考下

在宣告式pipeline中使用指令碼

Jenkins pipeline專門提供了一個script步驟,你能在script步驟中像寫程式碼一樣寫pipeline邏輯。

pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                script{
                    def s = "Hello World"
                    println s
                }
                echo 'Hello World'
            }
        }
    }
}

可以看出,在script塊中的其實就是Groovy程式碼。大多數時候,我們是不需要使用script步驟的。如果在script步驟中寫了大量的邏輯,則說明你應該把這些邏輯拆分到不同的階段,或者放到共享庫中。共享庫是一種擴充套件Jenkins pipeline的技術

pipeline內建基礎步驟(重要)

Jenkins 的pipeline內建了很多基礎步驟,我們可以直接使用。

參考章節:https://weread.qq.com/web/reader/12f320007184556612f32b6k4e73277021a4e732ced3b55

4. 環境變數與構建工具

環境變數

環境變數可以被看作是pipeline與Jenkins互動的媒介。比如,可以在pipeline中通過BUILD_NUMBER變數知道構建任務的當前構建次數。環境變數可以分為Jenkins內建變數和自定義變數。

1. 內建環境變數

使用方式:

小技巧:在除錯pipeline時,可以在pipeline的開始階段加一句:sh 'printenv',將env變數的屬性值列印出來。這樣可以幫助我們避免不少問題。

不推薦方法三,因為出現變數衝突時,非常難查問題。

Jenkins的控制檯也對內建的環境變數做了說明,可以自己開啟看下。

image-20210626234914858

2. 自定義環境變數

environment指令可以在pipeline中定義,代表變數作用域為整個pipeline;也可以在stage中定義,代表變數只在該階段有效。

pipeline {
    agent any
    environment {
        DEVOPS_IP = '10.50.1.50'
    }
    stages {
        stage('Hello') {
            environment {
                NAME = 'Hello World...'
            }
            steps {
                echo "${DEVOPS_IP}"
                echo "${NAME}"
                sh 'printenv'
            }
        }
    }
}

但是這些變數都不是跨pipeline的,比如pipeline a訪問不到pipeline b的變數。在pipeline之間共享變數可以通過引數化pipeline來實現。

在實際工作中,還會遇到一個環境變數引用另一個環境變數的情況。在environment中可以這樣定義:

image-20210627000611998

在定義變數的時候,為了防止變數衝突,建議將變數加上字首(自定義的環境變數會覆蓋預設的環境變數)。

3. 自定義全域性環境變數

env中的變數都是Jenkins內建的,或者是與具體pipeline相關的。有時候,我們需要定義一些全域性的跨pipeline的自定義變數。

進入Manage Jenkins→Configure System→Global properties頁,勾選“Environment variables”核取方塊,單擊“Add”按鈕,在輸入框中輸入變數名和變數值即可。

image-20210627001302209

自定義全域性環境變數會被加入 env 屬性列表中,所以,使用自定義全域性環境變數與使用Jenkins內建變數的方法無異:${env.g name}。

構建工具

構建每一步都是可重複的,儘量與機器無關。所以,構建工具的安裝、設定也應該是自動化的、可重複的。

雖然Jenkins只負責執行構建工具提供的命令,本身沒有實現任何構建功能,但是它提供了構建工具的自動安裝功能。

1. tool指令

tools指令能幫助我們自動下載並安裝所指定的構建工具,並將其加入PATH變數中。這樣,我們就可以在sh步驟裡直接使用了。但在agent none的情況下不會生效。通過命令sh "printenv",可以看到環境變數的所有值。

tools指令預設支援3種工具:JDK、Maven、Gradle。通過安裝外掛,tools指令還可以支援更多的工具。

配置maven

進入Manage Jenkins→Global Tool Configuration→Maven頁,設定如下圖所示。

image-20210627132943803

pipeline {
    agent any
    environment {
        DEVOPS_IP = '10.50.1.50'
    }
    
    tools {
        maven 'maven.3.6.3'
    }
    
    stages {
        stage('Hello') {
            environment {
                NAME = 'Hello World...'
            }
            steps {
                echo "${DEVOPS_IP}"
                echo "${NAME}"
                sh 'printenv'
                sh 'java -version'
                sh 'mvn -v'
            }
        }
    }
}

這樣,當執行到tools指令時,Jenkins會自動下載並安裝Maven。將mvn命令加入環境變數中,可以使我們在pipeline中直接執行mvn命令。

Config File Provider外掛可以設定全域性的setting設定。

利用環境變數支援更多的構建工具

如果想讓Jenkins支援更多的構建工具,也是同樣的做法:在Jenkins agent上安裝構建工具,並記錄下它的可執行命令的目錄,然後在需要使用此命令的Jenkinspipeline的PATH環境變數中加入該可執行命令的目錄。

image-20210627140434285

利用tools作用域實現多版本編譯

在實際工作中,有時需要對同一份原始碼使用多個版本的編譯器進行編譯。tools指令除了支援pipeline作用域,還支援stage作用域。

image-20210627140743985

在列印出來的日誌中,會發現每個stage下的JAVA_HOME變數的值都不一樣。

5. 程式碼質量

靜態程式碼分析

使用maven外掛在某個stage加入靜態程式碼分析。(靜態程式碼掃描通常被安排在編譯之後)

單元測試

效能測試

Jenkins整合SonarQube(有時間將這個環境搭建起來,將分析報告推送到GitLab)

Allure測試報告(美化測試報告,不是重點)

6. 觸發 pipeline 執行

自動化是指pipeline按照一定的規則自動執行。而這些規則被稱為pipeline觸發條件。

對於pipeline觸發條件,可以從兩個維度來區分:時間觸發和事件觸發。接下來,我們從這兩個維度分別介紹。

時間觸發

時間觸發是指定義一個時間,時間到了就觸發pipeline執行。在Jenkins pipeline中使用trigger指令來定義時間觸發。

tigger指令只能被定義在pipeline塊下,Jenkins內建支援cron、pollSCM,upstream三種方式。其他方式可以通過外掛來實現。

使用場景:一些比較重的任務可以放到凌晨再跑。

image-20210629150639184

事件觸發

事件觸發就是發生了某個事件就觸發pipeline執行。這個事件可以是你能想到的任何事件。比如手動在介面上觸發、其他job主動觸發、HTTP API Webhook觸發等。

常用的有下面幾種:

  • 由上游任務觸發:upstream(其他job來觸發)
  • GitLab通知觸發
  • 在pipeline中實現GitLab trigger

將構建狀態資訊推送到GitLab

Generic Webhook Trigger

安裝 Generic Webhook Trigger 外掛(下文使用 GWT 簡稱)後,Jenkins 會暴露一個 API:<JENKINS URL>/generic-webhook-trigger/invoke,即由GWT外掛來處理此API的請求。

如何處理呢?GWT外掛接收到JSON或XML的HTTP POST請求後,根據我們配置的規則決定觸發哪個Jenkins專案。基本原理就這麼簡單。

7. 多分支構建

在實際專案中,往往需要多分支同時進行開發。如果為每個分支都分別建立一個Jenkins專案,實在有些多餘。

不同分支釋出不同環境

image-20210711102036257

when指令的用法

when指令允許pipeline根據給定的條件,決定是否執行階段內的步驟。when指令必須至少包含一個條件。when指令除了支援branch判斷條件,還支援多種判斷條件。

這塊內容非常有用,可以經常參考。

https://weread.qq.com/web/reader/12f320007184556612f32b6k9a132c802349a1158154a83

GitLab trigger對多分支pipeline的支援

image-20210711103410476

Generic Webhook Trigger外掛在多分支pipeline場景下的應用

在多分支pipeline場景下,我們希望觸發某個分支的構建執行。

8. 引數化pipeline

什麼是引數化pipeline

引數化pipeline是指可以通過傳參來決定pipeline的行為。

使用parameters指令

parameters {
      string defaultValue: 'none', description: '字串', name: 'D_ENV', trim: true
      text defaultValue: 'a\nb\nc\n', description: '文字', name: 'D_TEXT'
      choice choices: 'a\nb\nc\n', description: '選一個', name: 'D_CHOICE'
      booleanParam defaultValue: false, description: '布林值引數', name: 'FLAG'
      password name: 'PASSWORD',defaultValue:'SECRET',description: 'password'
   }

參考這個文章:https://zhuanlan.zhihu.com/p/80622378。

由另外一個pipeline傳引數

可以在一個pipeline中“呼叫”另一個pipeline。在Jenkins pipeline中可以使用build步驟實現此功能。build步驟是pipeline外掛的一個元件,所以不需要另外安裝外掛,可以直接使用。

build步驟其實也是一種觸發pipeline執行的方式,它與triggers指令中的upstream方式有兩個區別:

(1) build步驟是由上游pipeline使用的,而upstream方式是由下游pipeline使用的。

(2) build步驟是可以帶引數的,而upstream方式只是被動觸發,並且沒有帶引數。

image-20210711104746938

使用Conditional BuildStep外掛處理複雜的判斷邏輯

Conditional BuildStep外掛(https://plugins.jenkins.io/conditional-buildstep)可以讓我們像使用when指令一樣進行條件判斷。以下程式碼就是安裝Conditional BuildStep外掛後的寫法。

image-20210711105208635

使用input步驟

執行input步驟會暫停pipeline,直到使用者輸入引數。這是一種特殊的引數化pipeline的方法。我們可以利用input步驟實現以下兩種場景:

(1)實現簡易的審批流程。例如,pipeline暫停在部署前的階段,由負責人點選確認後,才能部署。

(2)實現手動測試階段。在pipeline中增加一個手動測試階段,該階段中只有一個input步驟,當手動測試通過後,測試人員才可以通過這個input步驟。

image-20210711105414434 image-20210711105438426

input步驟可以與timeout步驟實現超時自動中止pipeline,防止無限等待。以下pipeline一小時後不處理就自動中止。

image-20210711105634260

9. 憑證管理

為什麼要憑證管理

在Jenkinsfile或部署指令碼中使用明文密碼會造成安全隱患。但是為什麼還頻繁出現明文密碼被上傳到GitHub上的情況呢

(1)程式設計師或運維人員不知道如何保護密碼。

(2)管理者沒有足夠重視,否則會給更多的時間讓程式設計師或運維人員想辦法隱藏明文密碼。

憑證是什麼

比如使用SSH登入遠端機器時,使用者名稱和密碼或SSH key就是憑證。而這些憑證不可能以明文寫在Jenkinsfile中。Jenkins憑證管理指的就是對這些憑證進行管理。

為了最大限度地提高安全性,在Jenkins master節點上對憑證進行加密儲存(通過Jenkins例項ID加密),只有通過它們的憑證ID才能在pipeline中使用,並且限制了將證照從一個Jenkins例項複製到另一個Jenkins例項的能力。

也因為所有的憑證都被儲存在Jenkins master上,所以在Jenkins master上最好不要執行任務,以免被pipeline非法讀取出來。

建立憑證

image-20210711111054019

• Kind:選擇憑證型別。

• Scope:憑證的作用域。有兩種作用域:

​ ◦ Global,全域性作用域。如果憑證用於pipeline,則使用此種作用域。

​ ◦ System,如果憑證用於Jenkins本身的系統管理,例如電子郵件身份驗證、代理連線等,則使用此種作用域。

• ID:在pipeline使用憑證的唯一標識。

新增憑證後,安裝Credentials Binding Plugin外掛(https://plugins.jenkins.io/credentials-binding),通過其提供的withCredentials步驟就可以在pipeline中使用憑證了。

常用憑證型別

  • Secret text
  • Username with password
  • Secret file
  • SSH Username with private key

優雅的使用憑證

宣告式pipeline提供了credentials helper方法(只能在environment指令中使用)來簡化憑證的使用。以下是使用方法。

https://weread.qq.com/web/reader/12f320007184556612f32b6k14b3246024514bfa6bb1534

使用HashiCorp Vault(加強版的憑證管理工具)

在Jenkins日誌中隱藏敏感資訊

10. 製品管理

常見的製品管理倉庫

  • Nexus
  • Artifactory

版本號管理

11.視覺化構建及檢視

Green Balls外掛

這個外掛的功能是讓構建成功的pipeline程式設計綠色。

Build Monitor View外掛

Build Monitor View外掛(https://plugins.jenkins.io/build-monitor-plugin)可以將Jenkins專案以一塊“看板”的形式呈現。

檢視

使用檢視可以對pipeline進行分組管理。

12.自動化部署

部署和釋出的區別

部署是指將應用程式放到對應的伺服器上。

釋出是指使用者能訪問到新的功能點。

Jenkins整合Ansible實現自動化部署

Ansible採用了與Puppet、Chef不一樣的解決方案,不需要在受控機器上安裝額外的客戶端軟體。原因是Ansible使用的是SSH協議與受控機器進行通訊的,一般伺服器預設有SSH服務。Ansible也因此被稱為agentless(去客戶端的)。

Puppet和Chef都自己做了一套DSL,而Ansible使用YAML格式作為自己的DSL格式。

13. 通知

郵件通知

釘釘通知

Http請求通知

14. 分散式構建與並行構建

Jenkins的架構

Jenkins採用的是“master+agent”架構(有時也稱為“master+slave”架構)。Jenkins master負責提供介面、處理HTTP請求及管理構建環境;構建的執行則由Jenkins agent負責(早期,agent也被稱為slave。目前還有一些外掛沿用slave的概念)。

基於這樣的架構,只需要增加agent就可以輕鬆支援更多的專案同時執行。這種方式稱為Jenkins agent的橫向擴容。

對於Jenkins master,存在單節點問題是顯而易見的,但是目前還沒有很好的解決方案。

image-20210711141901828

在學習Jenkins的過程中,發現各種文件中摻雜著node、executor、agent、slave4個術語,新手很容易被它們弄得一頭霧水。它們分別是什麼意思呢?

• node:節點,指包含Jenkins環境及有能力執行專案的機器。master和agent都被認為是節點。

• agent:代理,在概念上指的是相對於Jenkins master的一種角色,實際上是指執行在機器或容器中的一個程式,它會連線上Jenkins master,並執行Jenkinsmaster分配給它的任務。

• slave:“傀儡”,與agent表達的是一個東西,只是叫法不同。

• executor:執行器,是真正執行專案的單元。一個執行器可以被理解為一個單獨的程式(事實上是執行緒)。在一個節點上可以執行多個執行器。

增加agent

https://weread.qq.com/web/reader/12f320007184556612f32b6k697324802676974ce5aceab

如何使用agent

agent部分描述的是整個pipeline或在特定階段執行任務時所在的agent。換句話說,Jenkins master根據此agent部分決定將任務分配到哪個agent上執行。agent部分必須在pipeline塊內的頂層定義,而stage塊內的定義是可選的。

image-20210711143736693

agent any告訴Jenkins master任何可用的agent都可以執行。

agent部分的定義可以放在階段中,用於指定該stage執行時的agent。

image-20210711143852693

通過標籤指定agent

image-20210711144220503

customWorkspace,自定義工作目錄。

不分配具體的agent

如果希望每個stage都執行在指定的agent中,那麼pipeline就不需要指定agent了。

image-20210711144653276 image-20210711144713702

在預設情況下,階段內所有的程式碼都將在指定的Jenkins agent上執行。when指令提供了一個beforeAgent選項,當它的值為true時,只有符合when條件時才會進入該Jenkins agent。這樣就可以避免沒有必要的工作空間的分配,也就不需要等待可用的Jenkins agent了。

將任務構建交給Docker

image-20210711145509583

Docker拉取映象時,預設是從Docker官方中心倉庫拉取的。那麼如何實現從私有倉庫拉取呢?比如在“製品管理”章節中在Nexus上建立的私有倉庫。

Docker外掛為我們提供了介面操作,具體步驟如下:

進入Manage Jenkins→Configure System頁面,找到“Pipeline ModelDefinition”部分

image-20210711145612897

Docker Label:當 pipeline 中的 agent 部分沒有指定 label 選項時,就會使用此配置。如docker {image'maven:3-alpine'}。

• Docker registry URL:Docker私有倉庫地址。

• Registry credentials:登入Docker私有倉庫的憑證。

並行構建

可以用於優化構建速度。

一些名詞

  • pipeline as code

參考

相關文章