持续集成
1. 概述
持续集成(Continuous integration,简称 CI)指的是,频繁地(一天多次)将代码集成到主干
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成
通过持续集成,团队可以快速的从一个功能到另一个功能,简而言之,敏捷软件开发很大一部分都要归功于持续集成
根据持续集成的设计,代码从提交到生产,整个过程有以下几步:
-
提交
流程的第一步,是开发者向代码仓库提交代码,所有后面的步骤都始于本地代码的一次提交
-
测试(第一轮)
代码仓库对提交操作配置了钩子,只要提交代码或者合并进主干,就会跑自动化测试
-
构建
通过第一轮测试,代码就可以合并进主干,就算可以交付了
-
测试(第二轮)
构建完成,就要进行第二轮测试。如果第一轮已经涵盖了所有测试内容,第二轮可以省略,当然,这时构建步骤也要移到第一轮测试前面
-
部署
过了第二轮测试,当前代码就是一个可以直接部署的版本。将这个版本的所有文件打包存档,发到生产服务器
-
回滚
一旦当前版本发生问题,就要回滚到上一个版本的构建结果。最简单的做法就是修改一下符号链接,指向上一个版本的目录
2. 组成要素
1、一个自动构建过程,从检出代码、编译构建、运行测试、结果记录、测试统计等都是自动完成的,无需人工干预
2、一个代码存储库,即需要版本控制软件来保障代码的可维护性,同时作为构建过程的素材库,一般使用 SVN 或 Git
3、一个持续集成服务器, Jenkins 就是一个配置简单和使用方便的持续集成服务器
3. 持续集成的好处
1、降低风险,由于持续集成不断去构建,编译和测试,可以很早期发现问题,所以修复的代价就少;
2、对系统健康持续检查,减少发布风险带来的问题;
3、减少重复性工作;
4、持续部署,提供可部署单元包;
5、持续交付可供使用的版本;
6、增强团队信心
4. 持续集成流程说明
1)开发人员每天进行代码提交,提交到 Git 仓库
2)然后,Jenkins 作为持续集成工具,使用 Git 工具或者 Git 仓库拉取代码到集成服务器,再配合 JDK、Maven 等软件完成代码编译、代码测试与审查、测试、打包等工作,在这个过程中有一步出错,都要重新执行一次流程
3)最后,Jenkins 把生成的包分发到测试服务器或生产服务器
Gitlab 代码托管服务器
GitLab 是一个用于仓库管理系统的开源项目,使用 Git 作为代码管理工具,并在此基础上搭建起来的 web 服务
GitLab 和 GitHub 一样属于第三方基于 Git 开发的作品,免费且开源。不同的是,GitLab 可以部署到自己的服务器上,数据库等一切信息都掌握在自己手上,适合团队内部协作开发
以 centos 为例,安装步骤如下:
-
安装相关依赖
yum -y install policycoreutils openssh-server openssh-clients postfix
-
启动 ssh 服务 & 设置为开机启动
systemctl enable sshd && sudo systemctl start sshd
-
设置 postfix 开机自启,并启动,postfix 支持 gitlab 发信功能
systemctl enable postfix && systemctl start postfix
-
开放 ssh 以及 http 服务,然后重新加载防火墙列表
firewall-cmd --add-service=ssh --permanent firewall-cmd --add-service=http --permanent firewall-cmd --reload
如果关闭防火墙就不需要做以上配置
-
下载 gitlab 包,并且安装在线下载安装包
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el6/gitlab-ce-12.4.2-ce.0.el6.x 86_64.rpm](https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el6/gitlab-ce-12.4.2-ce.0.el6.x86_64.rpm
-
修改 gitlab 配置
vi /etc/gitlab/gitlab.rb
修改 gitlab 访问地址和端口,默认为 80,我们改为 82
external_url ‘http://192.168.66.100:82’ nginx[‘listen_port’] = 82
-
重载配置及启动
gitlab gitlab-ctl reconfigure gitlab-ctl restart
-
把端口添加到防火墙
firewall-cmd --zone=public --add-port=82/tcp --permanent firewall-cmd --reload
Jenkins
Jenkins 是一款流行的开源持续集成(Continuous Integration)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能
1. Jenkins 安装
-
获取 Jenkins 安装包,下载页面:https://jenkins.io/zh/download/
进行安装:
rpm -ivh jenkins-2.190.3-1.1.noarch.rpm
-
修改 Jenkins 配置
vi /etc/syscofig/jenkins
修改内容如下:
JENKINS_USER="root" JENKINS_PORT="8888"
-
启动 Jenkins
systemctl start jenkins
-
打开浏览器访问 http://localhost:8888
-
获取并输入 admin 账户密码
cat /var/lib/jenkins/secrets/initialAdminPassword
2. Jenkins 插件管理
Jenkins 本身不提供很多功能,我们可以通过使用插件来满足我们的使用。例如从Gitlab拉取代码,使用Maven构建项目等功能需要依靠插件完成
Jenkins 国外官方插件地址下载速度非常慢,可以修改为国内插件地址:Jenkins - Manage Jenkins - Manage Plugins,点击 Available
这样做是为了把 Jenkins 官方的插件列表下载到本地,接着修改地址文件,替换为国内插件地址
cd /var/lib/jenkins/updates
sed -i 's/http:\/\/updates.jenkinsci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && sed -i
's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
最后,Manage Plugins 点击 Advanced,把 Update Site 改为国内插件下载地址 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
Sumbit 后,在浏览器输入:http://localhost:8888/restart,重启 Jenkins,下载中文汉化插件
Jenkins - Manage Jenkins - Manage Plugins,点击 Available,搜索 "Chinese",勾选并安装。重启 Jenkins 后,就看到 Jenkins 汉化了
3. Jenkins 用户权限管理
我们可以利用 Role-based Authorization Strategy 插件来管理 Jenkins 用户权限,安装插件,点击 Manage Jenkins,选择 Configure Global Security,授权策略切换为 Role-Based Strategy,保存
在系统管理页面进入 Manage and Assign Roles,点击 Manage Roles,可创建角色
- Global roles(全局角色):管理员等高级用户可以创建基于全局的角色
- Project roles(项目角色):针对某个或者某些项目的角色
- Slave roles(奴隶角色):节点相关的权限
在系统管理页面进入 Manage Users,创建用户。接下来是为用户分配角色,系统管理页面进入 Manage and Assign Roles,点击 Assign Roles,为用户分配角色
4. Jenkins 凭证管理
凭据可以用来存储需要密文保护的数据库密码、Gitlab 密码信息、Docker 私有仓库密码等,以便 Jenkins 可以和这些第三方的应用进行交互
要在 Jenkins 使用凭证管理功能,需要安装 Credentials Binding 插件。安装插件后,会多出一个凭证菜单,在这里管理所有凭证
可以添加的凭证有五种:
- Username with password:用户名和密码
- SSH Username with private key:使用 SSH 用户和密钥
- Secret file:需要保密的文本文件,使用时 Jenkins 会将文件复制到一个临时目录中,再将文件路径设置到一个变量中,等构建结束后,所复制的 Secret file 就会被删除
- Secret text:需要保存的一个加密的文本串,如钉钉机器人或 Github 的 api token
- Certificate:通过上传证书文件的方式
5. 集成 Maven
-
Jenkins 关联 JDK 和 MAVEN
Jenkins - Global Tool Configuration - JDK,新增 JDK,配置指定 JDK 的 JAVA_HOME
Jenkins - Global Tool Configuration - Maven,新增 Maven,配置指定 MAVEN 的 MAVEN_HOME
-
添加 Jenkins 全局变量
Manage Jenkins - Configure System - Global Properties,添加三个全局变量 JAVA_HOME、M2_HOME、PATH+EXTRA
我们也可以在拉取代码时完成构建,选择 构建 - 增加构建步骤 - Execute Shell,输入:mvn clean package
保存配置后,选择项目,点击构建 Build Now 开始构建项目
查看 linux 的 /var/lib/jenkins/workspace/**目录,会生成一个 target 目录,里面有相应的包生成
Jenkins 项目构建类型
Jenkins 中自动构建项目的类型有很多,常用的有以下三种:
- *风格软件项目(FreeStyle Project)
- Maven 项目(Maven Project)
- 流水线项目(Pipeline Project)
每种类型的构建都可以完成一样的构建过程与结果,只是在操作方式、灵活度等方面有所区别,在实际开发中,可以根据自己的需求和习惯来选择
1. *风格项目构建
一个*风格项目来完成项目的集成过程:拉取代码 - 编译 - 打包 - 部署
-
创建项目
-
配置源码管理,从 gitlab 拉取代码
-
编译打包
构建 - 添加构建步骤 - Executor Shell
echo "开始编译和打包" mvn clean package echo "编译和打包结束"
-
部署,把项目部署到远程的 Tomcat
Jenkins 部署项目到 Tomcat 服务器,需要用到 Tomcat 的用户,所以修改 tomcat 以下配置,添加用户及权限
vi /opt/tomcat/conf/tomcat-users.xml
内容如下:
<tomcat-users> <role rolename="tomcat"/> <role rolename="role1"/> <role rolename="manager-script"/> <role rolename="manager-gui"/> <role rolename="manager-status"/> <role rolename="admin-gui"/> <role rolename="admin-script"/> <user username="tomcat" password="tomcat" roles="manager-gui,managerscript,tomcat,admin-gui,admin-script"/> </tomcat-users>
为了能够刚才配置的用户登录到 Tomcat,还需要修改以下配置
vi /opt/tomcat/webapps/manager/META-INF/context.xml
把下面内容注释
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
重启 Tomcat
/opt/tomcat/bin/shutdown.sh 停止 /opt/tomcat/bin/startup.sh 启动
访问:http://localhost:8080/manager/html ,输入 tomcat 和 tomcat,看到以下页面代表成功
Jenkins 本身无法实现远程部署到 Tomcat 的功能,需要安装 Deploy to container 插件实现
添加 Tomcat 用户凭证,添加构建后操作,选择 Deploy war/ear to a container,部署到容器(远程 tomcat)
改动代码后的持续集成
- 源码修改并提交到 gitlab
- 在 Jenkins 中项目重新构建
- 访问 Tomcat
2. Maven 项目构建
使用 Maven 项目构建需要安装 Maven Integration 插件,拉取代码和远程部署的过程和*风格项目一样,只是构建部分不同。之前是通过 shell 来指定编译后的行为,现在则是在 Build 操作界面输入指定的 pom.xml 文件路径,输入 maven 指令
3. Pipeline 流水线项目构建
3.1 Pipeline 简介
Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作
Pipeline 脚本是由 Groovy 语言实现的,支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
Pipeline 也有两种创建方法:
- 可以直接在 Jenkins 的 Web UI 界面中输入脚本
- 也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中(推荐在 Jenkins 中直接从源代码控制 SCM 中直接载入 Jenkinsfile Pipeline 这种方法)
要使用 Pipeline,需安装 Pipeline 插件,Manage Jenkins - Manage Plugins - 可选插件 – 安装 Pipeline,安装插件后,创建项目的时候多了流水线类型
3.2 Pipeline 语法快速入门
-
Declarative 声明式 Pipeline
流水线 - 选择 Declarative Pipeline - 选择 HelloWorld 模板,生成内容如下:
pipeline { agent any stages { stage('Hello') { steps { echo 'Hello World' } } } }
- stages:代表整个流水线的所有执行阶段,通常 stages 只有一个,里面包含多个 stage
- stage:代表流水线中的某个阶段,可能出现多个,一般分为拉取代码,编译构建,部署等阶段
- steps:代表一个阶段内需要执行的逻辑,steps 里面是 shell 脚本,git 拉取代码,ssh 远程发布等任意内容
编写一个简单的声明式 Pipeline:
pipeline { agent any stages { stage('拉取代码') { steps { echo '拉取代码' } } stage('编译构建') { steps { echo '编译构建' } } stage('项目部署') { steps { echo '项目部署' } } } }
点击构建,可以看到整个构建过程
我们可以在流水线语法里选择片段生成器,快速生成 Pipeline 代码:
-
生成一个 pull stage
选择
checkout from version controller
,拉取代码,选择类型为 git,填写好 git 项目地址,填写拉取分支名字,生成流水线脚本,脚本里就包含了凭证信息 -
生成一个构建 stage
选择
sh:shell script
,输入mvc clean package
,点击生成脚本 -
生成一个部署 stage
选择
deploy
,填写WAR files:targer/*.war
,选择 tomcat 远程,然后填写 tomcat 的地址就可远程部署,可以同时部署多台 tomcat
-
Scripted 脚本式 Pipeline
流水线 - 选择 Scripted Pipeline,编写一个简单的脚本式 Pipeline:
node { def mvnHome stage('拉取代码') { // for display purposes echo '拉取代码' } stage('编译构建') { echo '编译构建' } stage('项目部署') { echo '项目部署' } }
- Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境
- Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念
- Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:
sh 'make'
,就相当于我们平时 shell 终端中执行 make 命令一样
完整代码如下:
pipeline{ agentanystages{ stage('拉取代码'){ steps{ checkout([ $class: 'GitSCM', branches: [ [name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [ ], submoduleCfg: [ ], userRemoteConfigs: [ [ credentialsId: '68f2087f-a034-4d39-a9ff-1f776dd3dfa8', url: 'git@192.168.66.100: itheima_group/web_demo.git' ] ] ]) } }stage('编译构建'){ steps{ shlabel: '', script: 'mvncleanpackage' } }stage('项目部署'){ steps{ deployadapters: [ tomcat8(credentialsId: 'afc43e5e-4a4e-4de6-984fb1d5a254e434', path: '', url: 'http: //192.168.66.102: 8080') ], contextPath: null, war: 'target/*.war' } } } }
-
Pipeline Script from SCM
之前我们都是直接在 Jenkins 的 UI 界面编写 Pipeline 代码,这样不方便脚本维护,建议把 Pipeline 脚本放在项目中,一起进行版本控制
-
在项目根目录建立 Jenkinsfile 文件,编写脚本内容,把文件上传到 Gitlab
-
在项目中引用该文件
-
点击构建,就开始拉取,拉取后拿到 Jenkins 后操作
-
Jenkins 构建触发器
Jenkins 内置了四种构建触发器:
- 远程触发构建
- 其他工程构建后触发(Build after other projects are build)
- 定时构建(Build periodically)
- 轮询SCM(Poll SCM)
1. 远程触发构建
在 Jenkins 工程下点击配置,然后构建触发器,其他系统发送 URL 请求,就可以让 Jenkins 开始构建(触发构建)
触发构建url:http://192.168.66.101:8888/job/web_demo_pipeline/build?token=6666
2. 其他工程构建后触发
该触发器的需求是:当前项目需要前一个项目构建完成后才能触发
-
创建 pre_job 流水线工程,该工程构建完成后触发当前项目
-
配置需要触发的工程
3. 定时构建
选择 Build periodically,输入定时字符串表达式,即可定时构建
下面是一些定时表达式的例子:
每30分钟构建一次:H代表形参 H/30 * * * * 10:02 10:32
每2个小时构建一次: H H/2 * * *
每天的8点,12点,22点,一天构建3次: (多个时间点中间用逗号隔开) 0 8,12,22 * * *
每天中午12点定时构建一次 H 12 * * *
每天下午18点定时构建一次 H 18 * * *
在每个小时的前半个小时内的每10分钟 H(0-29)/10 * * * *
每两小时一次,每个工作日上午9点到下午5点(也许是上午10:38,下午12:38,下午2:38,下午4:38) H H(9-16)/2 * * 1-5
4. 轮询 SCM
轮询 SCM,是指定时扫描本地代码仓库的代码是否有变更,如果代码有变更就触发项目构建
Jenkins 会定时扫描本地整个项目的代码,增大系统的开销,不建议使用轮询 SCM
5. Git hook 自动触发构建
利用 Gitlab 的 webhook 实现代码 push 到仓库,立即触发项目自动构建,需要安装两个插件:Gitlab Hook 和 GitLab
需要把生成的 webhook URL 配置到 Gitlab 中:
- 使用 root 账户登录到后台,点击 Admin Area - Settings - Network,勾选
Allow requests to the local network from web hooks and services
让网络钩子允许请求本地网络 - 点击项目 - Settings - Integrations,在项目添加 webhook
在 Jenkins 中,Manage Jenkins - Configure System,取消勾选 Enable authentication for '/project' end-point GitLab connections
Jenkins 参数化构建
有时候在项目构建的过程中,我们需要根据用户的输入动态传入参数,从而影响整个构建结果,比如:我们希望根据用户传入的参数,部署不同的分支,这时我们可以使用参数化构建
在 Jenkins 添加字符串类型参数
改动 pipeline 流水线代码
点击 Build with Parameters,就用指定参数开始了构建
Jenkins 配置邮箱服务器
安装 Email Extension 插件 template,Jenkins 设置邮箱相关参数:Manage Jenkins - Configure System,
设置 Jenkins 默认邮箱信息
在项目根目录编写 email.html,并把文件推送到 Gitlab,内容如下:
<!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, sansserif">
<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>触发原因: ${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><textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">${BUILD_LOG,
maxLines=100}</textarea>
</td>
</tr>
</table>
</body>
</html>
编写 Jenkinsfile 添加构建后发送邮件的 Pipeline 代码,这个 post 可以到声明式脚本生成器里选择 post,选择对应的 conditions,比如选择永远都执行等等,他和 stage 是分开的
pipeline {
agent any
stages {
stage('拉取代码') {
steps {
checkout([$class: 'GitSCM',
branches: [[name: '*/master']],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: '68f2087f-a034-4d39-a9ff-1f776dd3dfa8',
url: 'git@192.168.66.100:itheima_group/web_demo.git']]])
}
}
stage('编译构建') {
steps {
sh label: '', script: 'mvn clean package'
}
}
stage('项目部署') {
steps {
deploy adapters: [tomcat8(credentialsId: 'afc43e5e-4a4e-4de6-984fb1d5a254e434',
path: '',
url: 'http://192.168.66.102:8080')],
contextPath: null,
war: 'target/*.war'
}
}
}
post { # 主要看这就行
always {
emailext(
subject: '构建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} -${BUILD_STATUS}!',
body: '${FILE,path="email.html"}',
to: 'xxx@qq.com'
)
}
}
}
邮件相关全局参数参考列表:系统设置 - Extended E-mail Notification - Content Token Reference,点击旁边的 ? 号