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


作者 | 劉春明


出品 | 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 編碼的文件即可。

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

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

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

<code># echo client-certificate-data的內容 | base64 -D > ~/client.crt
# echo client-key-data的內容 | base64 -D > ~/client.crt/<code>

根據這兩個文件製作pxf文件:

<code># openssl pkcs12 -export -out ~/cert.pfx -inkey ~/client.key -in ~/client.crt -certfile ~/ca.crt
# Enter Export Password:
# Verifying - Enter Export Password:/<code>

自定義一個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內容。

<code>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']]
            }   
        }
    }
}/<code>

上面的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文件內容如下:

<code>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/<code>

通過上面的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如下:

<code>FROM jenkinsci/jnlp-slave:latest
LABEL maintainer="[email protected]"
USER root/<code>

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

<code>FROM python:3.6.4
LABEL maintainer="[email protected]"
USER root
RUN pip install --upgrade pip
RUN pip3 install pipenv/<code>

Java鏡像是根據DockerHub上的maven鏡像擴展來的。主要改動則是將公司內部使用的maven配置文件settings.xml放到鏡像裡面。完整的Dockerfile如下:

<code>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/<code>

製作完容器鏡像之後,我們會將其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的啟動項進行修改,主要涉及以下幾個參數:

<code>-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/<code>

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即生效。

<code>java -Dhudson.model.LoadStatistics.clock=2000 -Dxxx -jar jenkins.war/<code>


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

總結


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

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

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


分享到:


相關文章: