写在前面:
本文是对公司项目做的一次自动化实战。公司架构分新旧两版,本文是对旧版本的一次实战。且由于maven仓库需内网环境的原因,选择非maven项目进行实战。
笔者在测试完成后已在个人服务器将所有源码移除,且本文未展示任何源码信息。
1.CI/CD
1.1 何为CI/CD
根据互联网上绝大多数的说法,CI/CD其实是三个概念,包括了一个CI和两个CD。分别是 Continuous Integration(持续集成), Continuous Delivery(持续交付)和 Continuous Deployment(持续部署)。
简言之,CI/CD旨在打通软件开发与交付部署的链路,使各个环节形成通路,持续提高开发效率与代码品质。
1.2 为何CI/CD
CI/CD有何目的?其核心还是自动化,通过自动化的手段来提高代码质量,提升程序开发体验,更为重要的是缩短软件的交付时间。
此外,可参考PDCA戴明环,恰如其分的计划实施检查处置,可以使得整个项目质量呈螺旋式上升。CI/CD持续集成也可通过自动化步骤将整个项目的开发,测试,部署,交付联通。及时发现问题,处置问题,发现新的问题,处置新的问题... ...
1.3实战CI/CD
就笔者个人而言,我所在的项目在项目初期就已在瑞道云平台实现持续集成,持续部署。
项目为旧版本项目,通过maven进行包管理。持续集成思路为:将整个后端代码打包后上传到maven仓库,前台应用包根据坐标引用后端的jar包。将前台应用包与后台代码的持续集成巧妙转化为后台打包推送仓库与前台应用包持续集成持续部署。
同时使用容器技术,对版本镜像进行管理,另有基于Kubernetes的容器调度与管理平台,对应用进行监控与告警。总的来说在较大程度上简化集成与部署步骤,同时对镜像版本规范管理,也有益于应对升级事故。
但也正如上文所述,该部署方式较大程度上弱化了后台代码的持续集成与持续部署。例如:码农甲更改了后台代码,希望能在测试环境测试,则他需要先将后台代码打包上传,再调度前台应用的流水线发版。
相较于上面的场景,想象如下的场景:程序员乙今日修改了后台代码,检查无误后就将代码推送到版本库,版本库识别到改动,调度CI/CD工具对改动代码进行集成与部署,并将构建结果邮件告知程序员乙以及项目同事。注意,此处集成与部署的是整个应用包,而不仅是后台,也就是说只需要提交代码,然后等邮件即可,极大程度简化步骤。
2.SVN触发Jenkins自动构建
1.前置说明
- 1.CI/CD工具此处使用的是Jenkins,较为主流。
-
- 注意:Jenkins构建代码时占用内存较大。笔者个人2+2的云服务器完全不够,一构建内存会狂飙至100%。
- 2.互联网上较多的资源介绍的是maven打包,因此这里就不过多介绍maven项目的CI/CD。此处选择ant打包。
- 3.代码版本管理网上较多资源选择git,此处选择svn,更加切合项目实际。
- 4.除上述所需环境外,还需Java环境,数据库(此处选择mysql5.7),以及默认已配置好svnhttp访问的httpd,跨主机调用时的内网穿透等。
-
- 一般情况不需要内网穿透,但正如笔者所述。2+2的云服务器难堪大用,因此将jerkins部署在本地,但svn库放在远程,svn钩子函数需要触发本地构建,因此需要用到内网穿透。
2.实操步骤
2.1.前置相关配置
注:基础环境配置忽略
- 1.Jenkins所需插件
- Subversion svn版本管理
- Ant Plugin ant打包
- Publish Over SSH ssh远程推送
- 发送邮件通知
- 2.Jenkins系统配置
-
Jenkins url与系统邮件地址
-
邮件通知相关配置(注意:邮件密钥需要自己去相关邮箱配置授权码。另:这里是163邮箱的配置。)
-
右下有高级设置,其他账户也可配置上
-
默认正文内容与默认收件人
-
默认标题与正文(资源来自互联网,源码在步骤最后)
-
邮件通知配置
-
SSH推送配送
-
全局的jdk配置以及ant配置
-
构建通知:${BUILD_STATUS} - ${PROJECT_NAME} - Build # ${BUILD_NUMBER} !
<!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>
<td>(本邮件是程序自动下发的,请勿回复!)</td>
</tr>
<tr>
<td><h2>
<font color="#0000FF">构建结果 - ${BUILD_STATUS}</font>
</h2></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>SVN 版本: ${SVN_REVISION}</li>
<li>触发原因: ${CAUSE}</li>
<li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">Changes Since Last
Successful Build:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"}
</td>
</tr>
<tr>
<td><b>Failed Test Results</b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td><pre
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
<br /></td>
</tr>
<tr>
<td><b><font color="#0B610B">构建日志 (最后 100行):</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<!-- <tr>
<td>Test Logs (if test has ran): <a
href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a>
<br />
<br />
</td>
</tr> -->
<tr>
<td><textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">${BUILD_LOG, maxLines=100}</textarea>
</td>
</tr>
</table>
</body>
</html>
2.2.创建一个构建任务
-
创建自由风格项目即可
-
源码管理选择svn
-
触发构建脚本与轮询都需勾选
-
构建环境选择ant,ant与jdk选择前置的全局配置
-
构建步骤,ant的build.xml文件会在项目实践中贴出
-
构建后步骤增加消息推送与ssh远程推送
(触发选择always)
(ssh推送直接推送到所需要的lib文件夹下,且删除旧包,重新部署tomcat)
- 注意:前文触发构建时配置的token“baseOSPBuild”,是配置在svn仓库下,hooks里面的钩子函数。但我这里展示的是另一种脚本触发方式。
上图中admin及token在如图所示处配置
@符号后面的域名是访问jenkins的域名。
2.3.项目实战
前置先删除之前的jar包
前台前置校验
后台提交代码触发整个项目构建
触发失败,未开启内网穿透
开启穿透后再次提交
推送成功
成功触发自动构建
邮箱收到构建结果
前台验证
后台类
另:
贴上build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="jar" name="baseOSP">
<!-- 引入配置文件 -->
<!-- <property file="build.properties" />-->
<!-- 配置引用属性 -->
<property name="src.dir1" value="model1" />
<property name="src.dir2" value="model2" />
<property name="build.dir" value="build" />
<property name="lib.dir" value="libs" />
<property name="name" value="baseOSP" />
<path id="master-classpath">
<fileset dir="${lib.dir}">
<include name="*.jar" />
</fileset>
<pathelement path="${build.dir}" />
</path>
<!-- 编译生成class文件 -->
<target name="build"
description="Compile main source tree java files">
<!-- 删除编译存放的classes文件 -->
<delete dir="${build.dir}/classes"></delete>
<!-- 新建编译存放的classes文件 -->
<mkdir dir="${build.dir}/classes" />
<copy todir="${build.dir}/classes">
<!-- 将项目中除会编译生成class文件的java文件之外其他类型的文件拷贝到对应的目录下,指定文件名后缀 -->
<fileset dir="${src.dir1}">
<include name="**/*.xml" />
</fileset>
<fileset dir="${src.dir2}">
<include name="**/*.xml" />
</fileset>
</copy>
<!-- java编译 -->
<javac destdir="${build.dir}/classes" source="1.8" target="1.8"
debug="true" deprecation="false" optimize="false" failonerror="true"
encoding="utf-8">
<src path="${src.dir1}" />
<!-- 编译所需要的jar包路径 -->
<classpath refid="master-classpath" />
</javac>
<javac destdir="${build.dir}/classes" source="1.8" target="1.8"
debug="true" deprecation="false" optimize="false" failonerror="true"
encoding="utf-8">
<src path="${src.dir2}" />
<!-- 编译所需要的jar包路径 -->
<classpath refid="master-classpath" />
</javac>
</target>
<target name="jar" depends="build"
description="Compress the java class files to jar">
<mkdir dir="${build.dir}/jar" />
<delete file="${build.dir}/jar/${name}.jar" />
<buildnumber file="${build.dir}/buildnum.txt" />
<!-- 指定时间戳 可以调用TODAY -->
<tstamp>
<format property="TODAY" pattern="yyyy-MM-dd HH:mm:ss" />
</tstamp>
<!-- 打包jar -->
<!-- includes 包含哪些后缀文件 空格分隔 -->
<jar destfile="./${name}.jar"
basedir="${build.dir}/classes"
includes="**/*.class **/*.xml">
<zipfileset src="${lib.dir}/DBFormBuilder-1.2.26.jar"></zipfileset>
<zipfileset src="${lib.dir}/osp-base-sdk-1.1.330.jar"></zipfileset>
</jar>
</target>
<!-- <target name="cleanup">-->
<!-- <delete dir="${build.dir}/classes" />-->
<!-- </target>-->
</project>
3.后续展望
- 本实例是在不使用maven,以及未上前后端分离应用的情况下作出的svn触发Jenkins持续集成的实例。前台应用包可单独创建流水线,通过svn钩子函数触发服务器tomcat重启即可。后台代码构建难度相较而言较高,与之相比已无说明意义,参照次实例应用即可。
- 前文提及的云服务器,内网穿透等使用因人而异。云服务器不做提及,内网穿透个人使用过ngrok和花生壳,推荐使用花生壳,ngrok虽然免费,但域名和服务器随机分配,稳定性差。使用云服务器及内网穿透等目的在于:证明ssh推送几svn远程触发的可行性。
- Jenkins另可与jmeter,sonar协同使用,提升代码品质,降低交付风险。
- 相关源码已在个人svn服务器尽数移除。