Loading

Jenkins 学习笔记

本学习笔记参考《Jenkins 2.x实践指南》。

1. Jenkins 简介

Jenkins 是一款自动化的任务执行工具。通常用于持续集成/持续交付领域。

可以通过界面或Jenkinsfile告诉Jenkins执行什么任务,何时执行。理论上,我们可以让它执行任何任务,但是通常只应用于持续集成和持续交付。

持续集成将软件生产过程从手工模式带入流水线模式,软件生产的某个环节都对应流水线上的每个环节。

流水线模式还能帮助我们将知识固化到自动化流水线中,在一定程度上解决了知识被人带走的问题。(手工模式对于整个软件生产流程很少有人全部知道,使用流水线模式整个软件生产的过程都以pipeline as code的形式保留下来,每个人都能看)

2. pipeline介绍

什么是pipeline

  • 学术的讲:软件从代码版本库到用户手中这一过程的自动化表现;
  • 通俗的讲:主要是指代码的自动构建、测试、打包和部署等过程;

Jenkins 1.x只能通过界面手动操作来“描述”部署流水线。Jenkins 2.x终于支持pipeline as code了,可以通过“代码”来描述部署流水线。

  • 更好地版本化:将pipeline提交到软件版本库中进行版本控制。
  • 更好地协作:pipeline的每次修改对所有人都是可见的。除此之外,还可以对pipeline进行代码审查。
  • 更好的重用性:手动操作没法重用,但是代码可以重用。

什么是JenkinsFile

Jenkinsfile就是一个文本文件,也就是部署流水线概念在Jenkins中的表现形式。像Dockerfile之于Docker。所有部署流水线的逻辑都写在Jenkinsfile中。

JK File 定义了流水线的部署逻辑

Jenkins默认不支持pipeline,需要安装pipeline插件。

pipeline语法的支持

pipeline支持以下两种语法:

  • Groovy语法(node为根节点的是脚本式语法);
  • 声明式语法(2.5版本开始支持,pipeline为根节点的是声明式语法,推荐使用)

3. pipeline 语法

Groovy语法

这里Groovy语法不是重点,但是还是需要了解必要的知识。如果想要详细了解,可以参考Groovy教程

// 一般用def定义变量
// 语句末尾不添加分号
def x = "abs"

// Groovy中的方法调用可以省略括号
println x
println "chensongxia"

// 支持命名参数
def m1(String givenName, String familyName) {
    return givenName + " " + familyName
}
// 调用时可以这样
println m1("chen", "songxia")
s1 = m1 familyName = "ru", givenName = "zhao"
println s1

// 支持默认参数
def m2(String name = "hi") {
    return name
}
// 括号不能省略
println m2()
println m2("chen")

// 支持单引号、双引号。双引号支持插值,单引号不支持
println '------支持单引号、双引号。双引号支持插值,单引号不支持 ---------'
println ''
def name = "world";
println "hello ${name}"
println 'hello ${name}'


// 支持三引号。三引号分为三单引号和三双引号。它们都支持换行,区别在于只有三双引号支持插值。
println ''
println '-----支持三引号。三引号分为三单引号和三双引号。它们都支持换行,区别在于只有三双引号支持插值。-----'
println """line one
line two
line three
${name}
"""

println '''line one
line two
line three
${name}
'''

// 支持闭包
println '-------支持闭包---------'
def codeBlock = { println "hello closure" }
// 闭包可以被当成函数直接调用
codeBlock();
// 闭包可以被当成一个参数传递给另外一个方法
def pipeline(closure){
    closure()
}
pipeline(codeBlock)

def stage(String name,closure){
    println name
    closure()
}
stage("jenkins",codeBlock)

pipeline组成

Jenkins pipeline其实就是基于Groovy语言实现的一种DSL(领域特定语言),用于描述整条流水线是如何进行的。流水线的内容包括执行编译、打包、测试、输出测试报告等步骤。

从软件版本控制库到用户手中这一过程可以分成很多阶段,每个阶段只专注处理一件事情,而这件事情又是通过多个步骤来完成的,这就是软件工程的pipeline。Jenkins对这个过程进行抽象,设计出一个基本的pipeline结构。

pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                echo 'Hello World'
            }
        }
    }
}
  • pipeline:代表整条流水线,包含整条流水线的逻辑。
  • stage部分:阶段,代表流水线的阶段。每个阶段都必须有名称。本例中,build就是此阶段的名称。
  • stages部分:流水线中多个stage的容器。stages部分至少包含一个stage。
  • steps部分:代表阶段中的一个或多个具体步骤(step)的容器。steps部分至少包含一个步骤,本例中,echo就是一个步骤。在一个stage中有且只有一个steps。
  • agent部分:指定流水线的执行位置(Jenkins agent)。流水线中的每个阶段都必须在某个地方(物理机、虚拟机或Docker容器)执行,agent部分即指定具体在哪里执行

以上每一个部分(section)都是必需的,少一个,Jenkins都会报错。

Jenkins的很多插件可以被直接拿来当作一个步骤使用。只要安装了这些适配了Jenkins pipeline的插件,就可以使用其提供的pipeline步骤。

Jenkins官方还提供了pipeline步骤参考文档(https://jenkins.io/doc/pipeline/steps/)。

post部分

根据pipeline或阶段的完成状态,post部分分成多种条件块,包括:

• always:不论当前完成状态是什么,都执行。
• changed:只要当前完成状态与上一次完成状态不同就执行。
• fixed:上一次完成状态为失败或不稳定(unstable),当前完成状态为成功时执行。
• regression:上一次完成状态为成功,当前完成状态为失败、不稳定或中止(aborted)时执行。
• aborted:当前执行结果是中止状态时(一般为人为中止)执行。
• failure:当前完成状态为失败时执行。
• success:当前完成状态为成功时执行。
• unstable:当前完成状态为不稳定时执行。
• cleanup:清理条件块。不论当前完成状态是什么,在其他所有条件块执行完成后都执行。post部分可以同时包含多种条件块。

下面给出一个列子:

pipeline支持的指令

基本结构满足不了现实多变的需求。所以,Jenkins pipeline通过各种指令(directive)来丰富自己。指令可以被理解为对Jenkins pipeline基本结构的补充。

Jenkins pipeline支持的指令有:

• environment:用于设置环境变量,可定义在stage或pipeline部分。
• tools:可定义在pipeline或stage部分。它会自动下载并安装我们指定的工具,并将其加入PATH变量中。
• input:定义在stage部分,会暂停pipeline,提示你输入内容。
• options:用于配置Jenkins pipeline本身的选项,比如options {retry(3)}指当pipeline失败时再重试2次。options指令可定义在stage或pipeline部分。
• parallel:并行执行多个step。在pipeline插件1.2版本后,parallel开始支持对多个阶段进行并行执行。
• parameters:与input不同,parameters是执行pipeline前传入的一些参数。
• triggers:用于定义执行pipeline的触发器。
• when:当满足when定义的条件时,阶段才执行。

在使用指令时,需要注意的是每个指令都有自己的“作用域”。如果指令使用的位置不正确,Jenkins将会报错。

配置pipeline本身(option指令)(用到的时候可以来参考这块)

options指令用于配置整个Jenkins pipeline本身的选项。根据具体的选项不同,可以将其放在pipeline块或stage块中。以下例子若没有特别说明,options被放在pipeline块中。

• buildDiscarder:保存最近历史构建记录的数量。当pipeline执行完成后,会在硬盘上保存制品和构建执行日志,如果长时间不清理会占用大量空间,设置此选项后会自动清理。

参考这个文档:https://weread.qq.com/web/reader/12f320007184556612f32b6k1ff325f02181ff1de7742fc

这节比较有用,可以参考下

在声明式pipeline中使用脚本

Jenkins pipeline专门提供了一个script步骤,你能在script步骤中像写代码一样写pipeline逻辑。

pipeline {
    agent any

    stages {
        stage('Hello') {
            steps {
                script{
                    def s = "Hello World"
                    println s
                }
                echo 'Hello World'
            }
        }
    }
}

可以看出,在script块中的其实就是Groovy代码。大多数时候,我们是不需要使用script步骤的。如果在script步骤中写了大量的逻辑,则说明你应该把这些逻辑拆分到不同的阶段,或者放到共享库中。共享库是一种扩展Jenkins pipeline的技术

pipeline内置基础步骤(重要)

Jenkins 的pipeline内置了很多基础步骤,我们可以直接使用。

参考章节:https://weread.qq.com/web/reader/12f320007184556612f32b6k4e73277021a4e732ced3b55

4. 环境变量与构建工具

环境变量

环境变量可以被看作是pipeline与Jenkins交互的媒介。比如,可以在pipeline中通过BUILD_NUMBER变量知道构建任务的当前构建次数。环境变量可以分为Jenkins内置变量和自定义变量。

1. 内置环境变量

使用方式:

小技巧:在调试pipeline时,可以在pipeline的开始阶段加一句:sh 'printenv',将env变量的属性值打印出来。这样可以帮助我们避免不少问题。

不推荐方法三,因为出现变量冲突时,非常难查问题。

Jenkins的控制台也对内置的环境变量做了说明,可以自己打开看下。

image-20210626234914858

2. 自定义环境变量

environment指令可以在pipeline中定义,代表变量作用域为整个pipeline;也可以在stage中定义,代表变量只在该阶段有效。

pipeline {
    agent any
    environment {
        DEVOPS_IP = '10.50.1.50'
    }
    stages {
        stage('Hello') {
            environment {
                NAME = 'Hello World...'
            }
            steps {
                echo "${DEVOPS_IP}"
                echo "${NAME}"
                sh 'printenv'
            }
        }
    }
}

但是这些变量都不是跨pipeline的,比如pipeline a访问不到pipeline b的变量。在pipeline之间共享变量可以通过参数化pipeline来实现。

在实际工作中,还会遇到一个环境变量引用另一个环境变量的情况。在environment中可以这样定义:

image-20210627000611998

在定义变量的时候,为了防止变量冲突,建议将变量加上前缀(自定义的环境变量会覆盖默认的环境变量)。

3. 自定义全局环境变量

env中的变量都是Jenkins内置的,或者是与具体pipeline相关的。有时候,我们需要定义一些全局的跨pipeline的自定义变量。

进入Manage Jenkins→Configure System→Global properties页,勾选“Environment variables”复选框,单击“Add”按钮,在输入框中输入变量名和变量值即可。

image-20210627001302209

自定义全局环境变量会被加入 env 属性列表中,所以,使用自定义全局环境变量与使用Jenkins内置变量的方法无异:${env.g name}。

构建工具

构建每一步都是可重复的,尽量与机器无关。所以,构建工具的安装、设置也应该是自动化的、可重复的。

虽然Jenkins只负责执行构建工具提供的命令,本身没有实现任何构建功能,但是它提供了构建工具的自动安装功能。

1. tool指令

tools指令能帮助我们自动下载并安装所指定的构建工具,并将其加入PATH变量中。这样,我们就可以在sh步骤里直接使用了。但在agent none的情况下不会生效。通过命令sh "printenv",可以看到环境变量的所有值。

tools指令默认支持3种工具:JDK、Maven、Gradle。通过安装插件,tools指令还可以支持更多的工具。

配置maven

进入Manage Jenkins→Global Tool Configuration→Maven页,设置如下图所示。

image-20210627132943803

pipeline {
    agent any
    environment {
        DEVOPS_IP = '10.50.1.50'
    }
    
    tools {
        maven 'maven.3.6.3'
    }
    
    stages {
        stage('Hello') {
            environment {
                NAME = 'Hello World...'
            }
            steps {
                echo "${DEVOPS_IP}"
                echo "${NAME}"
                sh 'printenv'
                sh 'java -version'
                sh 'mvn -v'
            }
        }
    }
}

这样,当执行到tools指令时,Jenkins会自动下载并安装Maven。将mvn命令加入环境变量中,可以使我们在pipeline中直接执行mvn命令。

Config File Provider插件可以设置全局的setting设置。

利用环境变量支持更多的构建工具

如果想让Jenkins支持更多的构建工具,也是同样的做法:在Jenkins agent上安装构建工具,并记录下它的可执行命令的目录,然后在需要使用此命令的Jenkinspipeline的PATH环境变量中加入该可执行命令的目录。

image-20210627140434285

利用tools作用域实现多版本编译

在实际工作中,有时需要对同一份源码使用多个版本的编译器进行编译。tools指令除了支持pipeline作用域,还支持stage作用域。

image-20210627140743985

在打印出来的日志中,会发现每个stage下的JAVA_HOME变量的值都不一样。

5. 代码质量

静态代码分析

使用maven插件在某个stage加入静态代码分析。(静态代码扫描通常被安排在编译之后)

单元测试

性能测试

Jenkins集成SonarQube(有时间将这个环境搭建起来,将分析报告推送到GitLab)

Allure测试报告(美化测试报告,不是重点)

6. 触发 pipeline 执行

自动化是指pipeline按照一定的规则自动执行。而这些规则被称为pipeline触发条件。

对于pipeline触发条件,可以从两个维度来区分:时间触发和事件触发。接下来,我们从这两个维度分别介绍。

时间触发

时间触发是指定义一个时间,时间到了就触发pipeline执行。在Jenkins pipeline中使用trigger指令来定义时间触发。

tigger指令只能被定义在pipeline块下,Jenkins内置支持cron、pollSCM,upstream三种方式。其他方式可以通过插件来实现。

使用场景:一些比较重的任务可以放到凌晨再跑。

image-20210629150639184

事件触发

事件触发就是发生了某个事件就触发pipeline执行。这个事件可以是你能想到的任何事件。比如手动在界面上触发、其他job主动触发、HTTP API Webhook触发等。

常用的有下面几种:

  • 由上游任务触发:upstream(其他job来触发)
  • GitLab通知触发
  • 在pipeline中实现GitLab trigger

将构建状态信息推送到GitLab

Generic Webhook Trigger

安装 Generic Webhook Trigger 插件(下文使用 GWT 简称)后,Jenkins 会暴露一个 API:<JENKINS URL>/generic-webhook-trigger/invoke,即由GWT插件来处理此API的请求。

如何处理呢?GWT插件接收到JSON或XML的HTTP POST请求后,根据我们配置的规则决定触发哪个Jenkins项目。基本原理就这么简单。

7. 多分支构建

在实际项目中,往往需要多分支同时进行开发。如果为每个分支都分别创建一个Jenkins项目,实在有些多余。

不同分支发布不同环境

image-20210711102036257

when指令的用法

when指令允许pipeline根据给定的条件,决定是否执行阶段内的步骤。when指令必须至少包含一个条件。when指令除了支持branch判断条件,还支持多种判断条件。

这块内容非常有用,可以经常参考。

https://weread.qq.com/web/reader/12f320007184556612f32b6k9a132c802349a1158154a83

GitLab trigger对多分支pipeline的支持

image-20210711103410476

Generic Webhook Trigger插件在多分支pipeline场景下的应用

在多分支pipeline场景下,我们希望触发某个分支的构建执行。

8. 参数化pipeline

什么是参数化pipeline

参数化pipeline是指可以通过传参来决定pipeline的行为。

使用parameters指令

parameters {
      string defaultValue: 'none', description: '字符串', name: 'D_ENV', trim: true
      text defaultValue: 'a\nb\nc\n', description: '文本', name: 'D_TEXT'
      choice choices: 'a\nb\nc\n', description: '选一个', name: 'D_CHOICE'
      booleanParam defaultValue: false, description: '布尔值参数', name: 'FLAG'
      password name: 'PASSWORD',defaultValue:'SECRET',description: 'password'
   }

参考这个文章:https://zhuanlan.zhihu.com/p/80622378。

由另外一个pipeline传参数

可以在一个pipeline中“调用”另一个pipeline。在Jenkins pipeline中可以使用build步骤实现此功能。build步骤是pipeline插件的一个组件,所以不需要另外安装插件,可以直接使用。

build步骤其实也是一种触发pipeline执行的方式,它与triggers指令中的upstream方式有两个区别:

(1) build步骤是由上游pipeline使用的,而upstream方式是由下游pipeline使用的。

(2) build步骤是可以带参数的,而upstream方式只是被动触发,并且没有带参数。

image-20210711104746938

使用Conditional BuildStep插件处理复杂的判断逻辑

Conditional BuildStep插件(https://plugins.jenkins.io/conditional-buildstep)可以让我们像使用when指令一样进行条件判断。以下代码就是安装Conditional BuildStep插件后的写法。

image-20210711105208635

使用input步骤

执行input步骤会暂停pipeline,直到用户输入参数。这是一种特殊的参数化pipeline的方法。我们可以利用input步骤实现以下两种场景:

(1)实现简易的审批流程。例如,pipeline暂停在部署前的阶段,由负责人点击确认后,才能部署。

(2)实现手动测试阶段。在pipeline中增加一个手动测试阶段,该阶段中只有一个input步骤,当手动测试通过后,测试人员才可以通过这个input步骤。

image-20210711105414434 image-20210711105438426

input步骤可以与timeout步骤实现超时自动中止pipeline,防止无限等待。以下pipeline一小时后不处理就自动中止。

image-20210711105634260

9. 凭证管理

为什么要凭证管理

在Jenkinsfile或部署脚本中使用明文密码会造成安全隐患。但是为什么还频繁出现明文密码被上传到GitHub上的情况呢

(1)程序员或运维人员不知道如何保护密码。

(2)管理者没有足够重视,否则会给更多的时间让程序员或运维人员想办法隐藏明文密码。

凭证是什么

比如使用SSH登录远程机器时,用户名和密码或SSH key就是凭证。而这些凭证不可能以明文写在Jenkinsfile中。Jenkins凭证管理指的就是对这些凭证进行管理。

为了最大限度地提高安全性,在Jenkins master节点上对凭证进行加密存储(通过Jenkins实例ID加密),只有通过它们的凭证ID才能在pipeline中使用,并且限制了将证书从一个Jenkins实例复制到另一个Jenkins实例的能力。

也因为所有的凭证都被存储在Jenkins master上,所以在Jenkins master上最好不要执行任务,以免被pipeline非法读取出来。

创建凭证

image-20210711111054019

• Kind:选择凭证类型。

• Scope:凭证的作用域。有两种作用域:

​ ◦ Global,全局作用域。如果凭证用于pipeline,则使用此种作用域。

​ ◦ System,如果凭证用于Jenkins本身的系统管理,例如电子邮件身份验证、代理连接等,则使用此种作用域。

• ID:在pipeline使用凭证的唯一标识。

添加凭证后,安装Credentials Binding Plugin插件(https://plugins.jenkins.io/credentials-binding),通过其提供的withCredentials步骤就可以在pipeline中使用凭证了。

常用凭证类型

  • Secret text
  • Username with password
  • Secret file
  • SSH Username with private key

优雅的使用凭证

声明式pipeline提供了credentials helper方法(只能在environment指令中使用)来简化凭证的使用。以下是使用方法。

https://weread.qq.com/web/reader/12f320007184556612f32b6k14b3246024514bfa6bb1534

使用HashiCorp Vault(加强版的凭证管理工具)

在Jenkins日志中隐藏敏感信息

10. 制品管理

常见的制品管理仓库

  • Nexus
  • Artifactory

版本号管理

11.可视化构建及视图

Green Balls插件

这个插件的功能是让构建成功的pipeline编程绿色。

Build Monitor View插件

Build Monitor View插件(https://plugins.jenkins.io/build-monitor-plugin)可以将Jenkins项目以一块“看板”的形式呈现。

视图

使用视图可以对pipeline进行分组管理。

12.自动化部署

部署和发布的区别

部署是指将应用程序放到对应的服务器上。

发布是指用户能访问到新的功能点。

Jenkins集成Ansible实现自动化部署

Ansible采用了与Puppet、Chef不一样的解决方案,不需要在受控机器上安装额外的客户端软件。原因是Ansible使用的是SSH协议与受控机器进行通信的,一般服务器默认有SSH服务。Ansible也因此被称为agentless(去客户端的)。

Puppet和Chef都自己做了一套DSL,而Ansible使用YAML格式作为自己的DSL格式。

13. 通知

邮件通知

钉钉通知

Http请求通知

14. 分布式构建与并行构建

Jenkins的架构

Jenkins采用的是“master+agent”架构(有时也称为“master+slave”架构)。Jenkins master负责提供界面、处理HTTP请求及管理构建环境;构建的执行则由Jenkins agent负责(早期,agent也被称为slave。目前还有一些插件沿用slave的概念)。

基于这样的架构,只需要增加agent就可以轻松支持更多的项目同时执行。这种方式称为Jenkins agent的横向扩容。

对于Jenkins master,存在单节点问题是显而易见的,但是目前还没有很好的解决方案。

image-20210711141901828

在学习Jenkins的过程中,发现各种文档中掺杂着node、executor、agent、slave4个术语,新手很容易被它们弄得一头雾水。它们分别是什么意思呢?

• node:节点,指包含Jenkins环境及有能力执行项目的机器。master和agent都被认为是节点。

• agent:代理,在概念上指的是相对于Jenkins master的一种角色,实际上是指运行在机器或容器中的一个程序,它会连接上Jenkins master,并执行Jenkinsmaster分配给它的任务。

• slave:“傀儡”,与agent表达的是一个东西,只是叫法不同。

• executor:执行器,是真正执行项目的单元。一个执行器可以被理解为一个单独的进程(事实上是线程)。在一个节点上可以运行多个执行器。

增加agent

https://weread.qq.com/web/reader/12f320007184556612f32b6k697324802676974ce5aceab

如何使用agent

agent部分描述的是整个pipeline或在特定阶段执行任务时所在的agent。换句话说,Jenkins master根据此agent部分决定将任务分配到哪个agent上执行。agent部分必须在pipeline块内的顶层定义,而stage块内的定义是可选的。

image-20210711143736693

agent any告诉Jenkins master任何可用的agent都可以执行。

agent部分的定义可以放在阶段中,用于指定该stage执行时的agent。

image-20210711143852693

通过标签指定agent

image-20210711144220503

customWorkspace,自定义工作目录。

不分配具体的agent

如果希望每个stage都运行在指定的agent中,那么pipeline就不需要指定agent了。

image-20210711144653276 image-20210711144713702

在默认情况下,阶段内所有的代码都将在指定的Jenkins agent上执行。when指令提供了一个beforeAgent选项,当它的值为true时,只有符合when条件时才会进入该Jenkins agent。这样就可以避免没有必要的工作空间的分配,也就不需要等待可用的Jenkins agent了。

将任务构建交给Docker

image-20210711145509583

Docker拉取镜像时,默认是从Docker官方中心仓库拉取的。那么如何实现从私有仓库拉取呢?比如在“制品管理”章节中在Nexus上创建的私有仓库。

Docker插件为我们提供了界面操作,具体步骤如下:

进入Manage Jenkins→Configure System页面,找到“Pipeline ModelDefinition”部分

image-20210711145612897

Docker Label:当 pipeline 中的 agent 部分没有指定 label 选项时,就会使用此配置。如docker {image'maven:3-alpine'}。

• Docker registry URL:Docker私有仓库地址。

• Registry credentials:登录Docker私有仓库的凭证。

并行构建

可以用于优化构建速度。

一些名词

  • pipeline as code

参考

posted @ 2021-07-11 15:07  程序员自由之路  阅读(2588)  评论(2编辑  收藏  举报