devops系列四:windows下Jenkins-发布测试版本

前言

经过前面几篇Jenkins学习之后决定构建完整的集成及发布流程如下

包括pull、build、audit、deploy、test

要求:

1.所有节点可勾选配置,如可跳过checkout、build、test

2.使用pipeline的方式

过程

新建pipeline

devops系列四:windows下Jenkins-发布测试版本

创建模板pipeline

pipeline {
    agent any
    stages {
        stage('Pull') {
            steps {
                echo 'Pull'
            }
        }
        stage('Build') {
            steps {
                echo 'Build'
            }
        }
        stage('DeployTest') {
            steps {
                echo 'DeployTest'
            }
        }
        stage('Test') {
            steps {
                echo 'Test'
            }
        }
        stage('Audit') {
            steps {
                echo 'Audit'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploy'
            }
        }
        stage('DingDingMsg') {
            steps {
                echo 'DingDingMsg'
            }
        }
    }
}

开始生成对应的片段

Pull

jenkins 拉取代码

devops系列四:windows下Jenkins-发布测试版本
生成代码

checkout([$class: 'SubversionSCM', additionalCredentials: [], excludedCommitMessages: '', excludedRegions: '', excludedRevprop: '', excludedUsers: '', filterChangelog: false, ignoreDirPropChanges: false, includedRegions: '', locations: [[cancelProcessOnExternalsFail: true, credentialsId: 'xxx', depthOption: 'infinity', ignoreExternalsOption: true, local: '.', remote: 'http://xxxx:xx/svn/xxxx/dotnet.docker.test']], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])

Build

安装.net5sdk

安装sonar配置

dotnet tool install --global dotnet-sonarscanner

如果执行失败请参考https://blog.csdn.net/weixin_37900078/article/details/107241190

sonar开始

编译并发布

sonarqube

压缩文件使用7z(因为可以生成tar包)

bat '''
dotnet sonarscanner begin /k:"dotnet.docker.test" /d:sonar.host.url="http://xxxxx:9000" /d:sonar.login="17695caad8717873109bf045bc3815ee7c33bd4a"
dotnet build "%JENKINS_HOME%\\workspace\\core_pipeline\\dotnet.docker.test.sln"
dotnet publish -c release
dotnet sonarscanner end /d:sonar.login="17695caad8717873109bf045bc3815ee7c33bd4a"
7z a  publish.tar  %JENKINS_HOME%\\workspace\\${env.JOB_NAME}\\bin\\Release\\net5.0\\publish\\

'''

执行时总是命令不存在, 把jenkins服务输入用户名密码

参考 https://www.icode9.com/content-4-221436.html

Audit

安装Email Extension Template插件

设置邮件参数

devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本

amojfegwhttsjcef

devops系列四:windows下Jenkins-发布测试版本
POP3服务器(端口995);2)SMTP服务器(端口465或587)

devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本
发邮件这部分卡的时间挺长了,后来知道了调试模式,从日志中知道了错误才调通的,详细配置参考https://www.cnblogs.com/zz0412/p/jenkins_jj_01.html

发邮件通知添加验证码

参考:https://www.thinbug.com/q/51382438

environment {
        randomToken="${bat(returnStdout: true, script: 'echo 		%random%').readLines().last()}"
    }
emailext(
    subject:"${env.JOB_NAME}(#${env.BUILD_NUMBER})生产部署任务",
    body:"""
   		 随机验证码是:${env.randomToken}!!!
    	<br> <a href="${BUILD_URL}input">请点击该链接登录后审批填入token发布</a><br>
    	<h3>或者将token告诉其他人,让其输入token</h3>
    	""",
    to:"xxx@qq.com"
)                          

Deploy

Publish Over SSH

devops系列四:windows下Jenkins-发布测试版本
devops系列四:windows下Jenkins-发布测试版本

默认传输地址

devops系列四:windows下Jenkins-发布测试版本

devops系列四:windows下Jenkins-发布测试版本

测试一下

devops系列四:windows下Jenkins-发布测试版本

生成脚本

devops系列四:windows下Jenkins-发布测试版本

Transfers:说明

  Source files:源文件地址,地址的目录是相对于jenkins workspace的目录,如果只需要执行命令不需要传输文件的时候,此处可以为空 要输入相对路径最后结尾***

  Remove prefix:去除的文件地址(删除地址前缀)。在Source files输入框中填入的地址,会默认在服务器下创建相同的文件夹,所以需要将我们不需要的文件夹在这里剔除掉

  Remote directory:远程服务器接收文件的地址

  Exec command:文件传输任务执行完毕后,在远程服务器上执行的命令

如果遇见问题SSH: Transferred 0 file(s)

参考文章https://www.jianshu.com/p/ef6a4022b7b5?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

sshPublisher(publishers: [sshPublisherDesc(configName: 'aliyun', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'bin/Release/net5.0/publish/', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'bin/Release/net5.0/publish/publish.tar', usePty: true)], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])

编写执行脚本如果有原来镜像和容器就删除,构建新的镜像和容器

echo "获取当前容器是否存在-----------------------------------------------------------------"
containerps=$(docker ps -f name=aspnetcore_sample -q)
containerstop=$(docker ps -a -f name=aspnetcore_sample -q)
for alpha in "$containerps";do
	if [ "$alpha" == "" ];then
	echo "检查是否存在停止的容器-------------------------------------------"
		for alpha1 in "$containerstop";do
		  if [ "$alpha1" == "" ];then
		  echo "不存指定容器-----------------------------------"
		  else
		  echo "存在停止了的 然后直接删除-----------开始------------------"
		  docker rm $alpha1
		  echo "存在停止了的 然后直接删除-----------完成------------------"
		fi
	   done
	else
	echo "存在-停止运行 然后删除----------------------开始-----------------"
	docker stop $alpha
	docker rm $alpha
	 echo "存在-停止运行 然后删除---------------------完成------------------"
	fi
done

echo "获取当前镜像是否存在--------------------------------------------------"
dockerlist=$(docker images coretest -q)
for alpha2 in "$dockerlist";do
  if [ "$alpha2" == "" ];then
	 echo "不存在指定镜像-------------------------------------------------" 
  else
	   echo "存在当前指定的镜像 删除镜像--------------开始--------------------"
	  docker rmi $alpha2
	 echo "存在当前指定的镜像 删除镜像--------------完成----------------------"
  fi
done
#进入目录
cd  /usr/local/docker/core/
#解压
tar -xvf publish.tar -C /usr/local/docker/core/
#进入目录
cd ./publish
#构建镜像
docker build -t coretest .
#启动镜像
docker run -d -p 5001:80 -p 5000:443 --name aspnetcore_sample coretest
                

当jenkins再次执行的时候发现SSH上传并执行脚本此处记得生成脚本的时候勾选上

devops系列四:windows下Jenkins-发布测试版本

还有就是执行docker要以后台执行,不然jenkins会一直运行

Test

下载并安装jmeter环境变量

下载并安装ant环境变量

参考资料https://www.cnblogs.com/weihc/p/14138045.html

将 jmeter的extras目录中ant-jmeter-1.1.1.jar包拷贝至ant安装目录下的lib目录中,该包相当于是ant和jmeter连接的桥梁

修改Jmeter的bin目录下jmeter.properties文件的配置:jmeter.save.saveservice.output_format=xml
作用是使Jmeter报告输出文件格式为xml

在项目中新建目录

devops系列四:windows下Jenkins-发布测试版本

jmx:存放测试脚本
html,jtl文件夹:用于保存html的结果报告

下一步配置build.xml文件

build.xml:Ant根据该文件运行jmeter脚本(关键文档,需要配置正确)

从jmeter-5.1.1\extras路径下找到build.xm文件上传到项目中去build_bk.xml为备份文件

以下为我配置的build.xml文件

<?xml version="1.0"?>
<project name="ant-jmeter" default="all">
	<description>

		Sample build file for use with ant-jmeter.jar
		See http://www.programmerplanet.org/pages/projects/jmeter-ant-task.php

		To run a test and create the output report:
		ant -Dtest=script
		#执行名称为script的单个测试
		To run a test only:
		ant -Dtest=script run

		To run report on existing test output
		ant -Dtest=script report

		The "script" parameter is the name of the script without the .jmx suffix.

		Additional options:
		-Dshow-data=y - include response data in Failure Details
		-Dtestpath=xyz - path to test file(s) (default user.dir).
		N.B. Ant interprets relative paths against the build file
		# 外部传入压测脚本路径
		-Djmeter.home=.. - path to JMeter home directory (defaults to parent of this build file)
		#外部传入jenkins安装路径
		-Dreport.title="My Report" - title for html report (default is 'Load Test Results')
	</description>

	<property name="testpath" value="${user.dir}"/>
	<property name="jmeter.home" value="${basedir}/.."/>
	<property name="report.title" value="Load Test Results"/>

	<!-- Name of test (without .jmx) -->
	<property name="test" value="test"/>

	<!-- Should report include response data for failures? -->
	<property name="show-data" value="n"/>

	<property name="format" value="2.1"/>

	<condition property="style_version" value="_21">
		<equals arg1="${format}" arg2="2.1"/>
	</condition>

	<condition property="funcMode">
		<equals arg1="${show-data}" arg2="y"/>
	</condition>

	<condition property="funcMode" value="false">
		<not>
			<equals arg1="${show-data}" arg2="y"/>
		</not>
	</condition>

	<!-- Allow jar to be picked up locally -->
	<path id="jmeter.classpath">
		<fileset dir="${basedir}">
			<include name="ant-jmeter*.jar"/>
		</fileset>
	</path>

	<taskdef
        name="jmeter"
        classpathref="jmeter.classpath"
        classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"/>

	<target name="all" depends="run,report"/>

	<target name="run">
		<echo>funcMode = ${funcMode}</echo>
		<delete file="${testpath}/report/html/${test}.html"/>
		<jmeter
            jmeterhome="${jmeter.home}"
            resultlog="${testpath}/report/jtl/${test}.jtl">
			<testplans dir="${testpath}/jmx" includes="*.jmx"/>
			
            <jvmarg value="-Xincgc"/>
            <jvmarg value="-Xmx128m"/>
            <jvmarg value="-Dproperty=value"/>
            <jmeterarg value="-qextra.properties"/>
        
			<!-- Force suitable defaults -->
			<property name="jmeter.save.saveservice.output_format" value="xml"/>
			<property name="jmeter.save.saveservice.assertion_results" value="all"/>
			<property name="jmeter.save.saveservice.bytes" value="true"/>
			<property name="file_format.testlog" value="${format}"/>
			<property name="jmeter.save.saveservice.response_data.on_error" value="${funcMode}"/>
		</jmeter>
	</target>

	<property name="lib.dir" value="${jmeter.home}/lib"/>

	<!-- Use xalan copy from JMeter lib directory to ensure consistent processing with Java 1.4+ -->
	<path id="xslt.classpath">
		<fileset dir="${lib.dir}" includes="xalan*.jar"/>
		<fileset dir="${lib.dir}" includes="serializer*.jar"/>
	</path>

	<target name="report" depends="xslt-report,copy-images">
		<echo>Report generated at ${report.datestamp}</echo>
	</target>

	<target name="xslt-report" depends="_message_xalan">
		<tstamp>
			<format property="report.datestamp" pattern="yyyy/MM/dd HH:mm"/>
		</tstamp>
		<xslt
            classpathref="xslt.classpath"
            force="true"
            in="${testpath}/report/jtl/${test}.jtl"
            out="${testpath}/report/html/${test}.html"
            style="${jmeter.home}/extras/jmeter-results-detail-report${style_version}.xsl">
			<param name="showData" expression="${show-data}"/>
			<param name="titleReport" expression="${report.title}"/>
			<param name="dateReport" expression="${report.datestamp}"/>
		</xslt>
	</target>

	<!-- Copy report images if needed -->
	<target name="copy-images" depends="verify-images" unless="samepath">
		<copy file="${jmeter.home}/extras/expand.png" tofile="${testpath}/report/html/expand.png"/>
		<copy file="${jmeter.home}/extras/collapse.png" tofile="${testpath}/report/html/collapse.png"/>
	</target>

	<target name="verify-images">
		<condition property="samepath">
			<equals arg1="${testpath}" arg2="${basedir}" />
		</condition>
	</target>

	<!-- Check that the xalan libraries are present -->
	<condition property="xalan.present">
		<and>
			<!-- No need to check all jars; just check a few -->
			<available classpathref="xslt.classpath" classname="org.apache.xalan.processor.TransformerFactoryImpl"/>
			<available classpathref="xslt.classpath" classname="org.apache.xml.serializer.ExtendedContentHandler"/>
		</and>
	</condition>

	<target name="_message_xalan" unless="xalan.present">
		<echo>Cannot find all xalan and/or serialiser jars</echo>
		<echo>The XSLT formatting may not work correctly.</echo>
		<echo>Check you have xalan and serializer jars in ${lib.dir}</echo>
	</target>


</project>


以下为编写的脚本

bat '''
	cd %JENKINS_HOME%/workspace/core_pipeline/jmeter/
	ant run report -Dshow-data=y -Dtestpath= %JENKINS_HOME%/workspace/core_pipeline/jmeter/ -Djmeter.home= D:/Jenkins/software/apache-jmeter-5.1.1
    '''

配置jenkins中查看测试结果

安装插件 [HTML Publisher plugin]

devops系列四:windows下Jenkins-发布测试版本

生成脚本

devops系列四:windows下Jenkins-发布测试版本

publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'jmeter/report/html', reportFiles: 'test.html', reportName: 'HTML Report', reportTitles: ''])

期间遇到的问题

1.jenkins 执行不识别ant

需要配置全局变量 需要重启jenkins

devops系列四:windows下Jenkins-发布测试版本

2.html格式显示错误

参考 https://www.cnblogs.com/kai2008009/p/13831490.html

原因:Jenkins为了避免受到恶意HTML/JS文件的攻击,会默认将安全策略CSP设置为:sandbox; default-src 'none'; img-src 'self'; style-src 'self';

devops系列四:windows下Jenkins-发布测试版本

最终脚本

pipeline {
    agent any
    parameters {
      extendedChoice defaultValue: '1,2,4,5', description: '自定义构建', descriptionPropertyValue: 'Pull,Build,Audit,Deploy,Test', multiSelectDelimiter: ',', name: 'boxes', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: '1,2,3,4,5', visibleItemCount: 5
    }
    environment {
        randomToken="${bat(returnStdout: true, script: 'echo %random%').readLines().last()}"
        skipProd = true
    }
    stages {
        stage('Pull') {
            
            when { expression {(params.boxes =~ '.*1.*').matches() }}
            steps {
                
				echo 'Pull Begin'
				 
                checkout([$class: 'SubversionSCM', additionalCredentials: [], excludedCommitMessages: '', excludedRegions: '', excludedRevprop: '', excludedUsers: '', filterChangelog: false, ignoreDirPropChanges: false, includedRegions: '', locations: [[cancelProcessOnExternalsFail: true, credentialsId: 'xx', depthOption: 'infinity', ignoreExternalsOption: true, local: '.', remote: 'http://xxxx6/svn/xxxx/dotnet.docker.test']], quietOperation: true, workspaceUpdater: [$class: 'UpdateUpdater']])
				echo 'Pull End'
            }
            
        }
        stage('Build') {
            
            when { expression {(params.boxes =~ '.*2.*').matches() }}
            steps {
                echo 'Build Begin'
                bat """
                        dotnet sonarscanner begin /k:"dotnet.docker.test" /d:sonar.host.url="http://1xxxx" /d:sonar.login="xxx"
                        dotnet build "%JENKINS_HOME%\\workspace\\${env.JOB_NAME}\\dotnet.docker.test.sln"
                        dotnet publish -c release
                        dotnet sonarscanner end /d:sonar.login="xxxx"
                        7z a  publish.tar  %JENKINS_HOME%\\workspace\\${env.JOB_NAME}\\bin\\Release\\net5.0\\publish\\
                    """
                echo 'Build End'        
            }
        }
        
        stage('Audit') {
            when { expression {(params.boxes =~ '.*3.*').matches() }}
            steps {
                echo 'Hello World'
                script{
                      emailext(
                            subject:"【请审批】${env.JOB_NAME}(#${env.BUILD_NUMBER})生产部署任务",
                            body:""" 随机验证码是:${env.randomToken}!!!<br> <a href="${BUILD_URL}input">请点击该链接登录后审批填入token发布</a><br><h3>或者将token值告诉人,让其输入token</h3>""",
                            to:"xxx.com"
                        )
                }
               
                    
            }
        }
        stage("请输入token"){
            steps{
                script{
                   timeout(5){  //等待审批人审批,并通过timeout设置任务过期时间,防止任务永远挂起
                        try {
                           def userInput = input(
                               id: 'inputap', message: "申请", ok:"确认token", parameters: [
                               [$class: 'StringParameterDefinition',defaultValue: "", name: 'token',description: '请输入秘钥' ]
                               ])
                           if (userInput == env.randomToken) {
                               env.skipProd = true
                           } else {
                               env.skipProd = false
                               echo "\033[31m 秘钥错误  \033[0m"
                           }
                            echo "\033[31m 当前输入秘钥为: ${userInput}  \033[0m"
                       }catch(err) { // input false
                           echo "******主动拒绝*****"
                           env.skipProd = false
                       }
                       echo "${env.skipProd}"
                   }
                }
            }
        }
        stage('Deploy') {
            when { expression {(params.boxes =~ '.*4.*').matches()&&env.skipProd }}
            steps {
               sshPublisher(publishers: [sshPublisherDesc(configName: 'aliyun', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''
                echo "获取当前容器是否存在-----------------------------------------------------------------"
                containerps=$(docker ps -f name=aspnetcore_sample -q)
                containerstop=$(docker ps -a -f name=aspnetcore_sample -q)
                for alpha in "$containerps";do
                    if [ "$alpha" == "" ];then
                    echo "检查是否存在停止的容器-------------------------------------------------"
                        for alpha1 in "$containerstop";do
                          if [ "$alpha1" == "" ];then
                          echo "不存指定容器-----------------------------------"
                          else
                          echo "存在停止了的 然后直接删除-----------开始------------------"
                          docker rm $alpha1
                          echo "存在停止了的 然后直接删除-----------完成------------------"
                        fi
                       done
                    else
                    echo "存在-停止运行 然后删除----------------------开始-----------------"
                    docker stop $alpha
                    docker rm $alpha
                     echo "存在-停止运行 然后删除---------------------完成------------------"
                    fi
                done

                echo "获取当前镜像是否存在-----------------------------------------------------------------"
                dockerlist=$(docker images coretest -q)
                for alpha2 in "$dockerlist";do
                  if [ "$alpha2" == "" ];then
                     echo "不存在指定镜像-------------------------------------------------" 
                  else
                       echo "存在当前指定的镜像 删除镜像--------------开始-----------------------------------"
                      docker rmi $alpha2
                     echo "存在当前指定的镜像 删除镜像--------------完成-----------------------------------"
                  fi
                done
                cd  /usr/local/docker/core/
                tar -xvf publish.tar -C /usr/local/docker/core/
                cd ./publish
                docker build -t coretest .
                docker run -d -p 5001:80 -p 5000:443 --name aspnetcore_sample coretest
                ''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'publish.tar', usePty: true)], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
            }
        }
        stage('Test') {
            when { expression {(params.boxes =~ '.*5.*').matches()&&env.skipProd }}
            steps {
                echo "test Begin ${env.BUILD_NUMBER}"
                bat """
                    cd %JENKINS_HOME%/workspace/${env.JOB_NAME}/jmeter/
                    ant run report -Dshow-data=y -Dtest=${env.BUILD_NUMBER}  -Dtestpath= %JENKINS_HOME%/workspace/${env.JOB_NAME}/jmeter/ -Djmeter.home= D:/Jenkins/software/apache-jmeter-5.1.1
                    """
                publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'jmeter/report/html/', reportFiles: "${env.BUILD_NUMBER}.html", reportName: 'HTML Report', reportTitles: ''])                
                echo 'test End'
            }
        }
        
    }
}

备注:

路径上面的工程名称读取系统变量,所有涉及到使用流水线名称的如当前的core_pipeline都换成 ${env.JOB_NAME},注意使用的时候不要用单引号,要用双引号这样才能读取值出来

引入jenkinsfile

devops系列四:windows下Jenkins-发布测试版本

定义流水线

devops系列四:windows下Jenkins-发布测试版本

第一次会失败,刷新页面第二次才会出现参数

上一篇:DevOps入门心得笔记--未完


下一篇:使用Azure Devops(TFS)编写代码:挂起工作,修正bug以及进行代码审查