up
100
作者 jokin 2017-01-22 19:58:50
写了0文章0获得了0条评论
暂无说说 · 评论
搭建Jenkins持续集成环境
字数·7747 评论0 喜欢0 转发0

搭建Jenkins持续集成环境,并利用Jenkins搭建自动构建系统,继而利用自动构建系统发布测试版或进行自动测试等

目录

Jenkins

Jenkins是一个用Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目复刻(派生/fork)。

Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中(例如Apache Tomcat)。它支持软件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和和RTC),可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令。Jenkins的主要开发者是川口耕介。Jenkins是在MIT许可证下发布的自由软件。

来自维基百科。

安装

安装基于2.32.2,下面的笔记针对1.647版本。

  1. 访问https://jenkins.io/,在下载按钮右侧的下拉框选择对应的平台。windows下得到的是一个zip文件。
  2. 安装。windows下解压得到jenkins.msi,直接安装。
  3. 安装完成。windows下安装完成会自动跳转到localhost:8080
  4. 打开Jenkins\secrets\initialAdminPassword,复制密码到网页。
  5. 安装建议的插件。
  6. 创建管理员账号。
  7. 完成。

执行上面的步骤,就能看到下面的开始页。

jenkins-index
之前使用的是1.647,相对来说简单点

登录

如果第一次打开Jenkins没有创建用户就退出了登录,那么需要使用用户名admin和上面initialAdminPassword里的密码来登录。如果创建了用户,则可以使用新用户登录。

jenkins-login

全局配置

  • 工作空间根目录 和 构建记录根目录
    最好设置为Job所在的目录下,这样Job各自的工作空间就会在Job对应的目录下创建,而不会在Jenkins根目录下创建。
    ${ITEM_ROOTDIR}/workspace
    ${ITEM_ROOTDIR}/builds

  • Jenkins URL
    可以设置为域名,也可以设置为IP,作为URL地址,用于别人访问Jenkins。

  • 系统管理员邮件地址
    Jenkins发送邮件时发件人信息。

  • Extended E-mail Notification
    当添加了Email Extension插件时设置项,主要设置邮件服务器(SMTP server)、邮件后缀名(Default user E-mail suffix)以及邮件格式(Default Content Type)。

  • 邮件通知
    和邮件扩展插件一样,这里可以通过发送测试邮件检验配置是否有效。

插件管理

插件管理可以对Jenkins的插件进行增删改查操作。每一个插件都有对应的帮助页面,从上面可以了解到插件的用途,也可以获取插件提供的API、Demo、文档等等。有时候这些帮助页很有用,遇到的问题Google可能无法得到答案,而它就在插件的帮助页面上!使用某个插件遇到了问题,不妨先查看对应的帮助页面。

用户和权限管理

Jenkins安装完成后,默认不会启用用户管理,需要配置:

  1. 在Jenkins的全局安全里面,勾选启用安全,选择Jenkins专有用户数据库,并勾上允许用户注册(一定要先勾上),然后保存。
  2. 保存后,Jenkins就会弹出注册页,这是第一个管理员,然后继续注册需要登录验证的用户。
  3. 使用某一个注册账号登录,取消允许用户注册后,就只有当初注册了的用户能够登录。然而,上面的操作后,普通用户一样具备管理权限。
  4. 此时,授权策略选择安全矩阵,把匿名用户的权限只保留Overall-Read,Job-Discover,Job-Read即可,这样未登录的用户只能查看。

如果需要按照角色分配权限,需要安装Role Strategy Plugin,这个插件的帮助页面有配置教程,可以实现按用户组分配权限。

环境变量

Jenkins的环境变量包括系统环境变量Jenkins内置环境变量Job构建时的环境变量,以下是常用的环境变量:

  • BUILD_URL 当前构建信息URL
  • JOB_URL
  • PROJECT_URL 项目工程目录URL
  • BUILD_STATUS 构建结果
  • SVN_REVISION 当前构建的SVN Revision
  • CHANGES 当前构建到上一次构建间的svn log合集(无论上一次构建成功与否,都会影响当前的这一次构建的CHANGES)

比如:
当前编译结果: ${BUILD_STATUS}
当前编译Revision: ${SVN_REVISION}
当前项目目录: ${PROJECT_URL}/ws
当前编译日志: ${BUILD_URL}/console
当前版本变化: ${CHANGES}

Job的配置

安装了不同的插件,Job会有不同的配置,但主要是这些配置。

Job的构建

Job的构建,分为构建参数设置、构建前、构建中、构建后,每个步骤都可以执行相应的脚本。

  • 脚本可以支持:
    windows命令行、Python、Groovy、Shell等等(可能需要安装对应的插件,或者安装Python然后在命令行里执行)。
  • 构建结果:
    脚本执行的返回值为0,则表示成功,非0表示失败(这点非常重要!Jenkins不能判断脚本内部如何执行,它只关心最后的返回值)。

参数化构建过程

参数化构建过程,顾名思义,指构建时传递到构建过程的参数,其中类型有:

  • 字符串
  • 文本
  • bool值
  • 列表
  • List Subversion tags
    这个参数可以列出版本库里所有的分支,在需要对版本库某个分支进行构建时非常有用(见下图)
    jenkins-branch

每一个参数都有一个Name字段,表示参数名,构建时参数名和对应的值将被设置为环境变量,Jenkins中可以通过${参数名}来访问。

比如:Name为Version的参数,构建时值设置为1,则${Version}的值为1(在Windows批处理下则是%Version%的值为1)。

这个值可以在构建时的脚本中访问,可以在构建后的脚本中访问,也可以在构建后的邮件设置中访问。但需要注意,每个变量,都有它的生命周期,并非每个变量的生命周期都这样长,具体要看变量是如何定义的。

${xxx}为Shell访问变量的方式;%xxx%为Windows命令行访问变量的方式。

Rebuilder插件

当设置参数构建了一个版本后,希望参数能够自动保存,可以在下一次Build构建时使用,但是暂时还没有插件在Build构建时提供这个功能。Rebuilder插件能够提供类似功能,但是它不是Build操作,而是Rebuild操作。安装Rebuilder插件后,Rebuild时参数就是上一次构建时的参数,此时修改参数为新参数即可进行新一次构建。

Email Extension插件

顾名思义,叫邮件扩展插件,增加了设置邮件内容设置邮件触发器等功能。在高级设置里(很多插件的设置都在高级设置里),可以设置邮件触发器,一般处理失败和成功事件。邮件发送对象:

  • Requestor 发起此次构建的用户邮件
  • Developers 此次构建与上一次构建,进行了更改的开发者名称(版本管理器里的提交者)与邮件后缀组合得到的邮件地址
  • (更多查看插件对应的帮助页面)

邮件附件: 附件帮助说使用Ant格式,结果试了很多次都失败!最后直接写*.dll来测试,就成功了。附件可以使用常见的正则表达式,路径以workspace为始目录,且附件必须在当前构建的目录下,否则会找不到附件。

Groovy Postbuild插件

此插件用于Build构建后Groovy脚本支持,可以在Build后做各种各样的事情,其中一样就是修改构建页显示的内容(见下图)

manager.addShortText( manager.getEnvVariable("VERSION") )
str = manager.getEnvVariable("SUBJECT")
manager.addShortText( str.substring(0,  str.size() > 10 ? 10 : str.size()) ) //would crash while size < 10
manager.addShortText( 'V' + manager.getEnvVariable("VER") )
manager.addShortText( manager.getEnvVariable("SVN_REVISION") )
manager.getEnvVariable("REBUILD") == "true" ? "" : manager.addShortText("Build")

以上代码的效果是:
jenkins-build-status

Multi-Branch Project Plugin插件

当一个仓库含有多个分支,需要为每个分支分别创建构建任务的时候,可以使用此插件。曾经试过使用上面的List Subversion tags参数化构建过程实现,当需要构建某个分支时,下拉选择对应的分支然后进行构建。然而仓库更新会对整个仓库进行更新,所以只是构建某个分支时,更新仓库的操作就非常浪费时间;而在脚本里只更新某个仓库,却因为构建过程svn被锁定(lock)而无法操作。其实这个思路是行的通的,只是没有时间折腾,就选择了Multi-Branch插件来实现。

jenkins-multi-branch

邮件中使用脚本

Jenkins中邮件服务是比较重要的一个,构建过程成功与失败都可以通过邮件通知。邮件中也可以使用脚本,不过需要Email Extension插件。

邮件脚本支持jelly和groovy。插件帮助文档和Github有相关的demo。

获取构建时间

jelly版buildtime.jelly

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
本次构建用时: ${build.durationString}
</j:jelly>

groovy版buildtime.groovy

<%//ANSI中文%>
本次构建用时: ${build.durationString}

如上,脚本的编码需要注意。

获取项目变更集

通常Jenkins获取项目变更集直接使用${CHANGESET},但是构建失败后,再次构建CHANGESET会为空,所以需要使用脚本来做一些逻辑处理。

jelly版changeset.jelly

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
<j:set var="changeSet" value="${build.changeSet}" />
<j:if test="${changeSet!=null}">
  <j:whitespace>变更汇总:&#10;</j:whitespace>
  <j:set var="hadChanges" value="false"/>
  <j:set var="index" value="1"/>
  <j:forEach var="cs" items="${changeSet}" varStatus="loop">
    <j:set var="hadChanges" value="true"/>
    <j:set var="aUser" value="${cs.hudsonUser}"/>
    <j:whitespace> ${index}. ${cs.msgAnnotated} - ${aUser!=null?aUser.displayName:cs.author.displayName}&#10;</j:whitespace>
	<j:set var="index" value="${index+1}"/>
  </j:forEach>
  <j:if test="${!hadChanges}">
    <j:whitespace>No Changes&#10;</j:whitespace>
  </j:if>
</j:if>
</j:jelly>

groovy版changeset.groovy

<%//ANSI中文%>
变更汇总:
<%
/**
 * 获取上次失败的构建列表(虽然失败,但是它们占用了changeSet)
 * 此脚本运行于post-successful-build,所以lastSuccessfulBuild和lastBuild就是当前的构建
 **/
def _getLastFailureBuilds()
{
	def failList = []
	def lastSuccessfulBuild
	def allBuilds = project.getBuilds()

	//you can't abort an "each" without throwing an exception.use a "find" or "any" closure instead
	//默认迭代变量it
	allBuilds.any()
	{	  
	  if (it.getNumber() == build.getNumber())	//忽略自己
	  {
		return false
	  }
	  else if (it.getResult() != Result.SUCCESS)	//include ABORTED/FAILURE/UNSTABLE
	  {
		failList << it
		return false			//手动return false,否则最后一句的返回值就是any的返回值,failList<<it会返回true而中断了遍历导致结果只有一条
	  }
	  else if (it.getResult() == Result.SUCCESS)
	  {
		lastSuccessfulBuild = it
		return true
	  }
	}
	return failList
}

//如果要函数内部能够访问,不能使用def或其他类型声明(比如 int i),声明是指代局部变量
hadChanges = false
index = 1
def _showChangeSet(changeSet)
{
	if(changeSet != null)
	{		
		changeSet.each()
		{
			cs->				//声明一个迭代变量
			hadChanges = true
			println " ${index}. ${cs.msgAnnotated} - ${cs.author}"
			index += 1	    
		}
	}
}

/*Main*/

/*按照后提交先输出顺序*/

_showChangeSet(build.changeSet)

def lastFailureBuilds = _getLastFailureBuilds()
lastFailureBuilds.each()
{
	_showChangeSet(it.getChangeSet())		//getChangeSet() 等价于 changeSet
}

if (! hadChanges)
{
	println "No Changes"
}
%>

把上面四个文件,放到Jenkins\email-templates下,没有该文件夹就手动创建。
然后在Email内容里调用:

此版本ChangeSet:
${SCRIPT, template="changeset.groovy"}

此版本构建用时:
${SCRIPT, template="buildtime.groovy"}

附上完整的邮件内容例子

++++++++++++++++++++++++++++++
+  $VERSION版本-$SUBJECT-测试版V$VER
++++++++++++++++++++++++++++++
此版本解决的问题:

${FIXBUG}

${SCRIPT, template="changeset.groovy"}
==============================
当前编译结果: ${BUILD_STATUS}

当前编译分支:
当前编译Revision: ${SVN_REVISION}

当前项目目录: ${PROJECT_URL}/ws
当前编译日志: ${BUILD_URL}/console

${SCRIPT, template="buildtime.groovy"}

From: gametaskcenter-autobuild  
- this email is auto sent by Jenkins do not reply

邮件截图

jenkins-mail

参考文献

  1. 维基百科,Jenkins。
  2. Jenkins Doc. https://jenkins.io/doc/.
  3. Email-ext plugin help. https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+plugin.
  4. Email-ext plugin github. https://github.com/jenkinsci/email-ext-plugin/tree/master/src/main/resources/hudson/plugins/emailext/templates.