网站首页 > 技术文章 正文
本学习笔记参考《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的控制台也对内置的环境变量做了说明,可以自己打开看下。
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中可以这样定义:
在定义变量的时候,为了防止变量冲突,建议将变量加上前缀(自定义的环境变量会覆盖默认的环境变量)。
3. 自定义全局环境变量
env中的变量都是Jenkins内置的,或者是与具体pipeline相关的。有时候,我们需要定义一些全局的跨pipeline的自定义变量。
进入Manage Jenkins→Configure System→Global properties页,勾选“Environment variables”复选框,单击“Add”按钮,在输入框中输入变量名和变量值即可。
自定义全局环境变量会被加入 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页,设置如下图所示。
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环境变量中加入该可执行命令的目录。
利用tools作用域实现多版本编译#
在实际工作中,有时需要对同一份源码使用多个版本的编译器进行编译。tools指令除了支持pipeline作用域,还支持stage作用域。
在打印出来的日志中,会发现每个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三种方式。其他方式可以通过插件来实现。
使用场景:一些比较重的任务可以放到凌晨再跑。
事件触发#
事件触发就是发生了某个事件就触发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项目,实在有些多余。
不同分支发布不同环境#
when指令的用法#
when指令允许pipeline根据给定的条件,决定是否执行阶段内的步骤。when指令必须至少包含一个条件。when指令除了支持branch判断条件,还支持多种判断条件。
这块内容非常有用,可以经常参考。
https://weread.qq.com/web/reader/12f320007184556612f32b6k9a132c802349a1158154a83
GitLab trigger对多分支pipeline的支持#
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方式只是被动触发,并且没有带参数。
使用Conditional BuildStep插件处理复杂的判断逻辑#
Conditional BuildStep插件(https://plugins.jenkins.io/conditional-buildstep)可以让我们像使用when指令一样进行条件判断。以下代码就是安装Conditional BuildStep插件后的写法。
使用input步骤#
执行input步骤会暂停pipeline,直到用户输入参数。这是一种特殊的参数化pipeline的方法。我们可以利用input步骤实现以下两种场景:
(1)实现简易的审批流程。例如,pipeline暂停在部署前的阶段,由负责人点击确认后,才能部署。
(2)实现手动测试阶段。在pipeline中增加一个手动测试阶段,该阶段中只有一个input步骤,当手动测试通过后,测试人员才可以通过这个input步骤。
input步骤可以与timeout步骤实现超时自动中止pipeline,防止无限等待。以下pipeline一小时后不处理就自动中止。
9. 凭证管理#
为什么要凭证管理#
在Jenkinsfile或部署脚本中使用明文密码会造成安全隐患。但是为什么还频繁出现明文密码被上传到GitHub上的情况呢
(1)程序员或运维人员不知道如何保护密码。
(2)管理者没有足够重视,否则会给更多的时间让程序员或运维人员想办法隐藏明文密码。
凭证是什么#
比如使用SSH登录远程机器时,用户名和密码或SSH key就是凭证。而这些凭证不可能以明文写在Jenkinsfile中。Jenkins凭证管理指的就是对这些凭证进行管理。
为了最大限度地提高安全性,在Jenkins master节点上对凭证进行加密存储(通过Jenkins实例ID加密),只有通过它们的凭证ID才能在pipeline中使用,并且限制了将证书从一个Jenkins实例复制到另一个Jenkins实例的能力。
也因为所有的凭证都被存储在Jenkins master上,所以在Jenkins master上最好不要执行任务,以免被pipeline非法读取出来。
创建凭证#
? 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,存在单节点问题是显而易见的,但是目前还没有很好的解决方案。
在学习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块内的定义是可选的。
agent any告诉Jenkins master任何可用的agent都可以执行。
agent部分的定义可以放在阶段中,用于指定该stage执行时的agent。
通过标签指定agent
customWorkspace,自定义工作目录。
不分配具体的agent
如果希望每个stage都运行在指定的agent中,那么pipeline就不需要指定agent了。
在默认情况下,阶段内所有的代码都将在指定的Jenkins agent上执行。when指令提供了一个beforeAgent选项,当它的值为true时,只有符合when条件时才会进入该Jenkins agent。这样就可以避免没有必要的工作空间的分配,也就不需要等待可用的Jenkins agent了。
将任务构建交给Docker#
Docker拉取镜像时,默认是从Docker官方中心仓库拉取的。那么如何实现从私有仓库拉取呢?比如在“制品管理”章节中在Nexus上创建的私有仓库。
Docker插件为我们提供了界面操作,具体步骤如下:
进入Manage Jenkins→Configure System页面,找到“Pipeline ModelDefinition”部分
Docker Label:当 pipeline 中的 agent 部分没有指定 label 选项时,就会使用此配置。如docker {image'maven:3-alpine'}。
? Docker registry URL:Docker私有仓库地址。
? Registry credentials:登录Docker私有仓库的凭证。
并行构建#
可以用于优化构建速度。
一些名词#
- pipeline as code
参考#
- jenkins官网:https://www.jenkins.io/
- 《Jenkins 2.x实践指南》:https://weread.qq.com/web/reader/12f320007184556612f32b6kc81322c012c81e728d9d180
- 《Jenkins权威指南》:https://weread.qq.com/web/reader/418326a07184553c4184bcekc81322c012c81e728d9d180
- 最佳实践:https://www.cnblogs.com/lasdaybg/p/10175152.html
- 最佳实践:https://blog.didispace.com/jenkins-pipeline-top-10-action/
- 上一篇: 重启了下 Jenkins,踩到了一个深埋多年的坑
- 下一篇: Jenkins的一些技巧
猜你喜欢
- 2024-11-21 Docker搭建Jenkins持续集成环境构建springcloud项目
- 2024-11-21 使用Jenkins完成项目持续集成和部署
- 2024-11-21 Jenkins的一些技巧
- 2024-11-21 重启了下 Jenkins,踩到了一个深埋多年的坑
- 2024-11-21 详解Jenkins 实现Gitlab事件自动触发Jenkins构建及钉钉消息推送
- 2024-11-21 Jenkins 安装部署教程
- 2024-11-21 Docker+Jenkins+Nginx+Spring Boot 自动化部署项目
- 2024-11-21 从零搭建一个基于 ELK 的日志、指标收集与监控系统
- 2024-11-21 手把手教你用 Jenkins 自动部署 SpringBoot
- 2024-11-21 远程触发Jenkins的Pipeline任务
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)