Jenkins CICD实用经验分享

1.修改 JVM 的内存配置

Jenkins 启动方式有两种方式,一种是以 Jdk Jar 方式运行,一种是将 War 包放在 Tomcat 容器下运行。不管何种方式运行,都会存在一个问题就是,默认 JVM 内存分配太少,导致启动或者运行一段时间后内存溢出报错 java.lang.OutOfMemoryError: PermGen space。所以,需要在启动前修改 JVM 内存配置。以 Tomcat 容器方式启动 Jenkins 为例配置如下:

1
2
3
4
# 进入到 Jenkins 运行所在 Tomcat bin目录
$ vim catalina.sh
# 在 #JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`" 行下增加修改配置 JVM 内存配置大小,例如下边配置:
JAVA_OPTS="$JAVA_OPTS -server -Xms512m -Xmx1024m -XX:PermSize=256M -XX:MaxPermSize=512m"

注意:这里的几个JVM 参数含义如下:

  • -Xms: 使用的最小堆内存大小
  • -Xmx: 使用的最大堆内存大小
  • -XX:PermSize: 内存的永久保存区域大小
  • -XX:MaxPermSize: 最大内存的永久保存区域大小

2.修改 Jenkins 主目录

Linux下Jenkins默认安装目录为:/home/jenkins/.jenkins,这个目录磁盘空间有限,长时间使用会导致磁盘空间不够,建议修改为其他大磁盘空间目录。这里修改安装目录有两种方式,一种是配置为系统环境变量中,一种是配置到 Tomcat 容器环境变量中。

2.1. 配置JENKINS_HOME到系统环境变量里面

1
2
3
4
5
6
7
注意:如果一台机器只安装一个Jenkins时,可以配置如下。
$vim /etc/profile
...
export JENKINS_HOME=/data/app/jenkins
export PATH=$PATH:$JENKINS_HOME
# 使配置生效
$ source /etc/profile

2.2 配置JENKINS_HOME到该Jenkins启动的Tomcat容器环境变量中

1
2
3
4
5
6
7
8
9
10
11
12
注意:如果一台机器上边安装多个Jenkins 时,不能配置JENKINS_HOME 到系统环境变量里面,需要配置JENKINS_HOME到该Jenkins启动的Tomcat 容器配置里面,这样可以区分不同的Jenkins目录。
(1)第一种方法是修改context.xml文件:
$ vim /data/app/apache-tomcat-7.0.85/conf/context.xml
<Context>
...
# 增加以下配置,优先获取该配置路径。
<Environment name="JENKINS_HOME" value="/data/app/jenkins/master/.jenkins" type="java.lang.String"/>
</Context>

(2)第二种方法是修改bin下的catalina.sh
添加如下:
export JENKINS_HOME="/data/app/jenkins/master/.jenkins"

这里要说明一下,如果一台机器上只安装了一个Jenkins服务时,可以配置 JENKINS_HOME到系统环境变量里面,如果安装了多个Jenkins服务时,不能这么配置,因为Jenkins会读取系统环境变量中JENKINS_HOME作为主目录安装,那样会存在配置覆盖的问题。

此时应该采用第二种方式,各自配置JENKINS_HOME到自己启动的Tomcat容器环境变量中,Jenkins会优先读取该容器环境变量作为各自的主目录安装。

附Jenkins寻找JENKINS_HOME环境变量的顺序为:

  • 首先读取容器环境变量
  • 如果没有,则读取系统环境变量
  • 如果还没有,则使用默认路径安装。

3.配置优化减少磁盘空间占用

Jenkins运行Job构建比较多时,如果没有配置好清理策略的话,会导致占用磁盘空间比较大,最终由于磁盘空间不够导致构建失败的问题。

3.1.丢弃旧的构建配置

可以在Job中配置丢弃旧的构建,通过设置“保持构建的天数” 和“保持构建的最大个数” 两个参数,控制该Job最大保存构建数量。

配置了最大保持3天之内的构建,如果超过3天的构建,则会在Job执行前被清理掉。同时配置了最大保持构建数量为10个,意思就是如果3天内构建次数如果超过10次,则最多保留最近执行的10个构建。这样配置的好处,除了能够自动清理一些Build之外,还能够为我们代码执行远程停止Job Build时,缩短停止时间

3.2.修改工作空间和构建记录根目录

Jenkins 工作主要分为安装主目录,工作空间目录以及构建记录目录,默认配置路径:/home/jenkins/.jenkins
先停止tomcat,修改catalina.sh指定JENKINS_HOME,然后将/home/jenkins/.jenkins复制到JENKINS_HOME下,最后启动tomcat,则主目录会变成新的指定的JENKINS_HOME目录。

4.设置全局属性

适当设置全局属性,可以避免在Job中重复写一些固定值,例如输出日志地址、接口请求地址等等,而且当固定值需要修改时,只需要修改一次即可,不用去每个Job里面修改,方便维护。可以去“系统管理” —> “系统配置” —> “全局属性” 下增加Environment variables键值对:

1
2
3
4
5
6
7
8
9
10
11
:DOCKER_JENKINS_URL

:http://127.0.0.1:8080/jenkins

:API_SERVICE_URL

:http://127.0.0.1:9090/api/v1

:LOG_PATH

:/data/app/logs

那么在job构建时执行”Execute Shell”,可以直接应用,例如:

1
2
3
4
5
6
7
8
#转发另一个Jenkins job执行
curl -X POST "${DOCKER_JENKINS_URL}/job/maven_build/buildWithParameters?token=a4575431315316313665555asdasdee55"

#进入到日志目录
cd ${LOG_PATH}

#输出接口地址
echo ${API_SERVICE_URL}

5.JDK/Maven/Gradle 等软件多版本安装

对于一些常用的软件,比如Jdk、Maven、Gradle等,可能每个项目对软件依赖版本不一样,有的项目依赖Jdk7,有的依赖Jdk8,所以为了更好的适配各个项目,可以指定安装多个版本软件,然后Job创建时选择其中一个版本使用。
这里以Jdk为例,去 “系统管理” —> “Global Tool Configuration” —> “JDK” 分别安装jdk6、Jdk7、Jdk8。

然后,在创建Job时,选择项目需要的一个版本即可!

6.设置构建超时时间

有些Job在执行构建时,由于某些原因导致构建挂起,耗时比较长,而这些长时间挂起的Job会导致Jenkins内存占用比较大,性能下降,严重的会直接导致 Jenkins挂掉。所以,需要设置构建超时时间来预防这种事情发生,一旦超过一定的时间,要让Job自动停止掉。

jenkins的”build timeout plugin”插件可以帮我们完成该任务。

Build Timeout
Aborts a build if it’s taking too long

例如,这里设置构建超过30分钟则将本次Build置为失败:
Abort the build if it’s stuck:
time-out strategy: Absolute Timeout minutes:30

7.配置视图分类管理Job

Jenkins默认视图为ALL,显示所有Job列表,如Job比较多的话,找某个Job会不太方便,虽然有Search搜索功能,毕竟还是不太方便。这时候,可以通过新建视图方式,对Job进行分门别类,这样管理和查找起来就方便多了!

8.配置多节点管理

一般我们会使用Jenkins Slave集群管理来完成日常持续集成操作,使用 Jenkins Slave一主多从方式,可以将Job调度到对应的Slave机器上执行,能够大大提高系统并发执行效率。

可以从“系统管理”—>“管理节点”—>“新建节点”,设置节点类型为“Permanent Agent”名称“slave1”的一个从节点,当然有多个节点时,可以创建多个。创建完毕之后,此时插件还属于不可用状态,因为还没有执行关联,具体关联方式可以参照Jenkins上节点关联说明,关联完毕之后,就可以在新建Job中配置指定那个Slave节点运行了

  • 远程工作目录: /data/app/jenkins/slave
  • 标签:slave1
  • 用法:尽可能的使用这个节点
  • 启动方式:Launch slave agents via SSH
  • 主机:jenkins master所在主机ip
  • 可用性:尽量保持代理在线

有两种方式指定Job在哪个Slave节点运行:

  • 一种是对于自由风格类型的Job,我们可以通过在“Restrict where this project can be run” 选项下指定“Label Expression” 标签指定节点标签。
  • 另一种是对于多配置项目类型的Job,可以通过在 “Configuration Matrix” 先配置“Slave”选择Node/Label勾选指定一个或多个Slave 执行。

9.一些实用插件

Jenkins的基础配置就能够满足我们日常的基本工作,但是为了提高构建效率和方便维护,Jenkins上提供了很多实用的插件,使用这些插件,可以更加轻松、更加简便、更加高效的执行持续集成和发布流程。下边,简单介绍几个插件。

9.1 Managed Script 插件管理脚本文件

该插件是为了在管理文件时创建Script脚本文件,然后在Job中配置直接使用,方便脚本的统一管理和维护。首先我们需要去“系统管理” —> “管理插件” —> “可选插件” 中选择“Managed script” 插件,安装重启即可。

安装完毕后,可以从“系统管理”—>“Managed files”—>“Add a new Config” 选择 “Managed script file” 类型,创建一个新的shell 脚本文件,然后输入我们要执行的脚本代码。这里我创建了两个脚本,分别为 before-build-step-shell 和 after-build-step-shell,意思很明确了,前者在构建前执行的一些操作,后者在构建后执行的一些操作。

注意: 这里的脚本可以使用一些Jenkins系统的环境变量参数、参数化构建时传递的参数以及系统命令。

创建完毕后,在Job中构建处选择“Execute managed script” 就可以使用这些脚本了。

9.2 PostBuildScript 插件根据 Build 状态执行脚本

推荐安装 PostBuildScript插件,该插件可以在构建后操作中,根据构建结果状态,执行对应的脚本操作,很实用的一个插件。同上安装该插件,重启 Jenkins 完毕插件生效后,Job 中构建后操作处选择 “Execute Scripts” ,然后在 “Add build step” 中选择 “Execute shell” 等选项(当然也可以配合上一个插件,那么这里就选择 “Execute managed script”),下边选择一个build状态条件值,如果选择SUCCESS状态,那么该脚本只有在Build成功时才会执行,其他状态依次类推,状态可以多选,多选代表多种状态都能下触发。

9.3 Jenkins2.0 Pipeline 插件执行持续集成发布流程

详细文章参考 初试Jenkins2.0 Pipeline持续集成

9.4 Kubernetes Plugin 插件动态创建 Jenkins Slave

传统的Jenkins Slave一主多从方式会存在一些痛点,比如Master单点故障,Slave配置环境差异,资源分配不均衡等导致可靠性和可维护性比较差,而使用 Kubernetes Plugin插件可以动态的创建和删除Jenkins Slave 节点,使用它可以很好的保证服务高可用,动态伸缩合理使用资源,以及良好的扩展性。

使用该插件后,它的工作流程大致为:当Jenkins Master接受到Build 请求时,会根据配置的Label动态创建一个运行在Docker Container中的 Jenkins Slave,并注册到Master上,当运行完Job后,这个Slave会被注销并且 Docker Container也会自动删除,恢复到最初状态。

详细文章参考 初试 Jenkins 使用 Kubernetes Plugin 完成持续构建与发布

10.JAVA 代码触发 Jenkins Job 创建、删除、停止等操作。

Jenkins Job创建、删除、构建等操作,除了在页面手动操作外,还可以通过 Jenkins API接口执行对应操作,详细接口可参考 Jenkins
REST API 文档地址:http:///api。这里演示的是使用 Jenkins-client.jar包,使用JAVA代码操作如何创建、删除、停止、触发构建等,使用代码触发jenkins相关操作,好处就是自己可控,这样可以配合自己的业务需要,随时启动或者新建Job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class JenkinsUtils {

private static String jenkins_url = "http://127.0.0.1/jenkins/";
private static String jenkins_user = "admin";
private static String jenkins_token = "1b356d175432ed0d34c440d68d00fe49";

/**
* 通过模板创建 Job
* @param jobName
* @param jobTemplate
* @return
*/
public static boolean createJobFromTemplate(String jobName, String jobTemplate) {
try {
URI uri = new URI(jenkins_url);
JenkinsServer jenkins = new JenkinsServer(uri, jenkins_user, jenkins_token);
String template = jenkins.getJobXml(jobTemplate);
Document doc = DocumentHelper.parseText(template);
String newConfigXml = doc.asXML();
jenkins.createJob(jobName, newConfigXml, false);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}

/**
* 删除/禁用 Job
* @param jobName
* @return
*/
public static boolean deleteJob(String jobName){
try {
URI uri = new URI(jenkins_url);
JenkinsServer jenkins = new JenkinsServer(uri, jenkins_user, jenkins_token);
jenkins.deleteJob(jobName, false);
jenkins.disableJob(jobName, false);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 启动 Job 构建
* @param jobName
* @param params
* @return
*/
public static boolean startJob(String jobName, String params){
try {
String buildUrl = jenkins_url + jobName + "/buildWithParameters?" + params;
HttpUtils.HttpGet(buildUrl);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 停止正在构建中的 Job,先清除等待队列中的 build,在停止运行中的 build
* @param jobName
* @return
*/
public static boolean stopJob(String jobName){
try {
URI uri = new URI(jenkins_url);
JenkinsServer jenkins = new JenkinsServer(uri, jenkins_user, jenkins_token);
// 先 kill 掉 queue 里面的 build
QueueItem qi = jenkins.getJob(jobName).getQueueItem();
if(qi != null){
HttpUtils.HttpPost(jenkinsUrl + "queue/cancelItem?id=" + qi.getId());
}
// 在 kill 掉正在运行中的 build
List<Build> bulidsList = jenkins.getJob(jobName).getAllBuilds();
for(Build b:bulidsList){
if(b.details().isBuilding()){
try{
b.Stop();
}catch(Exception ee){
ee.printStackTrace();
return false;
}
}
}
} catch(Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}

这里有一个地方要注意,在停止构建中的Job 时,这里是遍历所有 Build,然后在Kill掉运行中的Build,如果Build历史比较多的时候,会耗时比较久,这将会导致立马重新执行该Job Build时,Build会被异常Abort掉。 也尝试过获取最后一次Build执行Stop操作,好像也不太好使。所以这里大家可以通过上边3.1、丢弃旧的构建配置中的操作,减少构建历史记录,这样就可以很快执行完毕,就不会出现上述问题了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!