Jenkins-pipeline讲解和使用

什么是Jenkins-Pipeline

Pipeline是一套运行在Jenkins上的工作流框架,2.X版本Jenkins的核心功能,主要是将一个大的工作流分拆成多个独立的功能模块,实现单个任务难以完成的复杂流程编排和可视化。
Jenkins Pipeline也是实现CICD As file的一个重要工具,将Pipeline编写成Jenkinsfile与业务代码一起存放。

Pipeline支持两种语法:
1、声明式语法

Jenkins新加入的语法规则在Jenkinsfile固定的关键字之内,所采用的语法风格大多与shell类似,这种风格更加符合日常的阅读习惯,也更简单,以后我都将采用这种方式进行介绍以及深入。

2、脚本式语法
不是shell脚本形式,而是基于Groovy语言的语法风格,学习成本相对较高

建议直接使用声明式语法清晰简单明了,合适大部分人入门

Pipeline和FreeStyle对比

灵活方式 显示形式
FreeStyle 图形化操作,合适入门操作,后期流程多后,不易于快速快速构建 只有统一日志展示,没有完整阶段流程信息展示
Pipeline 结构化代码语法,易于阅读和管理,可以实现CICD as Code 阶段流程信息展示清晰,每个阶段构建时间和对应的构建日志清晰可读

Jenkins-Pipline语法介绍


图片来源:https://wiki.eryajf.net/pages/3298.html#_1-%E6%A1%86%E6%9E%B6%E4%BB%8B%E7%BB%8D%E3%80%82

  • Aagent: 一个 Aagent 就是一个 Jenkins执行 Step 的具体运行环境。
  • Stage: 表示Pipeline的一个阶段,如clone code阶段,Build阶段。一个Pipeline中至少需要一个Stage。
  • Step: 表示实际的执行步骤,小到执行一个 Shell 脚本,大到构建一个 Docker 镜像,由各类 Jenkins Plugin 提供,当插件扩展Pipeline DSL 时,通常意味着插件已经实现了一个新的步骤,在Stage下有且只能有一个step。

environment

环境变量,可以定义在全局变量或者步骤中的局部变量,取决于所定义位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
pipeline {
agent any
environment {
DISABLE_AUTH = 'true'
}
stages {
stage(“Build”) {
steps {
echo env.DISABLE_AUTH
}
}
}
}

运行结果,会输出对应的环境变量。

options

选项,定义流水线运行时的配置选项。如历史构建记录数量保留,超时时间等操作。以下例子定义重试次数.

1
2
3
4
5
6
7
8
9
10
11
12
13
pipeline {
agent any
options {
retry(3)
}
stages {
stage('Example') {
steps {
sh "dwdwe"
}
}
}
}

编写一个无法执行的命令,可以通过Console Output看见会Retrying 3次才停止。

parameters

参数,为流水线运行时设置相关的参数,不需要在UI界面上额外定义。
出现在Pipeline块内,并且只有一次。
常用参数:
string:字符串类型的参数,例如: parameters { string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '') }
文本: 一个text参数,可以包含多行,例如: parameters { text(name: 'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '') }
booleanParam: 一个布尔参数,例如: parameters { booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: '') }
choice: 选择参数,例如: parameters { choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '') }

password: 在 Jenkins 参数化构建 UI 提供一个暗文密码输入框,所有需要脱敏的信息,都可以通过这个参数来配置。
parameters { password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'A secret password') }

注意:
这种声明定义之后,需要手动构建一次,然后才会自动落位到配置好的参数化构建中了。
例子:

1
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
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
echo "Biography: ${params.BIOGRAPHY}"
echo "Toggle: ${params.TOGGLE}"
echo "Choice: ${params.CHOICE}"
echo "Password: ${params.PASSWORD}"
}
}
}
}

在执行Build时会多出这些选项

post

运行后处理,当流水线完成后根据配置的条件做一些动作,如:构建失败后邮件通知。
条件:
always:无论怎么样总是执行。
changed: 当前Pipeline状态与先前不一致情况下执行。
failuer: 失败情况下。
success: 成功情况下。
unstable: 不稳定情况下,Pipeline状态标识为黄色。
aborted: Pipeline中止情况下。
cleanup: 无论怎么样,执行目录清理

例如:
无论怎么样,将执行情况邮件发送。

1
2
3
4
5
6
7
8
post{
always{
mail to : ‘team@example.com’,
subject:”Pipeline statue:${currentBuild.fullDisplayName}”,
body:”The execution result${env.Build_url}”
}
}

定义三种执行状态,此时shell命令无法执行,输出always和success输出信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pipeline {
agent any
stages {
stage('Example') {
steps {
sh 'dwd'
}
}
}
post {
always {
echo 'already exec'
}
success {
echo 'exec success '
}
failure {
echo 'exec failure'
}
}
}

tool

构建工具,获取通过自动安装或手动安装工具的环境变量,支持maven、jdk、gradle,工具的名称必须预先在Jenkins的系统设置->全局工具配置中定义。
例如在Jenkins—>Global Tool Configuration中添加工具对应的环境变量,然后在项目中引用。

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

input

交互输入,在流水线执行各个阶段的时候,由人工确认是否继续执行。

1
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
pipeline{
agent any
environment{
approvalMap = ''
}
stages {
stage('pre deploy'){
steps{
script{
approvalMap = input (
message: '发布到哪个环境?',
ok: '确定',
parameters:[
choice(name: 'ENV',choices: 'test\npre\nprod',description: '发布到什么环境?'),
string(name: 'username',defaultValue: '',description: '输入用户名')
],
submitter: 'admin',
)
}
}
}
stage('deploy'){
steps{
echo "操作者是 ${approvalMap['username']}"
echo "发布到什么环境 ${approvalMap['ENV']}"
}
}
}
}

点击构建,程序将会在input的步骤停住,等待用户进行相应输入和选择。
message :必填。页面展示信息

ok:input表单上“ok”按钮的可选文本。

submitter:允许提交此input选项的用户或外部组名列表,用逗号分隔。默认允许任何用户。

例子结果如图所示:

when

条件判断,允许流水线根据给定的条件决定是否应该执行阶段,when 指令必须包含至少一个条件.
内置条件
branch:分支匹配,如when { branch 'release-v2.5' }
environment:环境变量匹配如when { environment name: 'DEPLOY_TO', value: 'production' }

not:当嵌套条件为false时执行stage。必须包含至少一个条件例如:when { not { branch 'master' } }
allOf:当所有嵌套条件都为真时,执行舞台。必须包含至少一个条件。例如:when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }
anyOf: 当至少一个嵌套条件为真时执行。必须至少包含一个条件。例如:when { anyOf { branch 'master'; branch 'staging' } }

以上例子支持通配符配置,如when { branch 'release-v2.*' }
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pipeline {
agent any
stages {
stage('Example') {
steps {
git branch: 'main', url: 'https://gitee.com/wanshaoyuan/spring-petclinic.git'
}
}
stage('deploy to proc'){
when {
branch 'main'
}
steps{
echo "deploy to proc env"
}
}
}
}

执行后因为是clone main分支,将输出deploy to proc env。

parallel

默认Pipeline是串行,可以通过parallel配置并行构建,阶段可以在他们内部声明多嵌套阶段, 它们将并行执行,一个阶段只能有一个 steps 或 parallel的阶段。

1
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
pipeline {
agent any
stages {
stage('one') {
steps {
echo "stage1"
}
}
stage('two') {
failFast true
parallel {
stage('并行1') {
steps {
echo "并行一"
}
}
stage('并行2') {
steps {
echo "并行二"
}
}
}
}
}
}

注:添加failFast true到包含parallel的stage中,其中一个失败时中止所有parallel内的stage。
本阶段会执行多个步骤。
通过BlueOcean查看Pipeline效果图

script

脚本标签,需要执行一些系统命令语法。

1
2
3
4
5
6
7
8
9
10
pipeline {
agent any
stages {
stage('Example') {
steps {
sh 'date'
}
}
}
}

通过sh执行对应的shell命令。

trigger

触发器,设置构建触发器,比如根据周期计划定时构建。
cron定时构建
根Linux内crontab对应的,分、时、日、月、周。这里例子定义每分钟执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pipeline {
agent any
triggers{
cron('* * * * 1')
}
stages {
stage('cat文件'){
steps{
sh '''cat README.md'''
}
}
}
}

注意点:
H关键字为Hash,表示当前设置的时间跨度范围内随机一值例如
triggers{ cron('H/15 * * * *') }
每15分钟执行一次,可能在 :07,:22,:37,:52执行

withCredentials

将secret与变量对应起来。在jenkins中创建的密钥,在Pipeline中希望通过变量方式引用,可以通过withCredentials进行。

例子
在全局凭据中创建个Username with password的凭证,输入用户名和密码

1
2
3
4
5
6
7
8
9
10
11
12
pipeline {
agent any
stages {
stage('部署到测试环境'){
steps{
withCredentials([usernamePassword(credentialsId: 'harbor_account', passwordVariable: 'password', usernameVariable: 'username')]) {
sh 'echo $username'
}
}
}
}
}

执行构建后会输出密钥中的用户名
注:
usernamePassword:withCredentials的类型,相应的还支持sshUserPrivateKey(ssh key)和certificate(证书)。
credentialsId:Jenkins中对应的配置名称。
passwordVariable: 密码项目转成对应的变量。
usernameVariable: 用户名项转换成对应的变量。

SSH User Private Key 示例

1
2
3
4
5
6
withCredentials(bindings: [sshUserPrivateKey(credentialsId: 'jenkins-ssh-key-for-abc', \
keyFileVariable: 'SSH_KEY_FOR_ABC', \
passphraseVariable: '', \
usernameVariable: '')]) {
// some block
}

Certificate 示例

1
2
3
4
5
6
withCredentials(bindings: [certificate(aliasVariable: '', \
credentialsId: 'jenkins-certificate-for-xyz', \
keystoreVariable: 'CERTIFICATE_FOR_XYZ', \
passwordVariable: 'XYZ-CERTIFICATE-PASSWORD')]) {
// some block
}

参考: https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile/#usernames-and-passwords

BlueOcean使用

Jenkins针对pipeline提供了全新的Blue Ocean界面,可以清晰的查看流水线的执行情况:

安装blueocean插件

安装完以后在对应的项目处会多出一个打开Blue Ocean按钮,

界面美化后的扁平化风格,可以通过此界面,进行一些常规的配置和操作

阶段性Pipeline重跑

日志统一下载

Jenkinsfile

目前主流大部分CI工具都支持Pipeline as Code,就是将整CI流程通过代码方式实现,然后将对应的代码和业务代码放置在一起,对应的CI工具在拉取业务代码后可以直接解析CI的流程代码进行执行。Jenkins也是支持这种方式的,通过将写好的Pipeline写在Jenkinsfile中存放在代码仓库中,Jenkins配置读取指定目录的Jenkinsfile文件即可。

实例Demo最佳实践

完整CICD步骤

1、clone 源码。
2、编译源码。
3、编译后进行代码扫描和编译后可执行文件扫描。
4、将编译后生成的制品上传到制品库。
5、镜像构建,将构建后的镜像上传到Harbor。
6、更新Gitlab中的部署文件。
7、触发ArgoCD同步。手动或自动部署到Kubernetes环境中。

Jenkins创建连接账号

因为要进行镜像上传和修改gitlab中的部署yaml,需要进行修改。
系统管理——>Manage Credentials——>创建全局域的认证信息

添加凭据

类型为Username with password

创建id为harbor_account和gitlab_account的凭证用于Pipeline连接。

代码库配置连接SonarQube信息

使用Pipeline流水线,需要在添加以下步骤

1、在对应的代码库的根目录创建sonar-project.properties文件

1
2
3
4
5
6
7
8
9
sonar.projectKey=test2
sonar.projectName=test2
sonar.projectVersion=1.0
sonar.sources=src
sonar.java.binaries=target/classes
sonar.java.source=1.8
sonar.java.target=1.8
sonar.language=java
sonar.sourceEncoding=UTF-8

Pipeline中添加以下步骤

1
2
3
4
5
6
7
8
9
10
stage('SonarQube analysis') {
steps {
script {
def sonarqubeScannerHome = tool name: 'SonarQubeScanner'
withSonarQubeEnv('sonar') {
sh "${sonarqubeScannerHome}/bin/sonar-scanner"
}
}
}
}

注:
1、SonarQubeScanner为全局工具配置中的SonarQube Scanner的配置名称。
2、withSonarQubeEnv配置的sonar变量为全局——>系统配置sonar-server的配置名称

Pipeline创建

创建个名称为spring-petclini的Pipeline
配置构建触发器

贴入以下Pipeline 代码

1
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
pipeline {
agent {
kubernetes {
cloud 'kubernetes'
namespace 'default'
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: git
image: alpine/git:v2.26.2
command:
- cat
tty: true
- name: maven
image: maven:3.6.3-openjdk-8
command:
- cat
tty: true
volumeMounts:
- mountPath: /root/.m2/repository
name: jenkins-maven-m2-pvc
- name: docker
image: docker:19.03-dind
command:
- cat
tty: true
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock
- name: helm-kubectl
image: registry.cn-shenzhen.aliyuncs.com/yedward/helm-kubectl:3.3.1-1.18.8
command:
- cat
tty: true
volumes:
- name: jenkins-maven-m2-pvc
persistentVolumeClaim:
claimName: jenkins-maven-m2-pvc
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: ""
"""
}
}
stages {
stage('Clone') {
steps {
container('git') {
git branch: 'main', credentialsId: 'gitlab', url: 'http://172.16.1.184/root/spring-petclinic.git' }
}
}
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package -DskipTests'
}
}
}
stage('SonarQube analysis') {
steps {
script {
def sonarqubeScannerHome = tool name: 'SonarQubeScanner'
withSonarQubeEnv('sonar') {
sh "${sonarqubeScannerHome}/bin/sonar-scanner"
}
}
}
}
stage('Publish') {
steps {
container('docker') {
withCredentials([usernamePassword(credentialsId: 'harbor_account', passwordVariable: 'USERPWD', usernameVariable: 'USERNAME')]) {
sh 'echo "$USERPWD" | docker login --username="$USERNAME" 172.16.1.31 --password-stdin'
sh 'docker build -t 172.16.1.31/spring-petclinic/spring-petclinic:$BUILD_NUMBER .'
sh 'docker push 172.16.1.31/spring-petclinic/spring-petclinic:$BUILD_NUMBER'
}
}
}
}
stage('Deploy') {
steps {
container('git') {
withCredentials([usernamePassword(credentialsId: 'gitlab_account', passwordVariable: 'USERPWD', usernameVariable: 'USERNAME')]) {
sh 'git config --global user.email "root@example.com"'
sh 'git config --global user.name "root"'
sh 'git remote set-url origin http://$USERNAME:$USERPWD@172.16.1.184/root/spring-petclinic.git'
sh 'sed -i "s/spring-petclinic:.*/spring-petclinic:$BUILD_NUMBER/g" deployment.yaml'
sh 'git add deployment.yaml'
sh 'git commit -m "update yaml"'
sh 'git push origin main'
}
}
}
}
}
}

注意点:
1、clone阶段如果是私有代码仓库,需要配置凭证,可以通过流水线语法生成对应的执行命令。

输入对应的git地址和分支,选择gitlab密钥,生成对应的执行代码

执行
修改spring-petclinic项目的代码注释掉主页的图片,提交代码,触发自动CICD查看效果。

注释掉首页小狗图片
spring-petclinic/src/main/resources/templates/welcome.html

1
<!--<img class="img-responsive" src="../static/resources/images/pets.png" th:src="@{/resources/images/pets.png}"/>-->

重新提交代码

查看已经没有对应logo

参考链接:
https://www.jenkins.io/doc/book/pipeline/syntax/
https://wiki.eryajf.net/pages/3298.html