pipeline笔记之从一个简单的项目构建开始
# 1,前言少叙。
Jenkins pipeline 是很早就出来了的功能,也是很早就了解了的功能,我也曾不止一次打算学一学这个被不少人推荐过的新方式,也听说过一些公司,将几千个项目,全部基于 Jenkinsfile 进行高效便捷的管理,心有向往,之所以始终没有彻底入门,一方面是因为公司现有所有项目都是基于传统的自由风格或者 maven 风格的构建方式,自己比较熟悉也比较得心应手了,一下子换方式有点难以出发,另一方面就是,实在没有遇到过多么好的教程,基本上每次出发都是从入门到放弃的过程,甚为难受,网上的教程,要么是太简单,只介绍了一星半点的,这类文章最没价值,空误时间,要么是太详细,一下子把 Jenkinsfile 语法的所有功能点都讲到了,却从来不曾简单系统地介绍过一个项目,使人迷失在繁多的功能点里,却没有丁点收获。
这次,当我干脆跳出原有的一切窠臼,以一种全新的视角来看 Jenkins pipeline 时,反而有了不少的收获,加之又购买了《Jenkins 2.0 实践指南》一书,给原有积累的大脑,带来不少新的启迪与发散,于是,打算从自己的角度,来记录一下 Jenkins pipeline。
想到自己学习这个知识点一路来走过的弯路,就对网上繁杂的文章感到愤慨,为了能给后来人导引一个比较明确且清晰的路线,我今天特别由浅入深,循序渐进,用个几篇文章,把这里边的门道儿说明清楚。
# 2,从一个简单的项目开始。
有不少的文章仅仅是将官方文档里边的一些介绍搬运了一下,不知道作者本人是否理解,是否学精通了,反正我个人在官方文档里边,也是积累了许多经验值之后,才能够看懂每个知识点的意义。
所以这里不用那种方式介绍,而是先配置一个简单的项目,用到了什么,再对对应的知识点进行讲解。
日常工作中的持续集成,最简单的,莫过于一些前端项目,只需要将开发的代码同步到远程主机定义好的目录即可,因此这里就创建一个简单的文件,模拟这种发布情景,暂不纠结复杂的编译之类的事情,先从简单的构建开始,以学习语法规范为要。
# 3,准备工作。
# 1,主机分布
主机 | 软件 | IP | 版本 |
---|---|---|---|
CentOS 7.3.1611 (Core) | Gitlab | 192.168.3.65 | 12.2.5 |
CentOS 7.3.1611 (Core) | Jenkins | 192.168.3.66 | 2.194 |
# 2,安装软件
安装软件的工作,这里就不再赘述了,可参考如下文章进行配置。
另外两台主机都需要安装如下基础软件:
yum -y install git rsync ntpdate && ntpdate -u cn.pool.ntp.org
# 3,创建项目
为后边实验顺利,这里先在 gitlab 创建一个测试项目,项目地址如下:
http://192.168.3.65/jenkins-learn/hello-world
里边只有一个 readme 文件,用于验证简单发布的一些结果。
# 4,秘钥安排
同样是为了后边实验顺利,这里提前将各处需要安排的秘钥,给安排妥当。
Jenkins 与 gitlab。
可能每个人以及每个公司在使用 Jenkins 的时候,与 gitlab 交互的时候,采用的方式都是不一样的,事实上再多也无非以下三种而已。
- 1,通过 Jenkins 主机秘钥方式,也就是将 Jenkins 主机公钥放到 gitlab 应用中,这种最方便,推荐。
- 2,通过用户名密码,在 Jenkins 处添加用户名密码,如果用户对项目有权限,则可以使用。
- 3,通过 gitlab 里边的 deploy key 进行授权。
我这里就采用了第一种方案,在 Jenkins 创建密钥对,然后将公钥放到管理员账号的配置里边。认证完成之后,在 Jenkins 主机,拉取一下刚刚项目的代码,做一下简单的连通性工作,否则后边在添加了代码 URL 之后会一直认证不过去,就是因为没有初始认证的缘故:
[root@jenkins ~]$git clone git@192.168.3.65:jenkins-learn/hello-world.git # 此处要求有一个认证,输入yes即可。
1
2Jenkins 与应用主机。
这个就比较简单了,这里 Jenkins 分别给两台机器都传了公钥,后续的实验,也就在这种状况下进行。
# 4,配置项目。
# 1,简单配置。
现在直接去 Jenkins 里边,创建一个 pipeline 风格的项目:
进入项目的配置,直接在最下边的流水线处,按如下内容配置:
将项目仓库地址填入,然后脚本路径一般写成默认的 Jenkinsfile,就这么简单的配置,就可以了,直接点击保存即可。
注意:
因为前边已经做过 Jenkins 的公钥放置到 gitlab 里边的操作,因此这里不需要配置任何认证,就不会再报错。
现在需要准备编写 Jenkinsfile 了,事实上在日常流程中,这个文件应该是准备好了之后,再来创建项目的,只不过这里为了便于理解整个流程,特别把顺序做了调整。
现在来到项目的根目录中:
$ ls
Jenkinsfile readme.md
$ cat readme.md
Jenkins pipeline test。
2
3
4
接着是 Jenkinsfile
的内容:
pipeline {
agent any
environment {
git_url = "git@192.168.3.65:jenkins-learn/hello-world.git"
remote_ip = "192.168.3.66"
remote_dir = "/opt/hello"
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('rsync') {
steps {
echo 'rsync'
sh '''
rsync -avz --progress -e 'ssh -p 22' --exclude='Jenkinsfile' --exclude='.git' --delete ${WORKSPACE}/ root@$remote_ip:$remote_dir
'''
}
}
stage('delete') {
steps {
echo '清理工作目录'
cleanWs()
}
}
}
post {
success {
sh "echo 成功了"
}
failure {
sh "echo 失败了"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
如上的参数内容暂时先不讲解,暂时去做一下构建,看看效果。
# 2,手动构建。
暂时先手动点击一下构建按钮,看看效果。
构建日志内容如下:
Started by GitLab push by 李启龙
Obtained Jenkinsfile from git git@192.168.3.65:jenkins-learn/hello-world.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /root/.jenkins/workspace/hello-world
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Declarative: Checkout SCM)
[Pipeline] checkout
No credentials specified
Cloning the remote Git repository
Cloning repository git@192.168.3.65:jenkins-learn/hello-world.git
> git init /root/.jenkins/workspace/hello-world # timeout=10
Fetching upstream changes from git@192.168.3.65:jenkins-learn/hello-world.git
> git --version # timeout=10
> git fetch --tags --progress git@192.168.3.65:jenkins-learn/hello-world.git +refs/heads/*:refs/remotes/origin/*
> git config remote.origin.url git@192.168.3.65:jenkins-learn/hello-world.git # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url git@192.168.3.65:jenkins-learn/hello-world.git # timeout=10
Fetching upstream changes from git@192.168.3.65:jenkins-learn/hello-world.git
> git fetch --tags --progress git@192.168.3.65:jenkins-learn/hello-world.git +refs/heads/*:refs/remotes/origin/*
> git rev-parse remotes/origin/master^{commit} # timeout=10
> git branch -a -v --no-abbrev --contains 1b884f34ab93fe97de3fb5d8311c2249257a9b89 # timeout=10
Checking out Revision 1b884f34ab93fe97de3fb5d8311c2249257a9b89 (origin/master)
> git config core.sparsecheckout # timeout=10
> git checkout -f 1b884f34ab93fe97de3fb5d8311c2249257a9b89
Commit message: "jenkins"
> git rev-list --no-walk a50c119cd98c57bd022c64a0da70a7b55e81714a # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] timeout
Timeout set to expire in 10 分
[Pipeline] {
[Pipeline] timestamps
[Pipeline] {
[Pipeline] stage
[Pipeline] { (rsync)
[Pipeline] echo
18:03:06 rsync
[Pipeline] sh
18:03:07 + rsync -avz --progress -e 'ssh -p 22' --exclude=Jenkinsfile --exclude=.git --delete /root/.jenkins/workspace/hello-world/ root@192.168.3.66:/opt/hello
18:03:07 sending incremental file list
18:03:07 ./
18:03:07 README.md
18:03:07
24 100% 0.00kB/s 0:00:00
24 100% 0.00kB/s 0:00:00 (xfr#1, to-chk=0/2)
18:03:07
18:03:07 sent 164 bytes received 44 bytes 416.00 bytes/sec
18:03:07 total size is 24 speedup is 0.12
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (delete)
[Pipeline] echo
18:03:07 清理工作目录
[Pipeline] cleanWs
18:03:07 [WS-CLEANUP] Deleting project workspace...
18:03:07 [WS-CLEANUP] Deferred wipeout is used...
18:03:07 [WS-CLEANUP] done
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Declarative: Post Actions)
[Pipeline] sh
18:03:08 + echo 成功了
18:03:08 成功了
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // timestamps
[Pipeline] }
[Pipeline] // timeout
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
刚刚的结果,其实是将代码同步到 Jenkins 本机了,那么去看一眼效果是否如我们所想:
[root@jenkins ~]$cd /opt/hello/
[root@jenkins hello]$ls
README.md
[root@jenkins hello]$cat README.md
Jenkins pipeline test。
2
3
4
5
如此以来,一个简单的项目就配置好了,这种将配置 code 化的方式,不仅优雅,节约了大量的配置时间,而且提高了不少的效率。
# 5,配置讲解。
现在进入正式的配置语法讲解环节,基于当前所用的,进行讲解。
再次将文件内容拉过来:
pipeline {
agent any
environment {
git_url = "git@192.168.3.65:jenkins-learn/hello-world.git"
remote_ip = "192.168.3.66"
remote_dir = "/opt/hello"
}
options {
// 表示保留10次构建历史
buildDiscarder(logRotator(numToKeepStr: '10'))
// 不允许同时执行流水线,被用来防止同时访问共享资源等
disableConcurrentBuilds()
// 设置流水线运行的超时时间, 在此之后,Jenkins将中止流水线
timeout(time: 10, unit: 'MINUTES')
// 输出构建的时间信息
timestamps()
}
stages {
stage('rsync') {
steps {
echo 'rsync'
sh '''
rsync -avz --progress -e 'ssh -p 22' --exclude='Jenkinsfile' --exclude='.git' --delete ${WORKSPACE}/ root@$remote_ip:$remote_dir
'''
}
}
stage('delete') {
steps {
echo '清理工作目录'
cleanWs()
}
}
}
post {
success {
sh "echo 成功了"
}
failure {
sh "echo 失败了"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pipeline
用于声明表示流水线的标识,表示这里将采用声明式的语法风格,与之对应的还要另外一种,叫做脚本式,这里不必纠结两个名词的意义,简单说明两种方式的意思就是:
声明式
:在 Jenkinsfile 固定的关键字之内,所采用的语法风格大多与 shell 类似,这种风格更加符合日常的阅读习惯,也更简单,以后我都将采用这种方式进行介绍以及深入。脚本式
:并不是我们脑海中理解的 shell script,而是一种基于 Groovy 语言的语法风格,对于并不熟悉开发的运维同学来说,这种方式学习成本以及普及性都有较大的局限,因此并不推荐这种方式。
我想,在我一路学习的过程中,也遇到不少文章是基于脚本式的,这类使用者原有的身份大多是 Java 开发者,因此书写起这种代码仿佛更加得心应手一些,但是我以前的多次放弃,与碰上这种风格的 Jenkinsfile 不无关系。
anget
此关键字用于表示当前流水线将要执行的位置,当我们的 Jenkins 是主从那种集群的时候,可以通过 agent 进行指定,同时也可以基于 docker 容器的构建,后边会详细介绍,这里的 any 表示任一客户端,因为当前 Jenkins 是单机的,因此就在当前主机执行。environment
用于设置环境变量,以便于代码复用的时候,更加清晰明了的简化工作内容,只要项目是类似的,那么直接在environment
区域进行配置即可,而无需穿梭到复杂的内容里更改配置。需要注意的一点是,变量的声明可以在 pipeline 以及 stage 区域当中,与大多数语言一样,不同的区域作用域也是不一样的。options
用来配置 Jenkins 应用自身的一些配置项,这个地方简单列举了几个,后边在详解参数配置的文章里边,会详细介绍。stages
此关键字用于表示流水线各个步骤的声明,其下一层级是 stage,stages 部分至少包含一个 stage。stage
表示实际构建的阶段,每个阶段做不同的事情,可按如上方式定义阶段的名称。steps
标识阶段之中具体的构建步骤,至少包含一个步骤,在 stage 中有且只能有一个 steps。post
用于定义在整个流水线执行结果的情况,通常可配合通知进行对项目构建状态的告知。
目前,这个项目所用到的语法,就是这些,一开始学习的时候,实在不必过于纠结每一个语法的原理或者深意,只需大致了解,然后专注于自己所想要的,只要完成自己所想的,就是最好的。
比如基于上边只有 rsync 单步骤情景,往 Java 项目上套,也是很简单的,只需要将日常构建的几个步骤,用一个一个 stage 来表示,这样,一个简单好用的流水线就完成了,在完成基础功能构建之后,再去深入各个参数,进行比较花哨的配置应用。
下一篇文章,就将详细介绍各个语法的规范以及用法,从而丰富我们的日常构建生活。