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

admin 2025-05-29 48人围观 ,发现244个评论

作者|刘春明,责编|Carol

出品|CSDN云计算(ID:CSDNcloud)

目前公司为了降低机器使用成本,对所有的AWS虚拟机进行了盘点,发现利用率低的机器中,有一部分是测试团队用作JenkinsSlave的机器。这不出我们所料,使用虚拟机作为JenkinsSlave,一定会存在很大浪费,因为测试Job运行完成后,Slave处于空闲状态时,虚拟机资源并没有被释放掉。

除了资源利用率不高外,虚拟机作为JenkinsSlave还有其他方面的弊端,比如资源分配不均衡,有的Slave要运行的job出现排队等待,而有的Slave可能正处于空闲状态。另外,扩容不方便,使用虚拟机作为Slave,想要增加JenkinsSlave,需要手动挂载虚拟机到JenkinsMaster上,并给Slave配置环境,导致管理起来非常不方便,维护起来也是比较耗时。

在2019年,运维团队搭建了Kubernetes容器云平台。为了实现公司降低机器使用成本的目标,我所在的车联网测试团队考虑将JenkinsSlave全面迁移到Kubernetes容器云平台。主要是想提高JenkinsSlave资源利用率,并且提供比较灵活的弹性扩容能力满足越来越多的测试Job对Slave的需求。

本文就是我们的实践总结。


整体架构

我们知道Jenkins是采用的Master-Slave架构,Master负责管理Job,Slave负责运行Job。在我们公司Master搭建在一台虚拟机上,Slave则来自Kubernetes平台,每一个Slave都是Kubernetes平台中的一个Pod,Pod是Kubernetes的原子调度单位,更多Kubernetes的基础知识不做过多介绍,在这篇文章中,大家只要记住Pod就是JenkinsSlave就行了。

基于Kubernetes搭建的JenkinsSlave集群示意图如下。


在这个架构中,JenkinsMaster负责管理测试Job,为了能够利用Kubernetes平台上的资源,需要在Master上安装Kubernetes-plugin。

Kubernetes平台负责产生Pod,用作JenkinsSlave执行Job任务。当JenkinsMaster上有Job被调度时,JenkinsMaster通过Kubernetes-plugin向Kubernetes平台发起请求,请Kubernetes根据Pod模板产生对应的Pod对象,Pod对象会向JenkinsMaster发起JNLP请求,以便连接上JenkinsMaster,一旦连接成功,就可以在Pod上面执行Job了。

Pod中所用的容器镜像则来自Harbor,在这里,一个Pod中用到了三个镜像,分别是Java镜像、Python镜像、JNLP镜像。Java镜像提供Java环境,可用来进行编译、执行Java编写的测试代码,Python镜像提供Python环境,用来执行Python编写的测试代码,JNLP镜像是Jenkins官方提供的Slave镜像。

使用Kubernetes作为JenkinsSlave,如何解决前面提到的使用虚拟机时的资源利用率低、资源分配不均的问题,并且实现Slave动态弹性扩容的呢?

首先,只有在JenkinsMaster有Job被调度时,才会向Kubernetes申请Pod创建JenkinsSlave,测试Job执行完成后,所用的Slave会被Kubernetes回收。不会像虚拟机作为Slave时,有Slave闲置的情况出现,从而提高了计算资源的利用率。

其次,资源分配不均衡的主要问题在于不同测试小组之间,因为测试环境和依赖不同而不能共享JenkinsSlave。而Kubernetes平台打破了共享的障碍,只要Kubernetes集群中有计算资源,那么就可以从中申请到适合自己项目的JenkinsSlave,从而不再会发生Job排队的现象。

借助Kubernetes实现Slave动态弹性扩容就更加简单了。因为Kubernetes天生就支持弹性扩容。当监控到Kubernetes资源不够时,只需要通过运维平台向其中增加Node节点即可。对于测试工作来讲,这一步完全是透明的。


配置JenkinsMaster

要想利用Kubernetes作为JenkinsSlave,第一步是在JenkinsMaster上安装Kubernetes插件。安装方法很简单,用Jenkisn管理员账号登录Jenkins,在ManagePlugin页面,搜索Kubernetes,勾选并安装即可。

接下来就是在JenkinsMaster上配置Kubernetes连接信息。JenkinsMaster连接Kubernetes云需要配置三个关键信息:名称、地址和证书。全部配置信息如下图所示。


名称将会在JenkinsPipeline中用到,配置多个Kubernetes云时,需要为每一个云都指定一个不同的名称。

Kubernetes地址指的是KubernetesAPIserver的地址,JenkinsMaster正是通过Kubernetesplugin向这个地址发起调度Pod的请求。

Kubernetes服务证书key是用来与KubernetesAPIserver建立连接的,生成方法是,从KubernetesAPIserver的/root/.kube/config文件中,获取/root/.kube/config中certificate-authority-data的内容,并转化成base64编码的文件即可。

echoclient-certificate-data的内容|base64-D~/~/~/~/~/

Verifying-EnterExportPassword:

自定义一个password并牢记。

接着再配置一下JenkinsURL和同时可以被调度的Pod数量。

配置完Kubernetes插件后,在JenkinsMaster上根据需要配置一些公共工具,比如我这了配置了allure,用来生成报告。这样在JenkinsSlave中用到这些工具时,就会自动安装到JenkinsSlave中了。



定制JenkinsPipeline

配置完成Kubernetes连接信息后,就可以在测试Job的Pipeline中使用kubernetes作为agent了。与使用虚拟机作为JenkinsSlave的区别主要在于部分。下面代码是完整的Jenkinsfile内容。

pipeline{

agent{

kubernetes{

cloud'kubernetes-bj'//JenkinsMaster上配置的Kubernetes名称

label'SEQ-AUTOTEST-PYTHON36'//Jenkinsslave的前缀

defaultContainer'python36'//stages和post步骤中默认用到的container。如需指定其他container,可用语法container("jnlp"){}

idleMinutes10//所创建的pod在job结束后的空闲生存时间

yamlFile"jenkins/jenkins_pod_"//pod的yaml文件

}

}

environment{

git_url='git@:liuchunming033/seq_jenkins_'

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{

gitbranch:"${git_branch}",credentialsId:"${git_key}",url:"${git_url}"

}

}

stage('安装测试依赖'){

steps{

sh"pipenvinstall"

}

}

stage('执行测试用例'){

steps{

sh""

}

}

}

post{

always{

container("jnlp"){//在jnlpcontainer中生成测试报告

allureincludeProperties:false,jdk:'',report:'jenkins-allure-report',results:[[path:'allure-results']]

}

}

}

}

上面的Pipeline中,与本文相关的核心部分是一段,这一段描述了如何在kubernetes平台生成JenkinsSlave。

cloud,是JenkinsMaster上配置的Kubernetes名称,用来标识当前的Pipeline使用的是哪一个Kubernetescloud。

label,是JenkinsSlave名称的前缀,用来区分不同的JenkinsSlave,当出现异常时,可以根据这个名称到Kubernetescloud中进行debug。

defaultContainer,在JenkinsSlave中我定义了是三个container,在前面有介绍。defaultContainer表示在Pipeline中的stages和post阶段,代码运行的默认container。也就是说,如果在stages和post阶段不指定container,那么代码都是默认运行在defaultContainer里面的。如果要用其他的container运行代码,则需要通过类似container(“jnlp”){…}方式来指定。

idleMinutes,指定了JenkinsSlave上运行的测试job结束后,JenkinsSlave可以保留的时长。在这段时间内,JenkinsSlave不会被Kubernetes回收,这段时间内如果有相同label的测试Job被调度,那么可以继续使用这个空闲的JenkinsSlave。这样做的目的是,提高JenkinsSlave的利用率,避免Kubernetes进行频繁调度,因为成功产生一个JenkinsSlave还是比较耗时的。

yamlFile,这个文件是标准的Kubernetes的Pod模板文件。Kubernetes根据这个文件产生Pod对象,用来作为JenkinsSlave。这个文件中定义了三个容器(Container)以及调度的规则和外部存储。这个文件是利用Kubernetes作为JenkinsSlave集群的核心文件,下面将详细介绍这个文件的内容。

至此,测试Job的Pipeline就建立好了。


定制JenkinsSlave模板

使用虚拟机作为JenkinsSlave时,如果新加入一台虚拟机,我们需要对虚拟机进行初始化,主要是安装工具软件、依赖包,并连接到JenkinsMaster上。使用Kubernetescloud作为JenkinsSlave集群也是一样,要定义JenkinsSlave使用的操作系统、依赖软件和外部磁盘等信息。只不过这些信息被写在了一个Yaml文件中,这个文件是Kubernetes的Pod对象的标准模板文件。Kubernetes会自根据这个Yaml文件,产生Pod并连接到JenkinsMaster上。

这个Yaml文件内容如下:

apiVersion:v1

kind:Pod

metadata:

②必选,负责连接JenkinsMaster,注意name一定要是jnlp

-name:jnlp

image:/sqe/jnlp-slave:root_user

imagePullPolicy:Always

③可选,python36环境,已安装pipenv,负责执行python编写的测试代码

-name:python36

image:/sqe/automation_python36:v1

imagePullPolicy:Always

设置pipenv的虚拟环境路径变量WORKON_HOME

-name:WORKON_HOME

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

可以对Pod使用的资源进行限定,可调。尽量不要用太多,够用即可。

resources:

limits:

cpu:300m

memory:500Mi

volumes:

-name:jenkins-slave

nfs:

path:/data/jenkins-slave-nfs/

server:10.125.234.64

设置系统时区为北京时间

RUNmv/etc/localtime/etc/\

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

echo"Asia/Shanghai"/etc/timezone支持中文

RUNapt-getupdate\

apt-getinstalllocales-y\

echo"zh_"/etc/\

locale-gen

安装jacococli

COPYjacoco-plugin//usr/bin

RUNchmod+x/usr/bin/

制作完容器镜像之后,我们会将其push到公司内部的harbor上,以便kubernetes能够快速的拉取镜像。大家可以根据自己实际情况,按照项目需求制作自己的容器镜像。

执行自动化测试

通过前面的步骤,我们使用Kubernetes作为JenkinsSlave的准备工作就全部完成了。接下来就是执行测试Job了。与使用虚拟机执行测试Job相比,这一步其实完全相同。

创建一个Pipeline风格的Job,并进行如下配置:

性能优化

跟虚拟机作为JenkinsSalve不同,Kubernetes生成JenkinsSlave是个动态创建的过程,因为是动态创建,就涉及到效率问题。解决效率问题可以从两方面入手,一方面是尽量利用已有的JenkinsSlave来运行测试Job,另一方面是加快产生JenkinsSlave的效率。下面我们分别从这两方面看看具体的优化措施。

7.1充分利用已有的JenkinsSlave

充分利用已有的JenkinsSlave,可以从两方面入手。

一方面,设置idleMinutes让JenkinsSlave在执行完测试Job后,不要被立即消毁,而是可以空闲一段时间,在这段时间内如果有测试Job启动,则可以分配到上面来执行,既提高了已有的JenkinsSlave的利用率,也避免创建JenkinsSlave耗费时间。

另一方面,在更多的测试Job流水线中,使用相同的label,这样当前面的测试Job结束后,所使用的JenkinsSlave也能被即将启动的使用相同lable的测试Job所使用。比如,测试job1使用的jenkinsSlave的lable是

DD-SEQ-AUTOTEST-PYTHON,那么当测试job1结束后,使用相同lable的测试job2启动后,既可以直接使用测试job1使用过的JenkinsSlave了。

7.2加快JenkinsSlave的调度效率

Kubernetes上产生JenkinsSlave并加入到JenkinsMaster的完整流程是:

JenkinsMaster计算现在的负载情况;

JenkinsMaster根据负载情况,按需通过KubernetesPlugin向KubernetesAPIserver发起请求;

KubernetesAPIserver向Kubernetes集群调度Pod;

Pod产生后通过JNLP协议自动连接到JenkinsMaster。

后三个步骤都是很快的,主要受网络影响。而第一个步骤,JenkinsMaster会经过一系列算法计算之后,发现没有可用的JenkinsSlave才决定向KubernetesAPIserver发起请求。这个过程在JenkinsMaster的默认启动配置下是不高效的。经常会导致一个新的测试Job启动后需要等一段时间,才开始在Kubernetes上产生Pod。

因此,需求对JenkinsMaster的启动项进行修改,主要涉及以下几个参数:

-=2000

-=5000

-=0

-=0.5

-=50

-=0.85

JenkinsMaster每隔一段时间会计算集群负载,时间间隔由决定,默认是10秒,我们将其调整到2秒,以加快Master计算集群负载的频率,从而更快的知道负载的变化情况。比如原来最快需要10秒才知道目前有多少job需要被调度执行,现在只需要2秒。

当JenkinsMaster计算得到集群负载后,发现没有可用的JenkinsSlave。Jenkinsmaster会通知KubernetesPlugin的NodeProvisioner以recurrencePeriod间隔生产Pod。因此recurrencePeriod值不能比小,否则会生成多个Jenkinsslave。

initialDelay是一个延迟时间,原本用于确保让静态的JenkinsSlave和Master建立起来连接,因为我们这里是使用Kubernetes插件动态产生Jenkinsslave,没有静态JenkinsSlave,所以我们将参数设置成0。

这个参数原本的意义是用于抑制评估master负载的抖动,对于评估得到的负载值有很大影响。默认decay是0.9。我们把decay设成了0.5,允许负载有比较大的波动,JenkinsMaster评估的负载就是在当前尽可能真实的负载之上,评估的需要的JenkinsSlave的个数。

和,这两个参数使计算出来的负载做整数向上对齐,从而可能多产生一个Slave,以此来提高效率。

将上面的参数,加入到JenkinsMater启动进程上,重启JenkinsMaster即生效。

=2000-

总结

本文介绍了使用Kubernetes作为持续集成测试环境的优势,并详细介绍了使用方法,对其性能也进行了优化。通过这个方式完美解决虚拟机作为JenkinsSlave的弊端。

除了自动化测试能够从Kubernetes中收益之外,在性能测试环境搭建过程中,借助Kubernetes动态弹性扩容的机制,对于大规模压测集群的创建,在效率、便捷性方面更具有明显优势。

作者介绍:刘春明,软件测试技术布道者,十年测试老兵,CSDN博客专家,MSTC大会讲师,ArchSummit讲师,运营“明说软件测试”公众号。擅长测试框架开发、测试平台开发、持续集成、测试环境治理等,熟悉服务端测试、APP测试、Web测试和性能测试。

☞一站式杀手级AI开发平台来袭!告别切换零散建模工具

☞北京四环堵车引发的智能交通大构想

☞拜托,别再问我什么是堆了!

☞北京四环堵车引发的智能交通大构想

☞你公司的虚拟机还闲着?基于Jenkins和Kubernetes的持续集成测试实践了解一下!

☞从到:详析这些年互联网的发展及未来方向

猜你喜欢
    不容错过