Jenkins 学习笔记

198 阅读12分钟

持续集成

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 为例,安装步骤如下:

  1. 安装相关依赖

    yum -y install policycoreutils openssh-server openssh-clients postfix

  2. 启动 ssh 服务 & 设置为开机启动

    systemctl enable sshd && sudo systemctl start sshd

  3. 设置 postfix 开机自启,并启动,postfix 支持 gitlab 发信功能

    systemctl enable postfix && systemctl start postfix

  4. 开放 ssh 以及 http 服务,然后重新加载防火墙列表

    firewall-cmd --add-service=ssh --permanent firewall-cmd --add-service=http --permanent firewall-cmd --reload

    如果关闭防火墙就不需要做以上配置

  5. 下载 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

  6. 修改 gitlab 配置

    vi /etc/gitlab/gitlab.rb

    修改 gitlab 访问地址和端口,默认为 80,我们改为 82

    external_urlhttp://192.168.66.100:82’
    nginx[‘listen_port’] = 82
    
  7. 重载配置及启动

    gitlab gitlab-ctl reconfigure gitlab-ctl restart

  8. 把端口添加到防火墙

    firewall-cmd --zone=public --add-port=82/tcp --permanent firewall-cmd --reload

Jenkins

Jenkins 是一款流行的开源持续集成(Continuous Integration)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能

1. Jenkins 安装

  1. 获取 Jenkins 安装包,下载页面:jenkins.io/zh/download…

    进行安装:rpm -ivh jenkins-2.190.3-1.1.noarch.rpm

  2. 修改 Jenkins 配置

    vi /etc/syscofig/jenkins

    修改内容如下:

    JENKINS_USER="root"
    JENKINS_PORT="8888"
    
  3. 启动 Jenkins

    systemctl start jenkins

  4. 打开浏览器访问 http://localhost:8888

  5. 获取并输入 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

  1. Jenkins 关联 JDK 和 MAVEN

    Jenkins - Global Tool Configuration - JDK,新增 JDK,配置指定 JDK 的 JAVA_HOME

    Jenkins - Global Tool Configuration - Maven,新增 Maven,配置指定 MAVEN 的 MAVEN_HOME

  2. 添加 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. 自由风格项目构建

一个自由风格项目来完成项目的集成过程:拉取代码 - 编译 - 打包 - 部署

  1. 创建项目

  2. 配置源码管理,从 gitlab 拉取代码

  3. 编译打包

    构建 - 添加构建步骤 - Executor Shell

    echo "开始编译和打包"
    mvn clean package
    echo "编译和打包结束"
    
  4. 部署,把项目部署到远程的 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)

改动代码后的持续集成

  1. 源码修改并提交到 gitlab
  2. 在 Jenkins 中项目重新构建
  3. 访问 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 语法快速入门
  1. 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

  2. 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'
                }
            }
        }
    }
    
  3. Pipeline Script from SCM

    之前我们都是直接在 Jenkins 的 UI 界面编写 Pipeline 代码,这样不方便脚本维护,建议把 Pipeline 脚本放在项目中,一起进行版本控制

    1. 在项目根目录建立 Jenkinsfile 文件,编写脚本内容,把文件上传到 Gitlab

    2. 在项目中引用该文件

    3. 点击构建,就开始拉取,拉取后拿到 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. 其他工程构建后触发

该触发器的需求是:当前项目需要前一个项目构建完成后才能触发

  1. 创建 pre_job 流水线工程,该工程构建完成后触发当前项目

  2. 配置需要触发的工程

3. 定时构建

选择 Build periodically,输入定时字符串表达式,即可定时构建

下面是一些定时表达式的例子:

30分钟构建一次:H代表形参 H/30 * * * * 10:02 10:322个小时构建一次: 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 中:

  1. 使用 root 账户登录到后台,点击 Admin Area - Settings - Network,勾选 Allow requests to the local network from web hooks and services 让网络钩子允许请求本地网络
  2. 点击项目 - 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>项目名称&nbsp;&nbsp;${PROJECT_NAME}</li>
                        <li>构建编号&nbsp;&nbsp;第${BUILD_NUMBER}次构建</li>
                        <li>触发原因:&nbsp;${CAUSE}</li>
                        <li>构建日志:&nbsp;<a
                                          href="${BUILD_URL}console">${BUILD_URL}console</a></li>
                        <li>构建&nbsp;&nbsp;Url&nbsp;&nbsp;<a
                                                             href="${BUILD_URL}">${BUILD_URL}</a></li>
                        <li>工作目录&nbsp;&nbsp;<a
                                                href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
                        <li>项目&nbsp;&nbsp;Url&nbsp;&nbsp;<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="&nbsp;&nbsp;&nbsp;&nbsp;%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,点击旁边的 ? 号