基于Docker+Jenkins部署实现接口自动化持续集成

前言

Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的LinuxMacWindows机器上,也可以实现虚拟化。容器是完全使用沙箱机制,互相之间不会有任何接口。

10分钟快速掌握Docker必备基础知识

Docker入门教程

Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件,支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。

Jenkins用户手册

环境搭建

前置条件

安装DockerLinux安装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下面的密码,保存起来

    image-20220723122337555

  • 在浏览器访问http://ip:8080ip替换成你服务器的ip,进入初始安装页面,输入上面复制的密码,点击继续

    image-20220723122847348

  • 选择安装推荐的插件

    image-20220723123354692

  • 安装插件需要耐心等待,比较耗时,插件安装完后进入创建账号页面

    image-20220723123533534

    image-20220723124409245

    image-20220723124636999

  • 然后点击开始使用jenkins,重启jenkins,命令如下

    sudo docker restart myjenkins
    
  • 浏览器访问http://ip:8080ip替换成你服务器的ip,本地安装ip输入127.0.0.1localhost

    image-20220723125232496

安装需要的其他插件

  • 点菜单系统管理-->插件管理-->可选插件,依次搜索allurebuild user vars Build Timestamp插件安装

    image-20220723125937914

    image-20220723234127768

    image-20220723234340004

  • 安装完成后,重启jenkins

    sudo docker restart myjenkins
    

全局工具配置

  • 点击菜单系统管理-->全局工具配置

    image-20220723140728816

    image-20220723140953300

邮件配置

  • 下面以网易企业邮箱为例,网易企业邮箱默认支持SMTPSMTP协议介绍

  • 系统管理-->Manage Credentials-->添加凭证,邮箱凭据配置

    image-20220723142000350

    image-20220723143421270

  • jenkins邮箱基础配置,进入系统管理-->系统配置,做如下配置

    增加系统管理员邮件地址 格式:发件人昵称<邮件地址>

    image-20220723144052349

    邮件配置测试网易企业邮箱SMTP地址查询

    image-20220723145246726

    image-20220723145558316

    点击测试后提示成功,收件人邮箱会收到一封邮件,说明邮件发送没问题

    配置扩展邮箱

    该配置将会作为我们jenkins任务执行完成后的邮件模板

    找到Extended E-mail Notification区域,做如下配置,点击高级-->Credentials选择前面添加的全局邮件凭证

    image-20220723150313615

    image-20220723150135980

    image-20220723150516967

git凭据配置

  • 进入系统管理-->Manage Credentials,填写GitLab/GitHub/码云的用户名和密码

    image-20220723151118548

企业微信添加群机器人

  • 在企业微信群中添加机器人

    选择你要通知的企业微信群,按照下图,选择添加群机器人,然后按照向导程序一步一步地操作,在这期间只需要你给机器人起个名字。至此你已经成功的创建了群机器人,在群对话窗口右下方,你可以看到刚刚创建的机器人。

    image-20220723151118999

    右键查看机器人资料,你就可以看到该机器人的Webhook 地址,接下来流水线脚本会用到

Jenkins节点配置

  • 因为我们要执行的是接口自动化,并且我们希望在jenkins slave节点上执行,而不是在jenkins所在的master上执行,因此我们需要配置节点相关的内容

    上图中master是指jenkins所在的服务器,用来统筹管理各个任务及配置

    slave指的是各个自动化任务执行的机器,也叫作节点

    master通过管理节点,及任务中的节点配置将不同的任务分配到不同的设备上执行

  • 系统管理->全局安全配置,找到代理进行如下设置并保存

    image-20220723164529175

  • 系统管理->节点管理,点击左侧的新建节点

    image-20220723165007732

    image-20220723165243306

    image-20220723165423830

    节点创建后初始是未连接状态

  • 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这是在节点管理中未连接的节点,可以看到下面这一串,复制过来,下面将要使用

    image-20220723172218640

    使用下述命令启动节点,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首页新建任务,创建流水线任务

    image-20220723180343515

  • 流水线脚本生成

    jenkins pipeline脚本入门

    实际上我们不用手动去编写这样的脚本,可以利用jenkins提供的生成功能来做

    打开任务后,点击左侧的流水线语法

    image-20220723181408439

  • 生成节点脚本

    image-20220723181721687

  • 生成接口自动化项目代码拉取脚本

    image-20220723182244427

  • 生成allure报告处理脚本

    image-20220723182443242

  • 生成邮件发送脚本

  • 企业微信群消息发送脚本,直接用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: 'xxxxxxxxx@xxx.com.cn'
                }
            }
        }
        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>"      
                        }
                    }'
            """
    }
    
  • 首页选择流水线任务-->配置,输入上述脚本保存,执行构建

    image-20220723234744961

  • 构建效果如下

    image-20220724213428767

  • 企业微信群消息

  • 邮件报告内容

    image-20220724215324740

后记

  • 怎么获取测试用例执行结果

    使用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系统时间不正确解决方案

其它参考资料

基于docker部署实现接口自动化持续集成

Jenkins: 配置jenkins任务构建成功通知企业微信@相关人

Jenkins实践扩展企业微信消息通知

Jenkins发送邮件时,修改发件人名称

Jenkins自动发送邮件配置及定时构建

把pytest运行结果通过jenkins发送到邮件正文里

利用pytest hook函数实现python自动化测试结果推送企微

pytest的Hook函数详解

Jenkins pipeline中按行读文件

Groovy基础语法

posted @   极客兵兵  阅读(370)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示