基於 Jenkins 和 Kubernetes 的持續集成測試實踐瞭解一下

基於 Jenkins 和 Kubernetes 的持續集成測試實踐瞭解一下

作者 | 劉春明,責編 | Carol

出品 | CSDN 雲計算(ID:CSDNcloud)

封圖 | 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 和 Kubernetes 的持续集成测试实践了解一下

整體架構

我們知道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 和 Kubernetes 的持续集成测试实践了解一下

配置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 和 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 = '[email protected]:liuchunming033/seq_jenkins_template.git'

git_key = 'c8615bc3-c995-40ed-92ba-d5b66'

git_branch = 'master'

email_list = '[email protected]'

}

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 和 Kubernetes 的持续集成测试实践了解一下

定製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 和 Kubernetes 的持续集成测试实践了解一下

定製容器鏡像

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

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

FROM jenkinsci/jnlp-slave:latest

LABEL maintainer="[email protected]"

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="[email protected]"

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="[email protected]"

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能夠快速的拉取鏡像。大家可以根據自己實際情況,按照項目需求製作自己的容器鏡像。

基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下

執行自動化測試

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

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

基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下

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

基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下

性能優化

跟虛擬機作為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

基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下

總結

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

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

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

基于 Jenkins 和 Kubernetes 的持续集成测试实践了解一下

☞一站式殺手級 AI 開發平臺來襲!告別切換零散建模工具

☞北京四環堵車引發的智能交通大構想

☞拜託,別再問我什麼是堆了!

☞北京四環堵車引發的智能交通大構想

☞你公司的虛擬機還閒著?基於Jenkins和Kubernetes的持續集成測試實踐瞭解一下!

☞從Web1.0到Web3.0:詳析這些年互聯網的發展及未來方向


分享到:


相關文章: