前言
经过前面几篇Jenkins学习之后决定构建完整的集成及发布流程如下
包括pull、build、audit、deploy、test
要求:
1.所有节点可勾选配置,如可跳过checkout、build、test
2.使用pipeline的方式
过程
新建pipeline
创建模板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 拉取代码
生成代码
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插件
设置邮件参数
amojfegwhttsjcef
POP3服务器(端口995);2)SMTP服务器(端口465或587)
发邮件这部分卡的时间挺长了,后来知道了调试模式,从日志中知道了错误才调通的,详细配置参考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
默认传输地址
测试一下
生成脚本
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上传并执行脚本此处记得生成脚本的时候勾选上
还有就是执行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
在项目中新建目录
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]
生成脚本
publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'jmeter/report/html', reportFiles: 'test.html', reportName: 'HTML Report', reportTitles: ''])
期间遇到的问题
1.jenkins 执行不识别ant
需要配置全局变量 需要重启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';
最终脚本
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
定义流水线
第一次会失败,刷新页面第二次才会出现参数