基於 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 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 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 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-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,並進行如下配置:
配置完成後,點選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測試和效能測試。