基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

作者 | 劉春明,責編 | Carol

封圖 | CSDN下載於視覺中國

目前公司為了降低機器使用成本,對所有的AWS虛擬機器進行了盤點,發現利用率低的機器中,有一部分是測試團隊用作Jenkins Slave的機器。

這不出我們所料,使用虛擬機器作為Jenkins Slave,一定會存在很大浪費,因為測試Job執行完成後,Slave 處於空閒狀態時,虛擬機器資源並沒有被釋放掉。

除了資源利用率不高外,虛擬機器作為Jenkins Slave還有其他方面的弊端,

比如資源分配不均衡

,有的 Slave 要執行的 job 出現排隊等待,而有的 Slave 可能正處於空閒狀態。

另外,

擴容不方便,

使用虛擬機器作為Slave,想要增加Jenkins Slave,需要手動掛載虛擬機器到Jenkins Master上,並給Slave配置環境,導致管理起來非常不方便,維護起來也是比較耗時。

在2019年,運維團隊搭建了Kubernetes容器雲平臺。為了實現公司降低機器使用成本的目標,我所在的車聯網測試團隊考慮將Jenkins Slave全面遷移到Kubernetes容器雲平臺。

主要是想提高Jenkins Slave資源利用率,並且提供比較靈活的彈性擴容能力滿足越來越多的測試Job對Slave的需求。

本文就是我們的實踐總結。

整體架構

我們知道Jenkins是採用的Master-Slave架構,Master負責管理Job,Slave負責執行Job。在我們公司Master搭建在一臺虛擬機器上,Slave則來自Kubernetes平臺,每一個Slave都是Kubernetes平臺中的一個Pod,Pod是Kubernetes的原子排程單位,更多Kubernetes的基礎知識不做過多介紹,

在這篇文章中,大家只要記住Pod就是Jenkins Slave就行了。

基於 Kubernetes 搭建的 Jenkins Slave 叢集示意圖如下。

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

在這個架構中,Jenkins Master 負責管理測試Job,為了能夠利用Kubernetes平臺上的資源,需要在Master上安裝Kubernetes-plugin。

Kubernetes平臺負責產生Pod,用作Jenkins Slave執行Job任務。當Jenkins Master上有Job被排程時,Jenkins Master透過Kubernetes-plugin向Kubernetes平臺發起請求,請Kubernetes根據Pod模板產生對應的Pod物件,Pod物件會向Jenkins Master發起JNLP請求,以便連線上Jenkins Master,

一旦連線成功,就可以在Pod上面執行Job了。

Pod中所用的容器映象則來自Harbor,在這裡,一個Pod中用到了三個映象,分別是

Java映象

Python映象

JNLP映象

。Java映象提供Java環境,可用來進行編譯、執行Java編寫的測試程式碼,Python映象提供Python環境,用來執行Python編寫的測試程式碼,JNLP映象是Jenkins官方提供的Slave映象。

使用Kubernetes作為Jenkins Slave,

如何解決前面提到的使用虛擬機器時的資源利用率低、資源分配不均的問題,並且實現Slave動態彈性擴容的呢?

首先,只有在Jenkins Master有Job被排程時,才會向Kubernetes申請Pod建立Jenkins Slave,測試Job執行完成後,所用的Slave會被Kubernetes回收。不會像虛擬機器作為Slave時,有Slave閒置的情況出現,從而提高了計算資源的利用率。

其次,資源分配不均衡的主要問題在於不同測試小組之間,因為測試環境和依賴不同而不能共享Jenkins Slave。而Kubernetes平臺打破了共享的障礙,只要Kubernetes叢集中有計算資源,那麼就可以從中申請到適合自己專案的Jenkins Slave,從而不再會發生Job排隊的現象。

藉助Kubernetes實現Slave動態彈性擴容就更加簡單了。因為Kubernetes天生就支援彈性擴容。當監控到Kubernetes資源不夠時,只需要透過運維平臺向其中增加Node節點即可。對於測試工作來講,這一步完全是透明的。

配置Jenkins Master

要想利用Kubernetes作為Jenkins Slave,第一步是在Jenkins Master上安裝Kubernetes外掛。安裝方法很簡單,用Jenkisn管理員賬號登入Jenkins,在Manage Plugin頁面,搜尋Kubernetes,勾選並安裝即可。

接下來就是在Jenkins Master上配置Kubernetes連線資訊。Jenkins Master連線Kubernetes雲需要配置三個關鍵資訊:

名稱

證書

。全部配置資訊如下圖所示。

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

名稱將會在Jenkins Pipeline中用到,配置多個Kubernetes雲時,需要為每一個雲都指定一個不同的名稱。

Kubernetes地址指的是Kubernetes API server的地址,Jenkins Master正是透過Kubernetes plugin向這個地址發起排程Pod的請求。

Kubernetes服務證書key是用來與Kubernetes API server建立連線的,生成方法是,從Kubernetes API server的/root/。kube/config檔案中,獲取/root/。kube/config中certificate-authority-data的內容,並轉化成base64 編碼的檔案即可。

#echo certificate-authority-data的內容 | base64 -D > ~/ca。crt

ca。crt的內容就是Kubernetes服務證書key。

上圖中的憑據,是使用客戶端的證書和key生成的pxf檔案。先將/root/。kube/config中client-certificate-data和client-key-data的內容分別轉化成base64 編碼的檔案。

#echo client-certificate-data的內容 | base64 -D > ~/client。crt

#echo client-key-data的內容 | base64 -D > ~/client。crt

根據這兩個檔案製作pxf檔案:

# openssl pkcs12 -export -out ~/cert。pfx -inkey ~/client。key -in ~/client。crt -certfile ~/ca。crt

# Enter Export Password:

# Verifying - Enter Export Password:

自定義一個password並牢記。

點選Add,選擇型別是Cetificate,點選Upload certificate,選取前面生成cert。pfx檔案,輸入生成cert。pfx檔案時的密碼,就完成了憑據的新增。

接著再配置一下Jenkins URL和同時可以被排程的Pod數量。

配置完畢,可以點選 “Test Connection” 按鈕測試是否能夠連線到 Kubernetes,如果顯示 Connection test successful 則表示連線成功,配置沒有問題。

配置完Kubernetes外掛後,在Jenkins Master上根據需要配置一些公共工具,比如我這了配置了allure,用來生成報告。這樣在Jenkins Slave中用到這些工具時,就會自動安裝到Jenkins Slave中了。

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

定製Jenkins Pipeline

配置完成Kubernetes連線資訊後,就可以在測試Job的Pipeline中使用kubernetes作為agent了。與使用虛擬機器作為Jenkins Slave的區別主要在於pipeline。agent部分。下面程式碼是完整的Jenkinsfile內容。

pipeline {

agent {

kubernetes{

cloud ‘kubernetes-bj’//Jenkins Master上配置的Kubernetes名稱

label ‘SEQ-AUTOTEST-PYTHON36’//Jenkins slave的字首

defaultContainer ‘python36’// stages和post步驟中預設用到的container。如需指定其他container,可用語法 container(“jnlp”){。。。}

idleMinutes 10//所建立的pod在job結束後的空閒生存時間

yamlFile “jenkins/jenkins_pod_template。yaml”// pod的yaml檔案

}

}

environment {

git_url = ‘git@github。com:liuchunming033/seq_jenkins_template。git’

git_key = ‘c8615bc3-c995-40ed-92ba-d5b66’

git_branch = ‘master’

email_list = ‘liuchunming@163。com’

}

options {

buildDiscarder(logRotator(numToKeepStr: ‘30’)) //儲存的job構建記錄總數

timeout(time: 30, unit: ‘MINUTES’) //job超時時間

disableConcurrentBuilds() //不允許同時執行流水線

}

stages {

stage(‘拉取測試程式碼’) {

steps {

git branch: “${git_branch}”, credentialsId: “${git_key}”, url: “${git_url}”

}

}

stage(‘安裝測試依賴’) {

steps {

sh “pipenv install”

}

}

stage(‘執行測試用例’) {

steps {

sh “pipenv run py。test”

}

}

}

post {

always{

container(“jnlp”){ //在jnlp container中生成測試報告

allure includeProperties: false, jdk: ‘’, report: ‘jenkins-allure-report’, results: [[path: ‘allure-results’]]

}

}

}

}

上面的Pipeline中,與本文相關的核心部分是agent.kubernetes一段,這一段描述瞭如何在kubernetes 平臺生成Jenkins Slave。

cloud,

是Jenkins Master上配置的Kubernetes名稱,用來標識當前的Pipeline使用的是哪一個Kubernetes cloud。

label,

是Jenkins Slave名稱的字首,用來區分不同的Jenkins Slave,當出現異常時,可以根據這個名稱到Kubernetes cloud中進行debug。

defaultContainer,

在Jenkins Slave中我定義了是三個container,在前面有介紹。defaultContainer表示在Pipeline中的stages和post階段,程式碼執行的預設container。也就是說,如果在stages和post階段不指定container,那麼程式碼都是預設執行在defaultContainer裡面的。如果要用其他的container執行程式碼,則需要透過類似container(“jnlp”){…}方式來指定。

idleMinutes,

指定了Jenkins Slave上執行的測試job結束後,Jenkins Slave可以保留的時長。在這段時間內,Jenkins Slave不會被Kubernetes回收,這段時間內如果有相同label的測試Job被排程,那麼可以繼續使用這個空閒的Jenkins Slave。這樣做的目的是,提高Jenkins Slave的利用率,避免Kubernetes進行頻繁排程,因為成功產生一個Jenkins Slave還是比較耗時的。

yamlFile,

這個檔案是標準的Kubernetes的Pod 模板檔案。Kubernetes根據這個檔案產生Pod物件,用來作為Jenkins Slave。這個檔案中定義了三個容器(Container)以及排程的規則和外部儲存。這個檔案是利用Kubernetes作為Jenkins Slave叢集的核心檔案,下面將詳細介紹這個檔案的內容。

至此,測試Job的Pipeline就建立好了。

定製Jenkins Slave模板

使用虛擬機器作為Jenkins Slave時,如果新加入一臺虛擬機器,我們需要對虛擬機器進行初始化,主要是安裝工具軟體、依賴包,並連線到Jenkins Master上。使用Kubernetes cloud作為Jenkins Slave叢集也是一樣,要定義Jenkins Slave使用的作業系統、依賴軟體和外部磁碟等資訊。只不過這些資訊被寫在了一個Yaml檔案中,這個檔案是Kubernetes的Pod 物件的標準模板檔案。Kubernetes會自根據這個Yaml檔案,產生Pod並連線到Jenkins Master上。

這個Yaml檔案內容如下:

apiVersion: v1

kind: Pod

metadata:

# ① 指定 Pod 將產生在Kubernetes的哪個namespace下,需要有這個namespace的許可權

namespace: sqe-test

spec:

containers:

# ② 必選,負責連線Jenkins Master,注意name一定要是jnlp

- name: jnlp

image: swc-harbor。nioint。com/sqe/jnlp-slave:root_user

imagePullPolicy: Always

# 將Jenkins的WORKSPACE(/home/jenkins/agent)掛載到jenkins-slave

volumeMounts:

- mountPath: /home/jenkins/agent

name: jenkins-slave

# ③ 可選,python36環境,已安裝pipenv,負責執行python編寫的測試程式碼

- name: python36

image: swc-harbor。nioint。com/sqe/automation_python36:v1

imagePullPolicy: Always

# 透過cat命令,讓這個container保持持續執行

command:

- cat

tty: true

env:

# 設定pipenv的虛擬環境路徑變數 WORKON_HOME

- name: WORKON_HOME

value: /home/jenkins/agent/。local/share/virtualenvs/

# 建立/home/jenkins/agent目錄並掛載到jenkins-slave Volume上

volumeMounts:

- mountPath: /home/jenkins/agent

name: jenkins-slave

# 可以對Pod使用的資源進行限定,可調。儘量不要用太多,夠用即可。

resources:

limits:

cpu: 300m

memory: 500Mi

# ④ 可選,Java8環境,已安裝maven,負責執行Java編寫的測試程式碼

- name: java8

image: swc-harbor。nioint。com/sqe/automation_java8:v2

imagePullPolicy: Always

command:

- cat

tty: true

volumeMounts:

- mountPath: /home/jenkins/agent

name: jenkins-slave

# ⑤ 宣告一個名稱為 jenkins-slave 的 NFS Volume,多個container共享

volumes:

- name: jenkins-slave

nfs:

path: /data/jenkins-slave-nfs/

server: 10。125。234。64

# ⑥ 指定在Kubernetes的哪些Node節點上產生Pod

nodeSelector:

node-app: normal

node-dept: sqe

透過上面的Yaml檔案,可以看到透過 spec。containers 在Pod中定義了三個容器,分別是負責連線Jenkins Master的jnlp,負責執行Python程式碼的python36,負責執行Java程式碼的java8。我們可以把Jenkins Slave比喻成豆莢,裡面的容器比喻成豆莢中的豆粒,每顆豆粒具有不同的職責。

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

同時,還聲明瞭一個叫作jenkins-slave 的volume,jnlp 容器將Jenkins WORKSPACE目錄(/home/jenkins/agent )mount到jenkins-slave 上。同時python36和java8這兩個容器也將目錄/home/jenkins/agent mount到jenkins-slave 上。從而,在任何一個容器中對/home/jenkins/agent 目錄的修改,在其他兩個容器中都能讀取到修改後的內容。掛載外部儲存的主要好處是可以將測試結果、虛擬環境持久化下來,特別是將虛擬環境持久化下來之後,不用每次執行測試建立新的虛擬環境,而是複用已有的虛擬環境,加快了整個測試執行的過程。

另外,還指定了使用kubernetes的哪一個Namespace名稱空間以及在哪些Node節點上產生Jenkins Slave。關於這個Yaml檔案的其他細節說明,我都寫在了檔案的註釋上,大家可以參考著理解。

定製容器映象

前面介紹了Jenkins Slave中用到了三個容器,下面我們分別來看下這三個容器的映象。

首先,DockerHub(https://hub。docker。com/r/jenkinsci/jnlp-slave)提供了Jenkins Slave的官方映象,我們這裡將官方映象中的預設使用者切換成root使用者,否則在執行測試用例時,可能會出現許可權問題。JNLP容器映象的Dockerfile如下:

FROM jenkinsci/jnlp-slave:latest

LABEL maintainer=“liuchunming@163。com”

USER root

Python映象是在官方的Python3。6。4映象中安裝了pipenv。因為我們團隊目前的Python專案都是用pipenv管理專案依賴的。這裡說一下,pipenv是pip的升級版,它既能為你專案建立獨立的虛擬環境,還能夠自動維護和管理專案的依賴軟體包。與pip使用requirements。txt管理依賴不同,pipenv使用Pipefile管理依賴,這裡的好處不展開介紹,有興趣的朋友可以檢視一下pipenv的官方文件https://github。com/pypa/pipenv。Python映象的Dockerfile如下:

FROM python:3。6。4

LABEL maintainer=“xxx@163。com”

USER root

RUN pip install——upgrade pip

RUN pip3 install pipenv

Java映象是根據DockerHub上的maven映象擴充套件來的。主要改動則是將公司內部使用的maven配置檔案settings。xml放到映象裡面。完整的Dockerfile如下:

FROM maven:3。6。3-jdk-8

LABEL maintainer=“xxx@163。com”

USER root

# 設定系統時區為北京時間

RUN mv /etc/localtime /etc/localtime。bak && \

ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \

echo “Asia/Shanghai” > /etc/timezone# 解決JVM與linux系統時間不一致問題

# 支援中文

RUN apt-get update && \

apt-get install locales -y && \

echo “zh_CN。UTF-8 UTF-8” > /etc/locale。gen && \

locale-gen

# 更新資源地址

ADD settings。xml /root/。m2/

# 安裝jacococli

COPY jacoco-plugin/jacococli。jar /usr/bin

RUN chmod +x /usr/bin/jacococli。jar

製作完容器映象之後,我們會將其push到公司內部的harbor上,以便kubernetes能夠快速的拉取映象。大家可以根據自己實際情況,按照專案需求製作自己的容器映象。

執行自動化測試

透過前面的步驟,我們使用Kubernetes作為Jenkins Slave的準備工作就全部完成了。接下來就是執行測試Job了。與使用虛擬機器執行測試Job相比,這一步其實完全相同。

建立一個Pipeline風格的Job,並進行如下配置:

基於 Jenkins 和 Kubernetes 的持續整合測試實踐瞭解一下!

配置完成後,點選Build就可以開始測試了。

效能最佳化

跟虛擬機器作為Jenkins Salve不同,Kubernetes生成Jenkins Slave是個動態建立的過程,因為是動態建立,就涉及到效率問題。解決效率問題可以從兩方面入手,一方面是儘量利用已有的Jenkins Slave來執行測試Job,另一方面是加快產生Jenkins Slave的效率。下面我們分別從這兩方面看看具體的最佳化措施。

7.1 充分利用已有的Jenkins Slave

充分利用已有的Jenkins Slave,可以從兩方面入手。

一方面,設定idleMinutes讓Jenkins Slave在執行完測試Job後,不要被立即消毀,而是可以空閒一段時間,在這段時間內如果有測試Job啟動,則可以分配到上面來執行,既提高了已有的Jenkins Slave的利用率,也避免建立Jenkins Slave耗費時間。

另一方面,在更多的測試Job流水線中,使用相同的label,這樣當前面的測試Job結束後,所使用的Jenkins Slave也能被即將啟動的使用相同lable的測試Job所使用。比如,測試job1使用的jenkins Slave 的lable是

DD-SEQ-AUTOTEST-PYTHON,那麼當測試job1結束後,使用相同lable的測試job2啟動後,既可以直接使用測試job1使用過的Jenkins Slave了。

7.2 加快Jenkins Slave的排程效率

Kubernetes上產生Jenkins Slave並加入到Jenkins Master的完整流程是:

Jenkins Master計算現在的負載情況;

Jenkins Master根據負載情況,按需透過Kubernetes Plugin向Kubernetes API server發起請求;

Kubernetes API server向Kubernetes叢集排程Pod;

Pod產生後透過JNLP協議自動連線到Jenkins Master。

後三個步驟都是很快的,主要受網路影響。而第一個步驟,Jenkins Master會經過一系列演算法計算之後,發現沒有可用的Jenkins Slave才決定向Kubernetes API server發起請求。

這個過程在Jenkins Master的預設啟動配置下是不高效的。經常會導致一個新的測試Job啟動後需要等一段時間,才開始在Kubernetes上產生Pod。

因此,需求對Jenkins Master的啟動項進行修改,主要涉及以下幾個引數:

-Dhudson。model。LoadStatistics。clock=2000

-Dhudson。slaves。NodeProvisioner。recurrencePeriod=5000

-Dhudson。slaves。NodeProvisioner。initialDelay=0

-Dhudson。model。LoadStatistics。decay=0。5

-Dhudson。slaves。NodeProvisioner。MARGIN=50

-Dhudson。slaves。NodeProvisioner。MARGIN0=0。85

Jenkins Master每隔一段時間會計算叢集負載,時間間隔由hudson。model。LoadStatistics。clock決定,預設是10秒,我們將其調整到2秒,以加快 Master計算叢集負載的頻率,從而更快的知道負載的變化情況。

比如原來最快需要10秒才知道目前有多少job需要被排程執行,現在只需要2秒。

當Jenkins Master計算得到叢集負載後,發現沒有可用的Jenkins Slave。Jenkins master會通知Kubernetes Plugin的NodeProvisioner以recurrencePeriod間隔生產Pod。因此recurrencePeriod值不能比hudson。model。LoadStatistics。clock小,否則會生成多個Jenkins slave。

initialDelay是一個延遲時間,原本用於確保讓靜態的Jenkins Slave和Master建立起來連線,因為我們這裡是使用Kubernetes外掛動態產生Jenkins slave,沒有靜態Jenkins Slave,所以我們將引數設定成0。

hudson。model。LoadStatistics。decay這個引數原本的意義是用於抑制評估master負載的抖動,對於評估得到的負載值有很大影響。預設decay是0。9。我們把decay設成了0。5,允許負載有比較大的波動,Jenkins Master評估的負載就是在當前儘可能真實的負載之上,評估的需要的Jenkins Slave的個數。

hudson。slaves。NodeProvisioner。MARGIN 和hudson。slaves。NodeProvisioner。MARGIN0,這兩個引數使計算出來的負載做整數向上對齊,從而可能多產生一個Slave,以此來提高效率。

將上面的引數,加入到Jenkins Mater啟動程序上,重啟Jenkins Master即生效。

java -Dhudson。model。LoadStatistics。clock=2000 -Dxxx -jar jenkins。war

總結

本文介紹了使用Kubernetes作為持續整合測試環境的優勢,並詳細介紹了使用方法,對其效能也進行了最佳化。透過這個方式完美解決虛擬機器作為Jenkins Slave的弊端。

除了自動化測試能夠從Kubernetes中收益之外,在效能測試環境搭建過程中,藉助Kubernetes動態彈性擴容的機制,對於大規模壓測叢集的建立,在效率、便捷性方面更具有明顯優勢。

作者介紹:劉春明,軟體測試技術佈道者,十年測試老兵,CSDN部落格專家,MSTC大會講師,ArchSummit講師,運營“明說軟體測試”公眾號。擅長測試框架開發、測試平臺開發、持續整合、測試環境治理等,熟悉服務端測試、APP測試、Web測試和效能測試。