• 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
    b
    c
    ', description: '文本', name: 'D_TEXT'
          choice choices: 'a
    b
    c
    ', 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

    参考

    人生的主旋律其实是苦难,快乐才是稀缺资源。在困难中寻找快乐,才显得珍贵~
  • 相关阅读:
    UVA 10976 Fractions Again?! 简单枚举题
    UVa 11059 Maximum Product(简单枚举7.1)使用longlong,输出格式%lld
    《Java核心技术卷I》——第5章 继承
    《Java核心技术卷I》——第3章 Java的基本程序设计结构
    windows服务器监控多个tomcat运行状态
    org.apache.jasper.JasperException: javax.el.PropertyNotFoundException: Property [xxx] not readable on type [xxx]
    断点续传
    创建密码带有特殊字符的dblink
    带有空格或tab的字符串的判断
    SQLState: 23000
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/14998644.html
Copyright © 2020-2023  润新知