Jenkins+GitLab+Docker+SpringCloud+Kubernetes实现可持续自动化微服务
<p> 现有混合云平台的场景下,即有线下和线上的环境,又有测试与正式的场景,而且结合了Docker,导致打包内容有所区分,且服务的发布流程复杂起来,手工打包需要在编译阶段就要根据环境到处更改配置,因此纯手工发布增加了实施的难度,需要一个统一的适应各种环境部署的方案。</p><p><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709212427348-2092028391.png" alt=""></p>
<h2>基于微服务的发布流程</h2>
<p> 手动/自动构建 -> Jenkins 调度 K8S API ->动态生成 Jenkins Slave pod -> Slave pod 拉取 Git 代码/编译/打包镜像 ->推送到镜像仓库 Harbor -> Slave工作完成,Pod 自动销毁 ->部署到测试或生产 Kubernetes(K8S)平台。</p>
<p> 上面是理想状况下的将服务编译打包成镜像上传到镜像库后部署到Kubernetes平台的一个流程,但问题是:</p>
<ol>
<li>我们有线上线下平台,代码在线下GitLab,是出不了外网的,因此线上K8S集群无法拉取代码编译。</li>
<li>Jenkins的master所在服务器是CentOS6.5,没有Docker环境,也没有在K8S集群服务器内,因此无法直接执行docker build镜像和 kubectl apply 发布服务到K8S集群。</li>
<li>Jenkins的slave节点都是无法访问外网的,</li>
<li>线上服务需要Pinpoint而线下环境暂时不需要启用Pinpoint,否则一直报错,因此需要根据选择的环境动态的构建Dockerfile,而且要求整个发布流程可选择。</li>
</ol>
<p>就上面现实问题,我们将发布流程简化:</p>
<p><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709212549582-1124943104.png" alt="" width="787" height="290"></p>
<p><strong>关键点:</strong></p>
<p>Docker镜像的打包使用com.spotify的docker-maven-plugin插件结合Dockerfile,调用远程服务器的Docker环境生成镜像。</p>
<p>K8S服务部署采用的是ssh方式,将Deployment文件上传到K8S集群服务器,然后执行部署命令。</p>
<h3>如何利用Dockerfile打包镜像</h3>
<p> 之前也是用com.spotify的docker-maven-plugin插件来打包镜像并推送到私有镜像仓库,但问题是无法根据环境写条件判断,如动态选择是否需要启动pinpoint,线上线下库地址动态更换,导致镜像名前缀也是要动态变化的,此时直接配置无法满足,需要结合Dockerfile来实现。</p>
<p>先更改pom文件,指定本项目的Dockerfile文件地址,默认是放在项目根目录下:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">plugin</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">groupId</span><span style="color: rgba(0, 0, 255, 1)">></span>com.spotify<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">groupId</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">artifactId</span><span style="color: rgba(0, 0, 255, 1)">></span>docker-maven-plugin<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">artifactId</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">version</span><span style="color: rgba(0, 0, 255, 1)">></span>1.2.0<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">version</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">configuration</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">覆盖相同标签镜像</span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">forceTags</span><span style="color: rgba(0, 0, 255, 1)">></span>true<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">forceTags</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 与maven配置文件settings.xml一致 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">serverId</span><span style="color: rgba(0, 0, 255, 1)">></span>nexus-releases<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">serverId</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">私有仓库地址 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">registryUrl</span><span style="color: rgba(0, 0, 255, 1)">></span>https://${docker.repostory}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">registryUrl</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">远程Docker地址 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">dockerHost</span><span style="color: rgba(0, 0, 255, 1)">></span>http://10.3.87.210:2375<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">dockerHost</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 注意imageName一定要是符合正则的,否则构建不会成功 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">指定镜像名称 仓库/镜像名:标签</span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">imageName</span><span style="color: rgba(0, 0, 255, 1)">></span>${docker.repostory}/${project.artifactId}:${project.version}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">imageName</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">dockerDirectory</span><span style="color: rgba(0, 0, 255, 1)">></span>${project.basedir}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">dockerDirectory</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">resources</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">resource</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 指定要复制的目录路径,这里是当前目录 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 将打包文件放入dockerDirectory指定的位置 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">targetPath</span><span style="color: rgba(0, 0, 255, 1)">></span>/app/<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">targetPath</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 指定要复制的根目录,这里是target目录 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">directory</span><span style="color: rgba(0, 0, 255, 1)">></span>${project.build.directory}<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">directory</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)"> 指定需要拷贝的文件,这里指最后生成的jar包 </span><span style="color: rgba(0, 128, 0, 1)">--></span>
<span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">include</span><span style="color: rgba(0, 0, 255, 1)">></span>${project.build.finalName}.jar<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">include</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">resource</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">resources</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">configuration</span><span style="color: rgba(0, 0, 255, 1)">></span>
<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">plugin</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<p><registryUrl>https://${docker.repostory}</registryUrl></p>
<p align="left">指定远程仓库地址,在主项目的<properties>中指定,这里默认线上仓库<docker.repostory>39.95.40.97:5000</docker.repostory></p>
<p><dockerHost>http://10.3.87.210:2375</dockerHost></p>
<p>指定Docker镜像打包服务器,这里指定线下服务器。</p>
<p><imageName>${docker.repostory}/${project.artifactId}:${project.version}</imageName></p>
<p>指定镜像名称 仓库/镜像名:标签</p>
<p><dockerDirectory>${project.basedir}</dockerDirectory></p>
<p>指定Dockerfile文件地址,此处指定项目根目录</p>
<p> </p>
<p><strong>Dockerfile</strong><strong>内容</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">FROM join:0.2
MAINTAINER {description} Join
ADD /app/{artifactId}-{version}.jar /app/
ENTRYPOINT ["java", "-Xmx512m","-Dspring.profiles.active={active}",{jarparam} "-jar", "/app/{artifactId}-{version}.jar"]</span></pre>
</div>
<p> 基础镜像用join:0.2,里面包含了pinpoint和监控jvm的promethus客户端包。</p>
<p> Jarparam会在Jenkins中动态替换运行时参数,active 指定当前运行环境,这里可能有人提议根据项目yml文件中指定内容自动匹配,因为要考虑到如果自动匹配 更换线上线下环境就需要更改yml配置文件后又要上传到gitlab,如此没有必要多做一步,直接在Jenkins中当作参数指定最为便捷。</p>
<p> 此处Dockerfile是通用模板,如果有特殊内容添加,可自行更改,此时的模板需要在Jenkins运行时替换参数后才有用,如果想直接在本机运行打包,可手动替换参数内容后运行:</p>
<pre>clean package -DskipTests docker:build</pre>
<p>推送</p>
<pre>clean package -DskipTests docker:build -DpushImage</pre>
<h2>Jenkins发布流程</h2>
<h3>利用Jenkins的pipeline构建流水线</h3>
<p> Pipeline也就是构建流水线,对于程序员来说,最好的解释是:使用代码来控制项目的构建、测试、部署等。使用它的好处有很多,包括但不限于:</p>
<p style="margin-left: 30px">l 使用Pipeline可以非常灵活的控制整个构建过程;</p>
<p style="margin-left: 30px">l 可以清楚的知道每个构建阶段使用的时间,方便构建的优化;</p>
<p style="margin-left: 30px">l 构建出错,使用stageView可以快速定位出错的阶段;</p>
<p style="margin-left: 30px">l 一个job可以搞定整个构建,方便管理和维护等。</p>
<p> </p>
<p> Pipeline 支持两种语法,声明式和脚本式。这两种方法都支持构建持续交付流水线,都可以通过 web UI 或 Jenkinsfile 文件来定义 Pipeline(通常认为创建 Jenkinsfile 文件并上传到源代码控制仓库是最佳实践)</p>
<p align="left">Jenkinsfile 就是一个包含对 Jenkins Pipeline 定义的文本文件,会上传到版本控制中。下面的 Pipeline 实现了基本的 3 段持续交付流水线。</p>
<p> </p>
<p>声明式 Pipeline:</p>
<div>
<div class="cnblogs_code">
<pre>//<span style="color: rgba(0, 0, 0, 1)"> Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Build</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
steps {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Building..</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Test</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
steps {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Testing..</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Deploy</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
steps {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Deploying....</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
}
}
}</span></pre>
</div>
<p> </p>
</div>
<p>对应的脚本式 Pipeline:</p>
<div>
<div class="cnblogs_code">
<pre>//<span style="color: rgba(0, 0, 0, 1)"> Jenkinsfile (Scripted Pipeline)
node {
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Build</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Building....</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Test</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Building....</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Deploy</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Deploying....</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p> </p>
</div>
<p> 注意,所有的 Pipeline 都会有这三个相同的 stage,可以在所有项目的一开始就定义好它们。下面演示在 Jenkins 的测试安装中创建和执行一个简单的 Pipeline。</p>
<p> 假设项目已经设置好了源代码控制仓库,并且已经按照入门章节的描述在 Jenkins 中定义好了 Pipeline。</p>
<p> 使用文本编辑器(最好支持 Groovy 语法高亮显示),在项目根目录中创建 Jenkinsfile。</p>
<p> 上面的声明式 Pipeline 示例包含了实现一个持续交付流水线所需的最少步骤。必选指令 agent 指示 Jenkins 为 Pipeline 分配执行程序和工作空间。没有 agent 指令的话,声明式 Pipeline 无效,无法做任何工作!默认情况下 agent 指令会确保源代码仓库已经检出,并且可用于后续步骤。</p>
<p> stage 和 step 指令在声明式 Pipeline 中也是必须的,用于指示 Jenkins 执行什么及在哪个 stage 中执行。</p>
<p> 对于脚本式 Pipeline 的更高级用法,上面的示例节点是至关重要的第一步,因为它为 Pipeline 分配了一个执行程序和工作空间。如果没有 node,Pipeline 不能做任何工作!在 node 内,业务的第一阶段是检出此项目的源代码。由于 Jenkinsfile 是直接从源代码控制中提取的,因此 Pipeline 提供了一种快速简单的方法来访问源代码的正确版本:</p>
<p style="margin-left: 30px">// Jenkinsfile (Scripted Pipeline)</p>
<p style="margin-left: 30px">node {</p>
<p style="margin-left: 30px"> checkout scm</p>
<p style="margin-left: 30px"> /* .. snip .. */</p>
<p style="margin-left: 30px">}</p>
<p>这个 checkout 步骤会从源代码控制中检查代码,scm 是特殊变量,它指示运行检出步骤,复制触发了这次 Pipeline 运行的指定版本。</p>
<p>最终的流程样式:</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709212924895-186715824.png" alt=""></p>
<p> </p>
<p> 一般用声明式来构建流水,实际操作过程中还是发现脚本式构建更顺手,而且Groovy语言更方便查资料,因此下面以脚本构建为主演示一个流程。</p>
<p>1.新建任务</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709212939081-1790885828.png" alt=""></p>
<p> </p>
<p>2.填写任务名和描述,由于防止构建历史太多,只保留3个。</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709212959150-518405205.png" alt=""></p>
<p> </p>
<p>3.添加构建时全局构建参数,用来构建流程动态选择环境,这里有两种方式,一种是直接在页面上添加,如下图,一种是在Jenkinsfile中添加(第一次构建时不会出现选项,第二次构建才会出现,因此首次构建需要试构建,暂停再刷新页面才会有选择框),两种最张效果一样,这里为了方便采用Jenkinsfile来添加全局参数。</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709213027903-74605517.png" alt=""></p>
<p> </p>
<p>Jenkinsfile中添加</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">properties([
parameters(, description: '程序打包环境'),choice(name: 'ENV_TYPE', choices: ['online', 'offline'], description: '线上、还是线下环境'),booleanParam(name: 'ON_PINPOINT', defaultValue: true, description: '是否添加Pinpoint监控'),booleanParam(name: 'ON_PROMETHEUS', defaultValue: true, description: '是否添加Prometheus监控'),string(name: 'EMAIL', defaultValue: '104@qq.com', description: '打包结果通知')])
])</span></pre>
</div>
<p><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709213107750-1867354625.png" alt=""></p>
<p> </p>
<p>4.选择源码代码库:</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709213127635-1228974532.png" alt=""></p>
<p> </p>
<p>需要添加认证,将Jenkins的ssh秘钥添加到GitLab的页面中,且需要将此处gitlab中joint用户添加到需要拉取代码的项目中才有权限拉取代码。</p>
<p>Jenkinsfile位置放在项目的根目录。</p>
<p>5. Jenkinsfile中指定maven目录地址</p>
<p align="left">MVNHOME = '/opt/maven354'</p>
<p>为防止手工填写项目名和版本号等一系列信息,因此直接读取pom文件中要编译项目的这些信息给全局变量:</p>
<div class="cnblogs_code">
<pre>pom = readMavenPom file: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pom.xml</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
artifactId </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.artifactId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
version </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.version}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
description </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p> </p>
<p>根据选择的线上环境还是线下环境,替换镜像仓库ip</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">if</span> (params.ENV_TYPE == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">offline</span><span style="color: rgba(128, 0, 0, 1)">'</span> || params.ENV_TYPE ==<span style="color: rgba(0, 0, 0, 1)"> null) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#39.95.40.97:7806#10.3.87.51:8080#g' pom.xml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
image </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">10.3.87.51:8080/${artifactId}:${version}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p> </p>
<p> </p>
<p>6.编译</p>
<p>利用maven构建,利用上面的内容先替换掉Dockerfile、Deployment中的变量,再根据选择的条件是否启用pinpoint和promethus,最后编译。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">def</span> jarparam=<span style="color: rgba(128, 0, 0, 1)">''</span>
<span style="color: rgba(0, 0, 255, 1)">def</span> pinname =<span style="color: rgba(0, 0, 0, 1)"> artifactId
</span><span style="color: rgba(0, 0, 255, 1)">if</span>( pinname.length() > 23<span style="color: rgba(0, 0, 0, 1)">) {
pinname </span>= artifactId.substring(0,23<span style="color: rgba(0, 0, 0, 1)">)
}
</span>//<span style="color: rgba(0, 0, 0, 1)">添加pinpoint
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(params.ON_PINPOINT) {
jarparam </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
</span>//<span style="color: rgba(0, 0, 0, 1)">添加prometheus
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(params.ON_PROMETHEUS) {
jarparam </span>= jarparam + <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{jarparam}#${jarparam}#g' Dockerfile</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">'${MVNHOME}/bin/mvn' -DskipTests clean package</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p> </p>
<p>需要注意的是pinpoint的pinpoint.applicationName不能操作24个字符,否则启用不成功,因此超过的直接截断。</p>
<p>Department文件详情看后文。</p>
<p>跳过测试编译打包 '${MVNHOME}/bin/mvn' -DskipTests clean package 需要在Jenkins服务器安装maven环境,还有指定maven的jar包私有仓库地址。</p>
<p>7. Docker打包</p>
<p>前提是上一步指定pom文件中的镜像仓库和Dockerfile中的内容是替换后的完整内容。</p>
<p align="left">sh "'${MVNHOME}/bin/mvn' docker:build"</p>
<p>8. 推送镜像</p>
<p align="left">sh "'${MVNHOME}/bin/mvn' docker:push"</p>
<h2>如何发布服务到K8S集群</h2>
<p> 前面几步已经将项目打包并生成了镜像并推送到了私有仓库,下面就是部署服务到K8S集群。</p>
<p>先看看Department.yaml文件:</p>
<div class="cnblogs_code">
<pre>---<span style="color: rgba(0, 0, 0, 1)">
apiVersion: apps</span>/<span style="color: rgba(0, 0, 0, 1)">v1
kind: Deployment
metadata:
name: {artifactId}
namespace: default
labels:
app: {artifactId}
version: {version}
spec:
selector:
matchLabels:
app: {artifactId}
replicas: </span>1<span style="color: rgba(0, 0, 0, 1)">
template:
metadata:
labels:
app: {artifactId}
annotations:
prometheus.io.jmx: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">true</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
prometheus.io.jmx.port: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1234</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
spec:
containers:
</span>-<span style="color: rgba(0, 0, 0, 1)"> name: {artifactId}
image: {image}
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> IfNotPresent\Always</span>
<span style="color: rgba(0, 0, 0, 1)"> imagePullPolicy: Always
ports:
</span>-<span style="color: rgba(0, 0, 0, 1)"> name: prometheusjmx
containerPort: </span>1234<span style="color: rgba(0, 0, 0, 1)">
livenessProbe: </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">kubernetes认为该pod是存活的,不存活则需要重启</span>
<span style="color: rgba(0, 0, 0, 1)"> httpGet:
path: </span>/<span style="color: rgba(0, 0, 0, 1)">health
port: {port}
scheme: HTTP
initialDelaySeconds: </span>60 <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"># 设置为系统完全启动起来所需的最大时间+若干秒</span>
timeoutSeconds: 5<span style="color: rgba(0, 0, 0, 1)">
successThreshold: </span>1<span style="color: rgba(0, 0, 0, 1)">
failureThreshold: </span>5<span style="color: rgba(0, 0, 0, 1)">
readinessProbe: </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">kubernetes认为该pod是启动成功的 </span>
<span style="color: rgba(0, 0, 0, 1)"> httpGet:
path: </span>/<span style="color: rgba(0, 0, 0, 1)">health
port: {port}
scheme: HTTP
initialDelaySeconds: </span>40 <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"># 设置为系统完全启动起来所需的最少时间</span>
timeoutSeconds: 5<span style="color: rgba(0, 0, 0, 1)">
successThreshold: </span>1<span style="color: rgba(0, 0, 0, 1)">
failureThreshold: </span>5<span style="color: rgba(0, 0, 0, 1)">
env:
</span>- name: eureka-<span style="color: rgba(0, 0, 0, 1)">server
value: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">eureka-server.default.svc.cluster.local</span><span style="color: rgba(128, 0, 0, 1)">"</span>
- name: eureka-server-<span style="color: rgba(0, 0, 0, 1)">replica
value: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">eureka-server-replica.default.svc.cluster.local</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
resources:
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 5%的CPU时间和700MiB的内存</span>
<span style="color: rgba(0, 0, 0, 1)"> requests:
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> cpu: 50m</span>
<span style="color: rgba(0, 0, 0, 1)"> memory: 700Mi
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 最多允许它使用</span>
<span style="color: rgba(0, 0, 0, 1)"> limits:
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> cpu: 100m</span>
<span style="color: rgba(0, 0, 0, 1)"> memory: 1000Mi
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 指定在容器中挂载路径</span>
<span style="color: rgba(0, 0, 0, 1)"> volumeMounts:
</span>- name: logs-<span style="color: rgba(0, 0, 0, 1)">volume
mountPath: </span>/<span style="color: rgba(0, 0, 0, 1)">logs
</span>- name: host-<span style="color: rgba(0, 0, 0, 1)">time
mountPath: </span>/etc/<span style="color: rgba(0, 0, 0, 1)">localtime
readOnly: true
</span>- name: host-<span style="color: rgba(0, 0, 0, 1)">timezone
mountPath: </span>/etc/<span style="color: rgba(0, 0, 0, 1)">timezone
readOnly: true
</span>- name: pinpoint-<span style="color: rgba(0, 0, 0, 1)">config
mountPath: </span>/app/pinpoint-agent/<span style="color: rgba(0, 0, 0, 1)">pinpoint.config
volumes:
</span>- name: logs-<span style="color: rgba(0, 0, 0, 1)">volume
hostPath:
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 宿主机上的目录</span>
path: /<span style="color: rgba(0, 0, 0, 1)">logs
</span>- name: host-<span style="color: rgba(0, 0, 0, 1)">time
hostPath:
path: </span>/etc/<span style="color: rgba(0, 0, 0, 1)">localtime
</span>- name: host-<span style="color: rgba(0, 0, 0, 1)">timezone
hostPath:
path: </span>/usr/share/zoneinfo/Asia/<span style="color: rgba(0, 0, 0, 1)">Shanghai
</span>- name: pinpoint-<span style="color: rgba(0, 0, 0, 1)">config
configMap:
name: pinpoint</span>-<span style="color: rgba(0, 0, 0, 1)">config
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 运行在指定标签的节点,前提是先给节点打标kubectl label nodes 192.168.0.113 edgenode=flow</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)"> nodeSelector:</span><span style="color: rgba(0, 128, 0, 1)">
#</span><span style="color: rgba(0, 128, 0, 1)"> edgenode: flow</span>
---<span style="color: rgba(0, 0, 0, 1)">
apiVersion: v1
kind: Service
metadata:
name: {artifactId}
namespace: default
labels:
app: {artifactId}
version: {version}
spec:
selector:
app: {artifactId}
ports:
</span>- name: tcp-{port}-<span style="color: rgba(0, 0, 0, 1)">{port}
protocol: TCP
port: {port}
targetPort: {port}</span></pre>
</div>
<p> </p>
<p>里面的变量会在前面几步自动替换掉。</p>
<p>添加了prometheus收集jvm的内容:</p>
<p>prometheus.io.jmx: "true"<br>
prometheus.io.jmx.port: "1234"</p>
<p>containerPort: 1234</p>
<p>将pinpoint的配置内容pinpoint.config用configMap 保存,方便更改内容。</p>
<p>其它内容不在此详解,可自行google。</p>
<p> </p>
<p> 网上资料一般发布服务都是直接kubectl
deploy,这种情况只适用于jenkins的服务器已包含在K8S服务器集群中。第二种情况是在K8S集群服务器里面生成Jenkins的一个slave节点,然后在pipeline里面设置node(“k8s”){ ……} 里面发布,具体方法自行google。</p>
<p> 这里为了避免麻烦,采用直接SSH到K8S服务器集群的方案发布服务。</p>
<h4>配置sshagent</h4>
<p align="left">SSH Agent Plugin :sshagent方法支持,用于上传构建产物到目标服务器,使用详情见:</p>
<p>https://wiki.jenkins.io/display/JENKINS/SSH+Agent+Plugin</p>
<p>在Jenkins插件库搜索后直接下载安装(需要连外网环境),生产环境已安装,直接使用。</p>
<p>使用:</p>
<div class="cnblogs_code">
<pre>sshagent(credentials: [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">deploy_ssh_key_23</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">scp -P 2222 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ssh -p 2222 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p> </p>
<p>先用ssh远程到K8S集群中的服务器,将Deployment文件上传,然后再远程执行kubectl apply发布服务。</p>
<p> 为了避免误操作,在发布前做了发布确认提示判断。</p>
<p align="left">timeout(time: 10, unit: 'MINUTES') {<br>
input '确认要部署吗?'<br>
}</p>
<p> 以上流程已完成整个流程,然后可以去K8S环境去看服务是否有正常运行。</p>
<h3>关于测试</h3>
<p> 上面的过程没有加入代码测试、代码质量分析SonarQube、发布后服务测试的阶段(Selenium是一套完整的Web应用程序测试系统http://www.51testing.com/zhuanti/selenium.html),后续可以接入。</p>
<p> </p>
<h3>如何进行多模块如何构建</h3>
<p> 很多项目采用的是多模块构成,因此每个项目配置和发布要求不一样,需要单独编译到部署,所以每个模块都需要独立的Dockerfile和Deployment文件,Jenkinsfile通用一份,然后在发布时自动弹出模块列表,选择需要发布的模块进行编译发布。</p>
<div class="cnblogs_code">
<pre>//<span style="color: rgba(0, 0, 0, 1)">需要处理的项目多项目时先进入子项目
projectwk </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
mainpom </span>= readMavenPom file: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pom.xml</span><span style="color: rgba(128, 0, 0, 1)">'</span>
//<span style="color: rgba(0, 0, 0, 1)">存在多个模块时,选择其中一个进行编译
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(mainpom.modules.size() ><span style="color: rgba(0, 0, 0, 1)"> 0 ) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">项目拥有模块==${mainpom.modules}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
timeout(time: </span>10, unit: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">MINUTES</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">def</span> selproj = input message: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">请选择需要处理的项目</span><span style="color: rgba(128, 0, 0, 1)">'</span>, parameters: //, submitterParameter: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">project</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
projectwk </span>=<span style="color: rgba(0, 0, 0, 1)"> selproj
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">选择项目=${projectwk}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p> 读取主项目的pom中的modules判断是否包含多个模块,供用户选择。</p>
<p>然后根据选择的模块进行编译,dir进入选择的模块读取信息并编译。</p>
<div class="cnblogs_code">
<pre>dir(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${projectwk}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">) {
pom </span>= readMavenPom file: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pom.xml</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
artifactId </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.artifactId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
version </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.version}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
description </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p><strong>完整的Jenkinsfile</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">properties([
parameters(, description: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">程序打包环境</span><span style="color: rgba(128, 0, 0, 1)">'</span>),choice(name: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ENV_TYPE</span><span style="color: rgba(128, 0, 0, 1)">'</span>, choices: [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">online</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">offline</span><span style="color: rgba(128, 0, 0, 1)">'</span>], description: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">线上、还是线下环境</span><span style="color: rgba(128, 0, 0, 1)">'</span>),booleanParam(name: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ON_PINPOINT</span><span style="color: rgba(128, 0, 0, 1)">'</span>, defaultValue: true, description: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">是否添加Pinpoint监控</span><span style="color: rgba(128, 0, 0, 1)">'</span>),booleanParam(name: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ON_PROMETHEUS</span><span style="color: rgba(128, 0, 0, 1)">'</span>, defaultValue: true, description: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">是否添加Prometheus监控</span><span style="color: rgba(128, 0, 0, 1)">'</span>),string(name: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">EMAIL</span><span style="color: rgba(128, 0, 0, 1)">'</span>, defaultValue: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1041126478@qq.com</span><span style="color: rgba(128, 0, 0, 1)">'</span>, description: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">打包结果通知</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
])
node {
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Prepare</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1.Prepare Stage</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
MVNHOME </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/opt/maven354</span><span style="color: rgba(128, 0, 0, 1)">'</span>
//echo <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">UUID=${UUID.randomUUID().toString()}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
checkout scm
</span>//<span style="color: rgba(0, 0, 0, 1)">需要处理的项目多项目时先进入子项目
projectwk </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
mainpom </span>= readMavenPom file: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pom.xml</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
repostory </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${mainpom.properties['docker.repostory']}</span><span style="color: rgba(128, 0, 0, 1)">"</span>
//<span style="color: rgba(0, 0, 0, 1)">存在多个模块时,选择其中一个进行编译
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(mainpom.modules.size() ><span style="color: rgba(0, 0, 0, 1)"> 0 ) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">项目拥有模块==${mainpom.modules}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
timeout(time: </span>10, unit: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">MINUTES</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">def</span> selproj = input message: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">请选择需要处理的项目</span><span style="color: rgba(128, 0, 0, 1)">'</span>, parameters: //, submitterParameter: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">project</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
projectwk </span>=<span style="color: rgba(0, 0, 0, 1)"> selproj
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">选择项目=${projectwk}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}
dir(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${projectwk}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">) {
pom </span>= readMavenPom file: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pom.xml</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
artifactId </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.artifactId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
version </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.version}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
description </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${pom.description}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
script {
GIT_TAG </span>= sh(returnStdout: true, script: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/usr/local/git/bin/git rev-parse --short HEAD</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">).trim()
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">GIT_TAG== ${GIT_TAG}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
image </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">192.168.4.2:5000/${artifactId}:${version}</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (params.ENV_TYPE == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">offline</span><span style="color: rgba(128, 0, 0, 1)">'</span> || params.ENV_TYPE ==<span style="color: rgba(0, 0, 0, 1)"> null) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#39.95.40.97:5000#10.3.80.50:5000#g' pom.xml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
image </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">10.3.80.50:5000/${artifactId}:${version}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(mainpom.modules.size() ><span style="color: rgba(0, 0, 0, 1)"> 0 ) {
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">编译总项目</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">'${MVNHOME}/bin/mvn' -DskipTests clean install</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}
dir(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${projectwk}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">) {
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">编译模块</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">2.编译模块 ${artifactId}</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">def</span> jarparam=<span style="color: rgba(128, 0, 0, 1)">''</span>
<span style="color: rgba(0, 0, 255, 1)">def</span> pinname =<span style="color: rgba(0, 0, 0, 1)"> artifactId
</span><span style="color: rgba(0, 0, 255, 1)">if</span>( pinname.length() > 23<span style="color: rgba(0, 0, 0, 1)">) {
pinname </span>= artifactId.substring(0,23<span style="color: rgba(0, 0, 0, 1)">)
}
</span>//<span style="color: rgba(0, 0, 0, 1)">添加pinpoint
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(params.ON_PINPOINT) {
jarparam </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
</span>//<span style="color: rgba(0, 0, 0, 1)">添加prometheus
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(params.ON_PROMETHEUS) {
jarparam </span>= jarparam + <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{jarparam}#${jarparam}#g' Dockerfile</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">'${MVNHOME}/bin/mvn' -DskipTests clean package</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
stash includes: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">target/*.jar</span><span style="color: rgba(128, 0, 0, 1)">'</span>, name: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">app</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Docker打包</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">3.Docker打包</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
unstash </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">app</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">'${MVNHOME}/bin/mvn' docker:build</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">推送镜像</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">4.Push Docker Image Stage</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">'${MVNHOME}/bin/mvn' docker:push</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
timeout(time: </span>10, unit: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">MINUTES</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
input </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">确认要部署吗?</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">发布</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (params.ENV_TYPE == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">offline</span><span style="color: rgba(128, 0, 0, 1)">'</span> || params.ENV_TYPE ==<span style="color: rgba(0, 0, 0, 1)"> null) {
sshagent(credentials: [</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">deploy_ssh_key_34</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">scp -r Deployment.yaml root@10.2.85.30:/docker/yaml/Deployment-${artifactId}.yaml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ssh root@10.2.85.30 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
sshagent(credentials: [</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">deploy_ssh_key_238</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]) {
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">scp -P 22 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
sh </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ssh -p 22 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">发布完成</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}
stage(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">通知负责人</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">){
</span>// emailext body: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">构建项目:${description}\r\n构建完成</span><span style="color: rgba(128, 0, 0, 1)">"</span>, subject: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">构建结果通知【成功】</span><span style="color: rgba(128, 0, 0, 1)">'</span>, to: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">${EMAIL}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">构建项目:${description}\r\n构建完成</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
}</span></pre>
</div>
<p>将Jenkinsfile文件放在项目根目录,然后将源码都上传到GitLab。</p>
<p align="left">打开BlueOcean,这是Jenkins新出的美化页面。</p>
<p><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214026448-1495387525.png" alt=""></p>
<p>选择自己的项目。</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214119462-829644504.png" alt="" width="695" height="284"></p>
<p>进入后点击运行,其中会弹出框选择发布参数(这里需要手工填写发布的端口,由于采用配置中心化,端口无法自动读取)。</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214203082-1925429859.png" alt="" width="796" height="361"></p>
<p>进入查看流程状态,失败会有相应的提示:</p>
<p><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214357451-32224749.png" alt="" width="724" height="314"></p>
<p>显示发布服务</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214448931-44600259.png" alt="" width="798" height="243"></p>
<p>在K8S内查看部署的服务启动情况。</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214555239-954570911.png" alt="" width="714" height="330"></p>
<p> </p>
<h3>Jenkinsfile Pipeline</h3>
<p>Jenkinsfile Pipeline语法内容可参考官网:https://jenkins.io/doc/book/pipeline/jenkinsfile/</p>
<p>还可以进入项目后,有个流水线语法:</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214631397-1954794516.png" alt="" width="707" height="243"></p>
<p>选择想要的功能,生成:</p>
<p> <img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/273387/201907/273387-20190709214649054-348900278.png" alt=""></p>
<p> Jenkins还可用作发布Vue前端项目,具体内容可参考 <strong><em>Jenkins</em></strong><strong><em>自动化构建</em></strong><strong><em>vue</em></strong><strong><em>项目然后发布到远程服务器</em></strong> 文档。</p>
<p> Jenkins要发布Net服务需要有一台windows的Jenkins slave,还需要在此节点上安装编译器MSBuild框架,Git框架、更改服务器上的IIS权限等功能,最后文件分发到其它windows服务器,过程比较繁琐,若无发布审核建议直接通过VS自带发布功能发布程序。</p>
</div>
<div id="MySignature" role="contentinfo">
<div style="background: rgb(230, 250, 230); padding: 10px 10px 10px 10px; border: 1px dashed rgb(224, 224, 224); font-family: 微软雅黑; font-size: 13px">
作者:欢醉
<br>公众号【一个码农的日常】 技术群:3199312041号群: 4378029862号群: 340250479
<br>
出处:http://zhangs1986.cnblogs.com/
<br>
码云:https://gitee.com/huanzui
<br>
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
<div style="position: fixed; right: 140px; bottom: 40px" id="div_diggc">
<div class="diggit" style="height: 25px; background-image: none; color: #FFFFFF; background-color: #2DAEBF; border: medium none; font-weight: bold; text-decoration: none">
<span class="diggnum">Top</span>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/zhangs1986/p/11102786.html
頁:
[1]