0%

使用Jenkins的Pipeline发布代码至远程服务器

通常我们软件开发流程大概是下面所示:

本地开发->本地自测->提交代码->编译发布->测试人员测试->提交至生产

在发布阶段,

  1. 有的项目是采用版本控制工具在Linux服务器上拉取对应分支的最新代码
  2. 有的是在本地对比Git版本的差异,生成差异的文件,打包上传至服务器,进行覆盖原来的代码文件完成发布

上面2种方法是我工作中遇到的,下面我们来学习一种新的发布方式:Jenkins

Jenkins可以帮你在写完代码后,一键完成开发过程中的一系列工作

特别是在开发阶段,配合WebHook可以非常省心的完成代码发布工作,开发者只需要提交代码,就会触发Jenkins发布任务的执行,从而将最新代码部署到服务器上

什么是Jenkins?

Jenkins是一个Java开放的开源程序,所以,需要提前安装Java JDK环境,能支持安装到windows,mac,linux平台,主要是一个管理工具

为什么要使用Jenkins?

我们用它,主要是项目上的持续集成和持续交付。持续集成对应英文(Continuous Integration),有时候简称CI,持续交付对应英文(Continuous Delivery),简称CD,以后,听到了CI和CD,就明白了什么意思。下面这张图,是Jenkins在实际项目运用上的一个经典的流程图

image-20210322152752226

##安装Jenkins

安装的方式很多,我这里学习Jenkins采用的是Docker创建容器的方式运行

下载Jenkins

docker pull jenkins

下载完成,查看下

1
2
3
☁  ~  docker image ls
REPOSITORY TAG IMAGE ID
jenkins/jenkins latest 9b74eda1c268

创建映射目录

这个目录根据个人需求,可以进行重新指定,我指定的是:/Users/zhimma/jenkins

mkdir /Users/zhimma/jenkins

创建容器

docker run -d -p 49001:8080 -v $PWD/jenkins:/var/jenkins_home -t jenkins/jenkins

我映射了容器的端口8080到主机上的端口49001, 第一个数字代表主机上的端口,而最后一个代表容器的端口

运行后,Docker会帮我们创建一个Jenkins的运行环境的容器,使用docker ps 查看容器启动情况

1
2
3
☁  ~  docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
782c2fb5ef74 jenkins/jenkins "/sbin/tini -- /usr/…" 3 days ago Up 3 days 50000/tcp, 0.0.0.0:49001->8080/tcp trusting_burnell

到这,我们的Jenkins环境搭建完成,下面我们做一些初始化工作

初始化Jenkins

解锁Jenkins

在浏览器中输入localhost:49001,进入web页面,第一次需要先解锁Jenkins

进入/Users/zhimma/jenkins/secrets目录,制initialAdminPassword文件的内容就是首次解锁的密码

安装推荐插件

设置管理员

安装Jenkins插件

我们目前使用GitLab管理代码,所以我们先安装下面几个插件

  • GitLab Plugin
  • Gitlab Hook Plugin
  • AnsiColor(可选)这个插件可以让Jenkins的控制台输出的log带有颜色(就和linux控制台那样)

安装插件

配置SSH

本机生成SSH:ssh-keygen -t rsa -C "Your email",最终生成id_rsa和id_rsa.pub(公钥)

Gitlab上添加公钥:复制id_rsa.pub里面的公钥添加到Gitlab

Jenkins上配置密钥到SSH:复制id_rsa里面的公钥添加到Jenkins(private key选项)

开始Pipeline

在Jenkins中,把每一段管道比作是不同的Job,我们提到Jenkins的工作流程,build-deploy-test-release,每个流程之间我们都可以用Pipeline来连接,大致如下效果图。

SCM:软件配置管理工具

创建Pipeline风格的项目

实现Pipeline-Web UI方式

如下图所示:

实现Pipeline-Jenkinsfile方式

上面通过Web UI方式只适用于非常简单的任务,而大型复杂的任务最好采用Jenkinsfile方式并纳入SCM管理。 这次我选择从SCM中的Jenkinsfile来定义管道。

我们需要在自己的项目根目录创建Jenkinsfile文件,在里面编写具体的发布流程代码。

使用Jenkinsfile

接下来详细介绍一下怎样编写Jenkinsfile来完成各种复杂的任务。

Pipeline支持两种形式,一种是Declarative管道,一个是Scripted管道。

一个Jenkinsfile就是一个文本文件,里面定义了Jenkins Pipeline。 将这个文本文件放到项目的根目录下面,纳入版本系统。

####Declarative风格类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pipeline {
agent any

stages {
stage('Build') {
steps {
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying..'
}
}
}
}

上面是一个Declarative类型的Pipeline,目前实际开发基本采用这种方式;

  • 第一行是小写的pipeline,然后一对大括{},大括号里面就是代码块,用来和别的代码块隔离出来,pipeline是一个语法标识符,也叫关键字,如果是Declarative类型,一定是pipeline {}这样起头的;如果是脚本文件,pipeline不要求一定是第一行代码。也就是说pipeline前面可以有其他代码,例如导入语句,和其他功能代码。pipeline是一个执行pipeline代码的入口,jenkins可以根据这个入门开始执行里面不同stage
  • 第二行agent any,agent是一个语法关键字,any是一个option类型,agent是代理的意思,这个和选择用jenkins平台上那一台机器去执行任务构建有关
  • 第三行stages{}, stages是多个stage的意思,也就是说一个stages可以包含多个stage,从上面代码结果你也可以看出来。上面写了三个stage,根据你任务需要,你可以写十多个都可以
  • 第四行stage(‘Build’) {}, 这个就是具体定义一个stage,一般一个stage就是指完成一个业务场景。Build是认为给这个任务取一个名字。
  • 第五行steps{},字面意思就是很多个步骤的意思。这里提一下,看到了steps,当然还有step这个指令。一般来说,一个steps{}里面就写几行代码,或者一个try catch语句。
post

post section 定义了管道执行结束后要进行的操作。支持在里面定义很多Conditions块: always, changed, failure, successunstable。 这些条件块会根据不同的返回结果来执行不同的逻辑。

  • always:不管返回什么状态都会执行
  • changed:如果当前管道返回值和上一次已经完成的管道返回值不同时候执行
  • failure:当前管道返回状态值为”failed”时候执行,在Web UI界面上面是红色的标志
  • success:当前管道返回状态值为”success”时候执行,在Web UI界面上面是绿色的标志
  • unstable:当前管道返回状态值为”unstable”时候执行,通常因为测试失败,代码不合法引起的。在Web UI界面上面是黄色的标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Declarative //
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
post { ①
always { ②
echo 'I will always say Hello again!'
}
}
}
stages

由一个或多个stage指令组成,stages块也是核心逻辑的部分。 我们建议对于每个独立的交付部分(比如Build,Test,Deploy)都应该至少定义一个stage指令。比如:

1
2
3
4
5
6
7
8
9
10
11
// Declarative //
pipeline {
agent any
stages { ①
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
steps

stage中定义一系列的step来执行命令。

1
2
3
4
5
6
7
8
9
10
11
// Declarative //
pipeline {
agent any
stages {
stage('Example') {
steps { ①
echo 'Hello World'
}
}
}
}
agent

agent指令指定整个管道或某个特定的stage的执行环境。它的参数可用使用:

  1. any - 任意一个可用的agent
  2. none - 如果放在pipeline顶层,那么每一个stage都需要定义自己的agent指令
  3. label - 在jenkins环境中指定标签的agent上面执行,比如agent { label 'my-defined-label' }
  4. node - agent { node { label 'labelName' } } 和 label一样,但是可用定义更多可选项
  5. docker - 指定在docker容器中运行
  6. dockerfile - 使用源码根目录下面的Dockerfile构建容器来运行
environment

environment定义键值对的环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Declarative //
pipeline {
agent any
environment { ①
CC = 'clang'
}
stages {
stage('Example') {
environment { ②
AN_ACCESS_KEY = credentials('my-prefined-secret-text') ③
}
steps {
sh 'printenv'
}
}
}
}
options

还能定义一些管道特定的选项,介绍几个常用的:

  • skipDefaultCheckout - 在agent指令中忽略源码checkout这一步骤。
  • timeout - 超时设置options { timeout(time: 1, unit: 'HOURS') }
  • retry - 直到成功的重试次数options { retry(3) }
  • timestamps - 控制台输出前面加时间戳options { timestamps() }
parameters

参数指令,触发这个管道需要用户指定的参数,然后在step中通过params对象访问这些参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Declarative //
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
}
}
}
}
triggers

触发器指令定义了这个管道何时该执行,一般我们会将管道和GitHub、GitLab、BitBucket关联, 然后使用它们的webhooks来触发,就不需要这个指令了。如果不适用webhooks,就可以定义两种cronpollSCM

  • cron - linux的cron格式triggers { cron('H 4/* 0 0 1-5') }
  • pollSCM - jenkins的poll scm语法,比如triggers { pollSCM('H 4/* 0 0 1-5') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Declarative //
pipeline {
agent any
triggers {
cron('H 4/* 0 0 1-5')
}
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
stage

stage指令定义在stages块中,里面必须至少包含一个steps指令,一个可选的agent指令,以及其他stage相关指令。

1
2
3
4
5
6
7
8
9
10
11
// Declarative //
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
}
tools

定义自动安装并自动放入PATH里面的工具集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Declarative //
pipeline {
agent any
tools {
maven 'apache-maven-3.0.1'
}
stages {
stage('Example') {
steps {
sh 'mvn --version'
}
}
}
}

注:① 工具名称必须预先在Jenkins中配置好了 → Global Tool Configuration.

内置条件
  • branch - 分支匹配才执行 when { branch 'master' }
  • environment - 环境变量匹配才执行 when { environment name: 'DEPLOY_TO', value: 'production' }
  • expression - groovy表达式为真才执行 expression { return params.DEBUG_BUILD } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Declarative //
pipeline {
agent any
stages {
stage('Example Build') {
steps {
echo 'Hello World'
}
}
stage('Example Deploy') {
when {
branch 'production'
}
echo 'Deploying'
}
}
}
Steps

这里就是实实在在的执行步骤了,每个步骤step都具体干些什么东西, 前面的SectionsDirectives算控制逻辑和环境准备,这里的就是真实执行步骤。

这部分内容最多不可能全部讲完,官方Step指南 包含所有的东西。

Declared PipelineScripted Pipeline都能使用这些step,除了下面这个特殊的script

一个特殊的step就是script,它可以让你在声明管道中执行脚本,使用groovy语法,这个非常有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Declarative //
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
script {
def browsers = ['chrome', 'firefox']
for (int i = 0; i < browsers.size(); ++i) {
echo "Testing the ${browsers[i]} browser"
}
}
script {
// 一个优雅的退出pipeline的方法,这里可执行任意逻辑
if( $VALUE1 == $VALUE2 ) {
currentBuild.result = 'SUCCESS'
return
}
}
}
}
}
}

Scripted风格类型

1
2
3
4
5
6
7
8
9
10
11
node {  
stage('Build') {
//
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}

这个代码,有两点和上面不同。

第一个是Scripted模式是node{}开头,并没有pipeline{}直观。

第二个要指出的是,scripted模式下没有stages这个关键字或者指令,只有stage。上面其实可以node(‘Node name’) {}来开头,Node name就是从节点或master节点的名称。

Scripted Pipeline没那么多东西,就是定义一个node, 里面多个stage,里面就是使用Groovy语法执行各个step了,非常简单和清晰,也非常灵活。

环境变量

Jenkins定了很多内置的环境变量,可在文档localhost:49001/pipeline-syntax/globals#env找到, 通过env直接使用它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pipeline {
agent any

stages {
stage('Build') {
steps {
// 测试环境变量
echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying..'
}
}
}
}

在项目根目录修改Jenkinsfile后,提交到服务器,点击立即构建,查看Console Output输出系统的环境变量

参考引用

https://blog.csdn.net/u011541946/article/details/83152494

https://blog.csdn.net/u011541946/article/category/8223796/2?

https://www.xncoding.com/2017/03/22/fullstack/jenkins02.html