基于Docker+Jenkins部署实现接口自动化持续集成
前言
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux
、Mac
或Windows
机器上,也可以实现虚拟化。容器是完全使用沙箱机制,互相之间不会有任何接口。
Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件,支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。
环境搭建
前置条件
安装Docker
,Linux 安装、macOS 安装、Windows 安装(比较麻烦,不建议)
正式开始
Docker部署Jenkins(2.346.2)
,CentOS8
下。非root
账号命令行前需添加sudo
创建网络
sudo docker network create jenkins
下载并运行 docker:dind 镜像
sudo docker run \ --name jenkins-docker \ --rm \ --detach \ --privileged \ --network jenkins \ --network-alias docker \ --env DOCKER_TLS_CERTDIR=/certs \ --volume jenkins-docker-certs:/certs/client \ --volume jenkins-data:/var/jenkins_home \ --publish 2376:2376 \ docker:dind \ --storage-driver overlav2
创建 docker 目录,下面创建一个 Dockerfile 文件,输入如下内容保存
FROM jenkins/jenkins:2.332.3-jdk11 USER root Run sed -i 's/deb.debian.org/mirrors.tencent.com/' /etc/apt/sources.list RUN apt-get update && apt-get install -y lsb-release RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \ https://download.docker.com/linux/debian/gpg RUN echo "deb [arch=$(dpkg --print-architecture) \ signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \ https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list RUN apt-get update && apt-get install -y docker-ce-cli
进入 docker 目录,执行下述命令,根据 Dockerfile 创建一个自己的 jenkins 镜像
sudo docker build -t myjenkins:latest .
启动自己的 jenkins 镜像
sudo docker run --name myjenkins --restart=on-failure --detach \ --network jenkins --env DOCKER_HOST=tcp://docker:2376 \ --env DOCKER_CERT_PATH=/certs/client --env DOCKER_TLS_VERIFY=1 \ --volume jenkins-data:/var/jenkins_home \ --volume jenkins-docker-certs:/certs/client:ro \ --publish 8080:8080 --publish 50000:50000 myjenkins:latest
运行后查看容器执行日志
sudo docker logs -f myjenkins
复制日志中
Please use the following password to proceed to installation
下面的密码,保存起来在浏览器访问
http://ip:8080
ip 替换成你服务器的 ip,进入初始安装页面,输入上面复制的密码,点击继续
选择
安装推荐的插件
安装插件需要耐心等待,比较耗时,插件安装完后进入创建账号页面
然后点击开始使用 jenkins,重启 jenkins,命令如下
sudo docker restart myjenkins
浏览器访问
http://ip:8080
ip 替换成你服务器的 ip,本地安装ip
输入127.0.0.1
或localhost
安装需要的其他插件
点菜单
系统管理
-->插件管理
-->可选插件
,依次搜索allure
、build user vars
、Build Timestamp
插件安装安装完成后,重启
jenkins
sudo docker restart myjenkins
全局工具配置
点击菜单
系统管理
-->全局工具配置
邮件配置
下面以网易企业邮箱为例,网易企业邮箱默认支持
SMTP
,SMTP 协议介绍系统管理
-->Manage Credentials
-->添加凭证
,邮箱凭据配置jenkins邮箱基础配置
,进入系统管理-->系统配置,做如下配置增加系统管理员邮件地址 格式:
发件人昵称<邮件地址>
邮件配置测试,网易企业邮箱 SMTP 地址查询
点击测试后提示成功,收件人邮箱会收到一封邮件,说明邮件发送没问题
配置扩展邮箱
该配置将会作为我们 jenkins 任务执行完成后的邮件模板
找到
Extended E-mail Notification
区域,做如下配置,点击高级-->Credentials
选择前面添加的全局邮件凭证
git凭据配置
进入
系统管理
-->Manage Credentials
,填写GitLab
/GitHub
/码云
的用户名和密码
企业微信添加群机器人
在企业微信群中添加机器人
选择你要通知的企业微信群,按照下图,选择
添加群机器人
,然后按照向导程序一步一步地操作,在这期间只需要你给机器人起个名字。至此你已经成功的创建了群机器人,在群对话窗口右下方,你可以看到刚刚创建的机器人。右键查看机器人资料,你就可以看到该机器人的
Webhook
地址,接下来流水线脚本会用到
Jenkins节点配置
因为我们要执行的是接口自动化,并且我们希望在
jenkins slave
节点上执行,而不是在 jenkins 所在的 master 上执行,因此我们需要配置节点相关的内容上图中 master 是指 jenkins 所在的服务器,用来统筹管理各个任务及配置
slave
指的是各个自动化任务执行的机器,也叫作节点master
通过管理节点,及任务中的节点配置将不同的任务分配到不同的设备上执行系统管理
->全局安全配置
,找到代理
进行如下设置并保存系统管理
->节点管理
,点击左侧的新建节点节点创建后初始是未连接状态
docker 部署 slave 节点及连接
我们使用节点是用来完成接口自动化项目的执行,先创建一个目录叫 dockerpython,在其中创建一个 Dockerfile 文件,内容如下
FROM jenkinsci/jnlp-slave USER root WORKDIR /home/jenkins Run sed -i 's/deb.debian.org/mirrors.tencent.com/' /etc/apt/sources.list RUN apt-get update && apt-get install -y python3 && apt-get install -y python3-pip
然后在
dockerpython
目录下执行下述命令,来创建镜像sudo docker build -t autotest1:latest .
系统管理
->节点管理
,查看slave1
节点http://ip:8080
指的是jenkins master
的连接地址522cb5d9dfdd8cxxxxxxxxxxx8d259fbe5
这是在节点管理中未连接的节点,可以看到下面这一串,复制过来,下面将要使用使用下述命令启动节点,ip 替换成你服务器的 ip,slave1 前面一串为上面复制
sudo docker run -itd --network jenkins --name slave1 autotest1 -url \ http://ip:8080 \ 522cb5d9dfdd8cxxxxxxxxxxx8d259fbe5 slave1
启动后输入如下命令查看日志,日志最后出现
Connected
字段,说明连接成功sudo docker logs -f slave1
pipeline流水线任务
点击 jenkins 首页新建任务,创建流水线任务
流水线脚本生成
实际上我们不用手动去编写这样的脚本,可以利用 jenkins 提供的生成功能来做
打开任务后,点击左侧的
流水线语法
生成节点脚本
生成接口自动化项目代码拉取脚本
生成 allure 报告处理脚本
生成邮件发送脚本
企业微信群消息发送脚本,直接用
Shell
脚本发送,Shell
脚本会集成到流水线脚本
中,暂时不用关注构建成功
curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1e352b24-8675-4d47-9652-xxxxxxxxxxxx' \ --header 'Content-Type: application/json' \ --data '{ "msgtype": "markdown", "markdown": { "content": "<font color=\'warning\'>**Jenkins任务通知**</font> \n >构建人: <font color=\'comment\'>${env.BUILD_USER}</font> >构建时间: <font color=\'comment\'>${BUILD_TIMESTAMP}</font> >任务名称: <font color=\'comment\'>${JOB_BASE_NAME}</font> >构建日志: <font color=\'comment\'>[点击查看](${BUILD_URL}/console)</font> >测试报告: <font color=\'comment\'>[点击查看](${BUILD_URL}/allure)</font> >构建状态: <font color=\'info\'>**Success**</font> >用例总数为: <font color=\'comment\'>**${TOTAL}**</font> >通过用例数: <font color=\'info\'>**${PASSED}**</font> >失败用例数: <font color=\'warning\'>**${FAILED}**</font> >错误用例数: <font color=\'comment\'>${ERROR}</font> >跳过用例数: <font color=\'comment\'>${SKIPPED}</font> >通过率为: <font color=\'comment\'>**${SUCCESS_RATE}**</font> >用例执行时长: <font color=\'comment\'>${TOTAL_TIMES}</font>" } }'
构建失败
curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1e352b24-8675-4d47-9652-xxxxxxxxxxxx' \ --header 'Content-Type: application/json' \ --data '{ "msgtype": "markdown", "markdown": { "content": "<font color=\'warning\'>**Jenkins任务通知**</font> \n >构建人: <font color=\'comment\'>${env.BUILD_USER}</font> >构建时间: <font color=\'comment\'>${BUILD_TIMESTAMP}</font> >运行时长: <font color=\'comment\'>${currentBuild.durationString}</font> >任务名称: <font color=\'comment\'>${JOB_BASE_NAME}</font> >构建日志: <font color=\'comment\'>[点击查看](${BUILD_URL}/console)</font> >构建状态: <font color=\'comment\'>**Failure**</font>" } }'
最终得
pipeline
脚本如下pipeline { agent { label 'slave1' } stages { stage('拉取自动化项目代码') { steps { git credentialsId: 'c85edf96-60de-4259-abdf-xxxxxxxxxxxx', url: '代码仓库地址' } } stage('执行自动化测试') { steps { // sh 'python3 -m pip install pipenv' 首次构建时执行,或者去docker容器手动执行安装pipenv sh 'pipenv install' sh 'pipenv run python main.py' } } stage('生成测试报告') { steps { allure includeProperties: false, jdk: '', results: [[path: 'allure-results']] } } stage('读取用例执行结果文件, 设置环境变量') { steps { script { def data = readFile(file: 'result.txt') // 按行读取 def lines = data.readLines() def result_list = [] for (line in lines) { String[] str; str = line.split('='); result_list.add(str) } // 设置环境变量 env.TOTAL = result_list[0][1] env.PASSED = result_list[1][1] env.FAILED = result_list[2][1] env.ERROR = result_list[3][1] env.SKIPPED = result_list[4][1] env.SUCCESS_RATE = result_list[5][1] env.TOTAL_TIMES = result_list[6][1] } } } stage('企业微信通知') { steps { script { wrap([$class: 'BuildUser']){ echo "full name is $BUILD_USER" echo "user id is $BUILD_USER_ID" echo "user email is $BUILD_USER_EMAIL" echo "TOTAL is $TOTAL" echo "PASSED is $PASSED" echo "FAILED is $FAILED" echo "ERROR is $ERROR" echo "SKIPPED is $SKIPPED" echo "SUCCESS_RATE is $SUCCESS_RATE" echo "TOTAL_TIMES is $TOTAL_TIMES" env.BUILD_USER = "${BUILD_USER}" } } } } stage('发送邮件') { steps { emailext body: '''<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title> </head> <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0"> <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> <tr> 本邮件由系统自动发出,无需回复!<br/> 各位同事,大家好,以下为${PROJECT_NAME}项目构建信息</br> <td><font color="#CC0000">构建结果 - ${BUILD_STATUS}</font></td> </tr> <tr> <td><br /> <b><font color="#0B610B">构建信息</font></b> <hr size="2" width="100%" align="center" /></td> </tr> <tr> <td> <ul> <li>项目名称: ${PROJECT_NAME}</li> <li>构建编号: 第${BUILD_NUMBER}次构建</li> <li>触发原因: ${CAUSE}</li> <li>任务地址: <a href="${BUILD_URL}">${BUILD_URL}</a></li> <li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li> <li>测试报告: <a href="${BUILD_URL}allure">${BUILD_URL}allure</a></li> <li>构建状态: ${BUILD_STATUS}</li> <li>用例总数为: <span style="font-weight: bold;">${ENV, var="TOTAL"}</span></li> <li>通过用例数: <span style="color: green;font-weight: bold;">${ENV, var="PASSED"}</span></li> <li>失败用例数: <span style="color: red;font-weight: bold;">${ENV, var="FAILED"}</span></li> <li>错误用例数: ${ENV, var="ERROR"}</li> <li>跳过用例数: ${ENV, var="SKIPPED"}</li> <li>通过率为: <span style="font-weight: bold;">${ENV, var="SUCCESS_RATE"}</span></li> <li>用例执行时长: ${ENV, var="TOTAL_TIMES"}</li> </ul> </td> </tr> </table> </body> </html>''', subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!', to: '[email protected]' } } } post { success { script { WeiXinSuccess() } } failure { script { WeiXinFailure() } } } } def WeiXinSuccess(){ sh """ curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1e352b24-8675-4d47-9652-xxxxxxxxxxxx' \ --header 'Content-Type: application/json' \ --data '{ "msgtype": "markdown", "markdown": { "content": "<font color=\'warning\'>**Jenkins任务通知**</font> \n >构建人: <font color=\'comment\'>${env.BUILD_USER}</font> >构建时间: <font color=\'comment\'>${BUILD_TIMESTAMP}</font> >任务名称: <font color=\'comment\'>${JOB_BASE_NAME}</font> >构建日志: <font color=\'comment\'>[点击查看](${BUILD_URL}/console)</font> >测试报告: <font color=\'comment\'>[点击查看](${BUILD_URL}/allure)</font> >构建状态: <font color=\'info\'>**Success**</font> >用例总数为: <font color=\'comment\'>**${TOTAL}**</font> >通过用例数: <font color=\'info\'>**${PASSED}**</font> >失败用例数: <font color=\'warning\'>**${FAILED}**</font> >错误用例数: <font color=\'comment\'>${ERROR}</font> >跳过用例数: <font color=\'comment\'>${SKIPPED}</font> >通过率为: <font color=\'comment\'>**${SUCCESS_RATE}**</font> >用例执行时长: <font color=\'comment\'>${TOTAL_TIMES}</font>" } }' """ } def WeiXinFailure(){ sh """ curl --location --request POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1e352b24-8675-4d47-9652-xxxxxxxxxxxx' \ --header 'Content-Type: application/json' \ --data '{ "msgtype": "markdown", "markdown": { "content": "<font color=\'warning\'>**Jenkins任务通知**</font> \n >构建人: <font color=\'comment\'>${env.BUILD_USER}</font> >构建时间: <font color=\'comment\'>${BUILD_TIMESTAMP}</font> >运行时长: <font color=\'comment\'>${currentBuild.durationString}</font> >任务名称: <font color=\'comment\'>${JOB_BASE_NAME}</font> >构建日志: <font color=\'comment\'>[点击查看](${BUILD_URL}/console)</font> >构建状态: <font color=\'comment\'>**Failure**</font>" } }' """ }
首页选择流水线任务-->配置,输入上述脚本保存,执行构建
构建效果如下
企业微信群消息
邮件报告内容
后记
怎么获取测试用例执行结果
使用
pytest
自带的 hook 函数pytest_terminal_summary
可以统计用例执行情况,收集测试结果在项目根目录下
conftest.py
文件中输入如下代码def pytest_terminal_summary(terminalreporter, exitstatus, config): """ 收集测试结果 """ result = { 'total': terminalreporter._numcollected, 'passed': len([i for i in terminalreporter.stats.get('passed', []) if i.when != 'teardown']), 'failed': len([i for i in terminalreporter.stats.get('failed', []) if i.when != 'teardown']), 'error': len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown']), 'skipped': len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown']), 'success_rate': len(terminalreporter.stats.get('passed', [])) / terminalreporter._numcollected * 100, 'total_times': round(time.time() - terminalreporter._sessionstarttime, 2), } // 将结果保存到本地 write_result_to_txt(**result) def write_result_to_txt( total, passed, failed, error, skipped, success_rate, total_times, ): """ 将测试结果写入本地文件 """ with open('result.txt', 'w') as fp: fp.write("TOTAL=%s" % total + "\n") fp.write("PASSED=%s" % passed + "\n") fp.write("FAILED=%s" % failed + "\n") fp.write("ERROR=%s" % error + "\n") fp.write("SKIPPED=%s" % skipped + "\n") fp.write("SUCCESS_RATE=%.2f%%" % success_rate + "\n") fp.write("TOTAL_TIMES=%.2fs" % total_times)
在 Jenkins 流水线脚本中将 txt 文件内容读取出来,加入环境变量
stage('读取用例执行结果文件, 设置环境变量') { steps { script { def data = readFile(file: 'result.txt') // 按行读取 def lines = data.readLines() def result_list = [] for (line in lines) { String[] str; str = line.split('='); result_list.add(str) } // 设置环境变量 env.TOTAL = result_list[0][1] env.PASSED = result_list[1][1] env.FAILED = result_list[2][1] env.ERROR = result_list[3][1] env.SKIPPED = result_list[4][1] env.SUCCESS_RATE = result_list[5][1] env.TOTAL_TIMES = result_list[6][1] } } }
遇到的问题
Jenkins 构建时,发现时区不对,显示的 UTC 时间。这是因为执行 jenkins 任务的 docker 容器本地时间是 UTC,解决办法如下: Jenkins 系统时间不正确解决方案
其它参考资料
Jenkins: 配置 jenkins 任务构建成功通知企业微信@相关人
把 pytest 运行结果通过 jenkins 发送到邮件正文里