DevOps之CICD服务器Jenkins

 

 

DevOps 之 CICD 服务器 Jenkins

Jenkins 部署与基本配置

 Jenkins 介绍

image

 

 

image

 

官方网站:

https://www.jenkins.io/zh/

image

 

官方文档

https://www.jenkins.io/zh/doc/

Jenkins 是基于 Java 开发的一种开源的CI(Continuous integration持续集成)&CD (Continuous
Delivery持续交付,Continuous Deployment持续部署)工具
Jenkins 的前身是商业软件 Hudson

 

Jenkins 用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。可用于自动化各种任务,如构建,测试和部署软件。
Jenkins 作为一个可扩展的自动化服务器,可以用作简单的 CI 服务器,或者变成任何项目的持续交付中心。
Jenkins 只是一个调度平台,其本身并不能完成项目的构建部署
Jenkins 需要安装各种插件,可能还需要编写Shell,python脚本等才能调用和集成众多的组件来实现复杂的构建部署功能

image

 

主要用途:

  • 持续、自动地构建/测试软件项目
  • 监控一些定时执行的任务

Jenkins特点:
开源免费
跨平台,支持所有的平台
master/slave支持分布式的build
web形式的可视化的管理页面
安装配置简单
及时快速的提示和帮助
已有的1800+插件

jenkins 版本

https://www.jenkins.io/download/
https://www.jenkins.io/zh/download/
https://mirrors.tuna.tsinghua.edu.cn/jenkins/

 

jenkins 项目产生两个发行线, 长期支持版本 (LTS) 和定期发布版本。

稳定版 LTS
LTS (长期支持) 版本每12周从常规版本流中选择,作为该时间段的稳定大版本。 每隔 4 周会更新迭
代稳定的小版本,其中包括错误和安全修复反向移植。
下载链接

https://get.jenkins.io/
https://mirrors.tuna.tsinghua.edu.cn/jenkins/

 

Jenkins 安装和启动

Jenkins 的安装

Jenkins 支持多种部署和运行方式
Jenkins支持多种安装方法

  • 包安装
  • JAVA的WAR文件
  • 容器运行

https://www.jenkins.io/zh/doc/book/installing/

 

安装前环境准备

系统要求

https://www.jenkins.io/doc/administration/requirements/java/

系统要求
最低推荐配置:

  • 256MB可用内存
  • 1GB可用磁盘空间(作为一个Docker容器运行jenkins的话推荐10GB)

为小团队推荐的硬件配置:

  • 1GB+可用内存
  • 50 GB+ 可用磁盘空间

JAVA 软件配置:

  • Java 8—无论是Java运行时环境(JRE)还是Java开发工具包(JDK)都可以。
  • Jenkins requires Java 11 or 17 since Jenkins 2.357 and LTS 2.361.1.

 

JAVA 环境

https://www.jenkins.io/doc/book/platform-information/support-policy-java/

 

jenkins基于JAVA实现,安装jenkins前需要先安装 JDK

#安装openjdk

dnf install java-21-openjdk -y

java --version

 

包安装 Jenkins

RHEL系统包安装 Jenkins
CentOS 安装jenkins官方说明

https://pkg.jenkins.io/redhat-stable/

 

包仓库安装过程

wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhatstable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install jenkins

rpm包国内镜像下载链接

https://mirrors.ustc.edu.cn/jenkins/

https://mirrors.aliyun.com/jenkins/redhat-stable/
https://mirrors.tuna.tsinghua.edu.cn/jenkins/redhat-stable/

https://mirrors.aliyun.com/jenkins/rpm-stable/

 

192.168.3.60 gitlab.ming.org
192.168.3.63 jenkins.ming.org

 

范例: CentOS二进制包安装jenkins 

wget https://mirrors.aliyun.com/jenkins/rpm-stable/jenkins-2.541.1-1.noarch.rpm

dnf install jenkins-2.541.1-1.noarch.rpm -y

rpm -ql jenkins 

 

systemctl start jenkins.service 

systemctl enable jenkins

#查看第一次的登录密码

grep -A2 password /var/log/jenkins/jenkins.log 

 

#查看第一次的登录密码

cat /var/lib/jenkins/secrets/initialAdminPassword

 

启动 Jenkins

通过脚本或service启动
安装和下载相关文件后,可以通过init脚本或service方式直接启动,
包安装后,直接内置service,可以直接启动

systemctl start jenkins

#查看日志

tail -fn 50 /var/log/jenkins/jenkins.log

 

首次登录 Jenkins页面初始化

首次登录 Jenkins 页面

用浏览器访问: http://jenkins.ming.org:8080/
默认内置用户admin,其密码为随机字符,可以从如下文件中查到密码

8042a3cf48177844bc2034cddb7eb919

cat /var/lib/jenkins/secrets/initialAdminPassword

输入文件中的密码:

4168ad72dd5db3994013bfb6dc74eaaf

 

选择安装 Jenkins 插件
因为默认安装插件需要连接国外的网站,会很慢,所以建议选择插件安装,先不安装任何插件,后续做优化配
置后再安装会快很多.

3d5076d558a49744c30fab95e50a3cd3

 

选择安装推荐的插件会安装很慢,可以选择不安装,直接点右上角的X直接完成安装过程,后续再用离线
方式安装插件

 

 

插件安装
选 无 ,不安装任何插件

97ffbe9674f3a06433b4cf488cc800e0

 

 

创建 Jenkins 管理员(可选)

用户信息保存在下面目录

ls /var/lib/jenkins/users/

系统默认有一个管理员帐号admin,继续即可

也可以新创建一个管理员帐号

也可以选项下面的 使用admin帐号继续

 

创建新用户或者给admin修改密码

f4628bb26e7ca0b9bfc8d24b721c0f53

 

配置 Jenkins URL
保存完成即可

fbf546028412a3d2a3d551c418b07b2f

 

如果创建新管理员,界面如下

a0ea803884a9042f7d8f8382152d1358

 

登陆 Jenkins界面
如果没有创建管理员,可以直接使用内置用户 admin 登录

f5f4de1db838be52957d8dd704693678

 

 

安装中文插件
插件管理--搜索 chinese -- 选中 Location: Chinese (simplified) 进行安装

cf93dedcbceb69e90ac4f5dab11c47d4

 

67f8838b14ba60e082d4bd7170c8113c

 

在浏览器地址栏输入,点击yes 重启jenkins
http://jenkins服务器:8080/restart

a1afe3de972779e0cd7e692738ca7e05

 

修改 Jenkins 的启动用户为 root

默认Jenkins以jenkins的用户身份运行,会导致权限受限,可以修改service文件设为root身份运行解决此问题
#基于RHEL系统安装修改下面文件

vim /etc/sysconfig/jenkins

#JENKINS_USER="jenkins"
JENKINS_USER="root"

systemctl restart jenkins.service 

 

实现 Java 应用源码编译并部署
java 程序需要使用构建工具,如: maven,ant,gradle等进行构建打包才能部署,其中maven比较流行
以下以 maven 为例实现 Java 应用部署
部署环境如下

image

 

.jenkins 实现CICD的JAVA应用的自由风格的任务

自由风格的任务构建基于 Spring Boot 的 JAR 包 JAVA 项目

导入项目
新版需要用root用户开启导入项目用到Gitlab功能才支持导入
搜索--管理中心--设置-- 通用--导入和导出设置

eb2389e9ae1d01b84943bd59a26fcc15

 

https://gitee.com/lbtooth/spring-boot-helloworld.git

e66848efc8c5cfeca90d9d789f8be4cd

 

569a523de91f615c4fb08858c1915a62

 

 

192.168.3.64

dnf install git -y

git clone https://gitlab.ming.org/devops/spring-boot-helloworld.git

dnf install -y maven

 

#镜像加速

vim /etc/maven/settings.xml

<mirror>
   <id>nexus-aliyun</id>
   <mirrorOf>*</mirrorOf>
   <name>Nexus aliyun</name>
   <url>http://maven.aliyun.com/nexus/content/groups/public</url>
 </mirror>
</mirrors>

测试单元测试

cd /root/spring-boot-helloworld

 mvn clean test

编译生成打包文件

  mvn clean package -Dmaven.test.skip=true

 cp -a /root/spring-boot-helloworld/target/spring-boot-helloworld-0.6-SNAPSHOT.jar /root/

java --version

启动

java -jar spring-boot-helloworld-0.6-SNAPSHOT.jar

curl 192.168.3.64:8080/version

 

 

e37b57e141a940ae7fe0e5a331566714

 

6653b1e5dfeb0e374b5eefc2aa02ecb3

192.168.3.63

vim /etc/ssh/ssh_config

#   StrictHostKeyChecking ask 改StrictHostKeyChecking no

 dnf install -y maven

dnf install git -y

cat gitlab.ming.org.crt  >> /etc/pki/tls/certs/ca-bundle.crt

在Jenkins服务器生成密钥 把公钥加到gitlab的 ssh密钥

su -s /bin/bash jenkins  

ssh-keygen -t rsa

cat /var/lib/jenkins/.ssh/id_rsa.pub

把Jenkins Jenkins用户公钥 放到192.168.3.64 authorized_keys

192.168.3.64

vim /root/.ssh/authorized_keys

 

 

ff4f772f43f6a4d4f9820ade9120ec56

 

 

706c9d599a3455f2148887fe4883e295

 

 

 

2905a97bab3ac079e9c13400cf8ada55

#!/bin/bash
rm -rf spring-boot-helloworld
git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
cd spring-boot-helloworld
mvn clean package -Dmaven.test.skip=true
ssh root@192.168.3.64 killall -9 java 
scp target/spring-boot-helloworld-*.jar root@192.168.3.64:/root/spring-boot-helloworld.jar
ssh root@192.168.3.64 java -jar /root/spring-boot-helloworld.jar &

2672d7d23924e492c5f733372c4cd300

 

 

589049846f92a2dd34c221ab7ad6e89c

 

curl 192.168.3.64:8080/version

 

 mkdir -p /data/jenkins/scripts

 vim /data/jenkins/scripts/spring-boot-helloworld.sh

[root@node1 ~]# cat /data/jenkins/scripts/spring-boot-helloworld.sh
#!/bin/bash
rm -rf spring-boot-helloworld
git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
cd spring-boot-helloworld
mvn clean package -Dmaven.test.skip=true
ssh root@192.168.3.64 killall -9 java 
scp target/spring-boot-helloworld-*.jar root@192.168.3.64:/root/spring-boot-helloworld.jar
ssh root@192.168.3.64 java -jar /root/spring-boot-helloworld.jar &

bash -x /data/jenkins/scripts/spring-boot-helloworld.sh

9f12af1938538aa91e946fe62b778ce5

 

安装插件之前

ab47a05fe2ad9617ddbb6bbcaf68053c

 

 

安装插件

gitlab

 

 

3c45b0d8ec096445bafd91651df46dd9

 

安装插件后

9a2268f5a2e6f4d18a3ea178bb9c6219

 

9341c9860d0ff32e85b1f5a3d4ffef5d

 

55b3ff332bbb9af2675e8f589a0a9b73

 

8962547791059db5d8d454ac3283a610

 

su -s /bin/bash jenkin

cat /var/lib/jenkins/.ssh/id_rsa

粘贴私钥

 

238624092ebb7d427743e94d9fca6097

 

 

 

b34c32822d2ad5b0d38f73ab24ee2e41

 

基于https协议的

1e14ebc6876e6b6439fe0814a22be018

 

需要创建基于https登录 gitlab账号凭据

92d1ec6bcc3fbc71436885eede9ca2ce

 

 

d57e7a16bc283be9c799d610dd639d80

 

 

83378170ecc19a316b2dadc3a0b2e570

 

 

[root@node1 ~]# cat  /data/jenkins/scripts/spring-boot-helloworld.sh
#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld
mvn clean package -Dmaven.test.skip=true
ssh root@192.168.3.64 killall -9 java 
scp target/spring-boot-helloworld-*.jar root@192.168.3.64:/root/spring-boot-helloworld.jar
ssh root@192.168.3.64 java -jar /root/spring-boot-helloworld.jar &

 

实现参数化构建

参数化构建
jenkins支持参数化构建,类似于脚本中的参数,可以实现灵活的构建任务
Jenkins 支持多种参数类型,比如:Boolean,Choice选项,字符串,Multi_line字符串,文件类型等

 

参数类型说明
参数化构建的目标在于为流水线提供基于参数值的灵活构建机制,从而让一个流水线的定义可以适用于多种需求情形

  • 其功能与引用方式与环境变量类似
  • 在触发作业运行之时,需要向各参数赋值
  • 参数在使用时实际上也表现为变量,可以通过变量的调用方式使用参数
  • 注意: 参数化功能无需安装插件即可支持

常用的参数类型
选项参数 Choice Parameter
布尔值参数 Boolean Parameter
字符参数 String Parameter
文本参数 Multi-line String Parameter
凭据参数
密码参数
文件参数
运行时参数

 

d003562fdc0030b76ea79ab93ba6e722

 

 

2c79fbd814df654f67feaaa9e6e94020

 

 

字符参数 String Parameter

字符参数实现实现不同分支的部署

87271678aa83aef60b8035e6c36f3a77

 

文本参数Multi-line String Parameter

966502c22b534a4a6877677aaf23e869

 

 

布尔值参数Boolean Parameter和选项参数Choice Parameter

86072e923ade8c61f3dd42ede5e3201f

 

 

259535188dd7c605788c7cb575ae6b13

 

 

3a0edac53ebae1c1f82bb21ae3c00686

 

 

b9e07781b5d86042b664c7c8ac65d91a

 

982ee1d3a2b1e8d8635d338bcc6e887e

 

 

 

#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld

TEST_HOST="
192.168.3.64
"
PROD_HOST="
192.168.3.65
"

mvn clean package -Dmaven.test.skip=true
[ $ENV = test ] && HOSTS=$TEST_HOST ||HOSTS=$PROD_HOST

for i in $HOSTS;do
  ssh root@$i killall -9 java
  ssh root@$i mkdir -p /opt/$APP-$TIME
  scp target/spring-boot-helloworld-*.jar root@$i:/opt/$APP-$TIME/spring-boot-helloworld.jar
  ssh root@$i "rm -f /root/spring-boot-helloworld.jar && ln -s /opt/$APP-$TIME/spring-boot-helloworld.jar /root/spring-boot-helloworld.jar"
  ssh root@$i java -jar /root/spring-boot-helloworld.jar &
done

 

 新建标签

cd9bc9b758b583a0948d14b12f43caf1

 

104d6dbae1f205a2ec20c6ed7ba37669

 

 

jenkins实现基于git-parameter插件实现参数化构建CICD的JAVA应用部署和回滚

安装插件

git par

0fea5bf5ca2bcd4d1d9783c4b3d32eba

 利用 Git Parameter 插件实现拉取指定 Tag

 

4de6e52ce764685dfe5f620a819f1e62

 

4301f0570a3c77de07129e5d64df5baf

 

 

通过git标签回滚指定版本

 

 

 

0998e0bf71ad737599b8abc18b479fc9

 

 

集成 Ansible 的任务构建

image

 

官方参考

https://plugins.jenkins.io/ansible/

在 Jenkins 上安装 Ansible 环境

dnf install -y epel-release

dnf  install ansible -y

ansible --version

 vim  /etc/ansible/hosts 

[webservers]
192.168.3.64  ansible_ssh_user=root
[appservers]
192.168.3.65  ansible_ssh_user=root

 

Jenkins服务器 生成root用户公钥 加入192.168.3.64 192.168.3.65 的/root/.ssh/authorized_key

ssh-keygen -t rsa

ansible all -m ping

ansible  -i /etc/ansible/hosts  -u root all -m ping  

 

11847fd6504cd413620c733e6c4ec2c3

 

 安装 Ansible 插件

安装ansible插件

c6953bddee7f608bb6cccb63d06a554d

 

安装插件后,添加了ansible的构建步骤

 

使用 Ansible Playbook 实现任务

准备 Playbook文件

cat /data/ansible/test.yml

[root@node1 ~]# cat /data/ansible/test.yml
- hosts: webservers
  remote_user: root
  tasks:
    - name: excute cmd
      shell:
        cmd: hostname -I
      register: result
    - name: show result
      debug:
        msg: "{{ result }}"
    - name: create new file
      file:
        path: /tmp/test.log
        state: touch
[root@node1 ~]# 

 

 创建任务

f71b2ca4acdacd54be4c74a05b8c9171

 

 

使用 Ansible Playbook 基于参数化实现任务测试和生产多套不同环境的部署
上面的任务是固定的,不灵活,利用参数在同一个任务就可以灵活实现测试和生产多套不同环境的部署

准备两个不同环境的主机清单文件

[root@node1 ansible]# cat /etc/ansible/hosts_test   
[webservers]
192.168.3.64  ansible_ssh_user=root

 

root@node1 ansible]# cat /etc/ansible/hosts_prod 
[webservers]
192.168.3.65  ansible_ssh_user=root

 

创建参数化任务

85a17e590699d792cf71295d83f8a37c

 

 

64b4dc38f50cf725560893bec6661b2e

 

执行任务

b121dd2e08fd3263d63943e1e000dc6f

 

 

构建后通知
Jenkins通知可以将任务的执行状态、事件或信息推送给相关用户,这些通常发生在pipeline的“构建后处
理(post-processing)”时期
Email是 Jenkins 内置支持的通知方式,它也能够通过 webhook 扩展支持其它的即时通信媒介,例如:钉
钉,Slack等

 

钉钉通知

插件说明

https://jenkinsci.github.io/dingtalk-plugin/
https://jenkinsci.github.io/dingtalk-plugin/guide/getting-started.html

 

Jenkins 安装 DingTalk 插件

安装完插后,建议重启,否则可以通知失败

 

3f85598b08967025590a25dfc9c9895f

 

Jenkins 系统配置

新版配置
Manage Jenkins --> 钉钉

83a232abeb1e0bd1b90636a6ed5b8283

 

 输入webhook链接和加签信息

fa5101c5221de89f503c10cd9793176b

 

测试查看钉钉收到消息

 

配置任务实现钉钉通知
创建新的任务,使用钉钉通知

e005fdcd0b506bb19655723b5a767abc

 

 

手机号支持多个,每个手机号一行,也可选 atall 即所有群里的人员(不必再输入手机号,如果输入手机
号,仍然会@相关人员)
自定义内容需要使用Markdown格式,比如:

- 构建ID: ${BUILD_ID}
- 部署项目: ${JOB_NAME}
- 项目URL: ${JOB_DISPLAY_URL}
- 部署目录: ${WORKSPACE}

 

aa575e7e4f572f9472eb88865007696a

 

 执行任务验证钉钉通知

如果任务成功,显示下面提示
注意: 如果不成功,重启 jenkins服务,再尝试执行构建

a3160abc83f90a55c283f876b14edbb9

 

 

e7c5418cebaffb24696ce8ec6db4a4ec

 

自动化构建
两种自动化构建方式

  • 周期性定时构建
  • Webhook 触发构建

 

定时和 SCM 构建
周期性构建这是—-种基于 cron 类型的构建机制.按照预定义的时间周期性启动作务
对于期望能够基于代码变更进行触的CI场景来说,周期性构建并非其最佳选项,但对于有些类型的住务,
它却也能够通过精心编排的周期性构建来避免资源冲突;
周期性构建分为定时构建和轮询构建

image

 

定时构建: 按时间周期性的触发构建
轮询SCM(Source Code Management):
指的是定期到代码仓库检查代码是否有变更,存在代码变更时就运行pipeline;为了能够从CI中得到
更多的收益,轮询操作越频繁越好;显然,这会给SCM带去无谓的压力,所以构建的触发由SCM负责
通知Jenkins最为理想;但在外部的SCM无法通知到局域网中的Jenkins时,可以采轮询SCM方式倒也
不失为一种选择
首次任务会进行构建,后续查看SCM代码是否变化,来决定是否构建

 

示例:H/2 * * * * 表示创建任务后2分钟开始每隔2分钟进行构建

 

每分钟执行一次构建

 

4eaebcf98ce733c211b2850b2e8350fd

 

ce6c5e59d7fe87bdce05ad72749f8cd9

 

范例: 每分钟执行SCM 构建

 

7ac82719fc02be59167241993cb8b356

 

 

注意:SCM任务会在左侧多出一个“Git 轮询日志”,可以看到轮询的记录信息
观察Git 轮询日志可以看到当有变化时才会构建,否则不会执行构建

258af59ad26c76bf17e6ddf77d7e6986

 

f58615c83a3040622a0574c4860b52ea

 

构建 Webhook 触发器
构建触发器(webhook),也称为钩子,实际上是一个HTTP回调,其用于在开发人员向gitlab提交代码后
能够触发jenkins自动执行代码构建操作。

image

 

常见场景:
只有在开发人员向develop分支提交代码的时候会自动触发代码构建和部署至测试环境,而向主分支提交的代码不会自动构建,需要运维人员手动部署代码到生产环境。

image

 

可以使用多种方式实现 Webhook 触发构建
触发远程构建: 此方式无需安装插件
Build when a change is pushed to GitLab. GitLab webhook URL: 需要安装Gitlab插件
Generic Webhook Trigger : 需要安装 Generic Webhook Trigger Plugin 插件

 

#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld

TEST_HOST="
192.168.3.64
"
PROD_HOST="
192.168.3.65
"

ENV=test
mvn clean package -Dmaven.test.skip=true
[ $ENV = test ] && HOSTS=$TEST_HOST ||HOSTS=$PROD_HOST

for i in $HOSTS;do
  ssh root@$i killall -9 java && sleep 1
  ssh root@$i mkdir -p /opt/$APP-$TIME
  scp target/spring-boot-helloworld-*.jar root@$i:/opt/$APP-$TIME/spring-boot-helloworld.jar
  ssh root@$i "rm -f /root/spring-boot-helloworld.jar && ln -s /opt/$APP-$TIME/spring-boot-helloworld.jar /root/spring-boot-helloworld.jar"
  ssh root@$i java -jar /root/spring-boot-helloworld.jar &
done

 

f8b768ac6679f9e69127360232dc88ff

 

 

9bf976ef575e34807a687acb80a7fdf4

 

 

 触发远程构建

 

Jenkins配置构建 Webhook 触发器

生成WebHook 触发器链接

f39bee4f0d7dc678a8a4ea0d0b3df948

 

 生成随机字符串

方法一

cat /dev/urandom |tr -dc 'a-zA-Z0-9'|head -c 20

W3i005qXFQ8NFbjRrJiB

方法二

echo $RANDOM|md5sum|cut -c 1-20

76c11afcfd4b9d448ca3

把随机字符串填入

b4c670d2ef829b59bff97e52ce30ea5d

 

然后拼起来下面Jenkins webhook地址

http://jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3

 

Jenkins 配置生成用户的 API Token
可以直接使用用户密码调用webhook,但是有泄露密码风险
可以对用户生成API Token,使用Token调用webhook,这样更加安全
注意:新版本不再允许用户的密码方式,curl测试成功,但gitlab测式会提示403错误,只支持Token

先用创建用户

ae8781b082b4ee3fc0bb18598504a545

 

a2ad954fc8fdad0737bce5b734c06f7a

 

185b57f29be473f6e82389ee438dd23a

 

 

用此用户登录后,修改自已用户配置---添加API Token 

a854a8b78301805c29804ff8a0a73fee

 

e72b23666ea03924fb4cc7c3cb8f1ecd

 

注意: 此值是一次性的,所以必须立即复制Token

1199c3a2792786c96b3489cfa03811e6b2

 

e8313215041b9addc78f37af6a69b378

 

 

curl 命令测试触发并验证远程触发构建

在任意主机使用图形化浏览器比如:chrome直接访问URL地址

http://admin:<token>@jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3
使用curl命令访问URL

curl http://admin:<token>@jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3

#如果执行正常,则无任何显示

 

范例:用token来触发

#格式1

curl http://jenkins:1199c3a2792786c96b3489cfa03811e6b2@jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3

#格式2

curl -u jenkins:1199c3a2792786c96b3489cfa03811e6b2 http://jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3 -I

530e09a43b4b1bfa213b2904ae733bca

 

 

Gitlab配置 Webhook

Gitlab 打开出站请求

以 管理员root身份修改下面配置
管理中心 -- 设置 -- 网络

11ab5c5d8d1321e0894c55db404baef3

 

gitlab配置 webhook
2.11.2.1.5.2 Gitlab配置 Webhook
在gitlab服务器指定项目中创建webhook,输入下面网址

#方法1
#新版格式
http://admin:<token>@jenkins.ming.org:8080/job/hello-world-war/build?token=<项目token>

#旧版格式
http://jenkins.ming.org:8080/job/hello-world-war/build?token=<项目token>

 

新版格式

#新版只支持token
http://用户:<token>@jenkins.ming.org:8080/job/hello-world-war/build?token=<项目token>


#示例

http://jenkins:1199c3a2792786c96b3489cfa03811e6b2@jenkins.ming.org:8080/job/spring-boot-helloworld-demo01/build?token=76c11afcfd4b9d448ca3

 

b481800d09035be793496011cbbf24a6

 

6e310e4509d642a8cda8c7a0a4d2d17a

 

49ff09fe36af600285b41a130f630515

 

测试调用 webhook

e8041bc3104c04720023619390f9fb30

 

 

 

5690e54b76bf12030663da52b3dbab49

 

d709383abc8ade2c07d88490cb8c5fc3

 

 提交代码自动触发 webhook执行

 

 

建议选择“标签推送事件”,只有tag推送时才会触发

 

GitLab Webhook URL
Jenkins 安装 GitLab 插件
需要安装 GitLab 插件,默认无此选项功能

 Jenkins 创建和配置任务

1df54295290790a24da6413b77283f49

 

d545cf818f984b798139390b3fdebe3e

 

 

fa4e503a14c27f6ca897cc72e806f560

 

http://jenkins.ming.org:8080/project/spring-boot-helloworld-demo01

 788f38fb7e5728148c4195a14d89b2b2
 
 

Gitlab 配置 Webhook
注意:需要确保在Gitlab 能够解析Jenkins的FQDN为IP
在gitlab 上在对应项目代码库--配置 -- Webhooks
将上面的WebHook URL 和 Secret token 分别复制到对应的文本框中
建议选择“标签推送事件”,只有tag推送时才会触发,只push不push tag 则不会触发构建

884b78b3f50e98dfb30726511ba89da7

 

cd2d8f2a1c0060b9dad60cb9a7aa049e

 

b4f4b77d095df96928b5135d6f7211b7

 

测试 

 

 

e9c79580d30ccefe6da2657d8719a46a

 

680fd78fef2d7d25cc472f33760e9cc4

 

验证结果
修改代码并提交仓库

git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git

cd /root/spring-boot-helloworld/src/main/java/com/neo/controller

sed  -i 's/0.[0-9]/0.9/g' HelloWorldController.java

git add .

git commit -m "v0.9"

git tag v0.9

git push -u origin main

推标签

git push --tags

 

19cd48acca3a42c8d2f68e141d882019

 

curl 192.168.3.64:8080/version

 

实现容器化的 Docker 任务

jenkins 支持 Docker 说明

image

 

案例: 实现自由风格任务实现 Docker 镜像制作并运行

fc08cc118c86804d1e98d64eb9f0360e

 

Jenkins服务器、业务服务器安装docker

在harbor.ming.org主机上安装Harbor 

192.168.3.63 harbor.ming.org

[root@node1 controller]# cat /data/jenkins/scripts/spring-boot-helloworld-docker.sh 
#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

#TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld
HUB=harbor.ming.org
HOSTS="
192.168.3.64
"
TAG=$(git tag --points-at HEAD)

mvn clean package -Dmaven.test.skip=true
docker build -t $HUB/example/$APP:$TAG .
docker login -u admin -p 123456 $HUB
docker push $HUB/example/$APP:$TAG

for i in $HOSTS;do
  ssh root@$i "docker rm -f $APP && docker run -d --name $APP -p 80:8888 --pull always --restart always $HUB/example/$APP:$TAG"
done

 

 

在 Jenkins 创建自由风格任务
范例:spring-boot-helloworld

8d9f7b7820160bccbf3166b60aad386a

 

 

73abb6ee9bd31fe4f2b24ac74e36c06f

 

a5d23a22dab6e9a38cdc5221f0190de7

 

 

af71921e76ac333f6a7ed406fcb76c9c

 

 

在Gitlab 准备项目
项目1: spring-boot-helloworld

准备Dockerfile

[root@node1 spring-boot-helloworld]# cat /root/spring-boot-helloworld/Dockerfile
#FROM adoptopenjdk/openjdk11:alpine-jre
#FROM openjdk:11.0.16
#FROM openjdk:8u212-jre-alpine3.9
FROM registry.cn-beijing.aliyuncs.com/wangxiaochun/openjdk:8u212-jre-alpine3.9

ADD target/spring-boot-helloworld-*-SNAPSHOT.jar /applications/spring-boot-helloworld.jar

#ENTRYPOINT ["/usr/local/openjdk-11/bin/java -jar /applications/spring-boot-helloworld.jar --server.port=80"]
#CMD ["java","-jar","/applications/spring-boot-helloworld.jar --server.port=8888"]
CMD ["java","-jar","/applications/spring-boot-helloworld.jar","--server.port=8888"]

 

 

 

在 Jenkins主机安装 Docker ,并信任harbor
测试能够拉取镜像推送到harbor

 

+ docker build -t harbor.ming.org/example/spring-boot-helloworld:1 .
ERROR: permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

 

 

#默认jenkins用户无法通过docker的socket文件访问docker服务器

ll /var/run/docker.sock
srw-rw---- 1 root docker 0 Mar 11 16:56 /var/run/docker.sock=

#方法1:
#修改jenkins用户权限可以访问docker的socket文件

把Jenkins用户加入docker组

usermod -aG docker jenkins

id jenkins 

#需要重启jenkins上面的权限才能生效

systemctl restart jenkins

 

在部署应用的目标主机安装 Docker,并且打开远程连接端口(可选),并且信任harbor
测试能从harbor拉取镜像

 

d51563c3c3a5a4440e0306576e886052

 

curl 192.168.3.64:80/version

 

使用webhook触发

推送tag 触发

Jenkins url token

30f09cb459b6933f7c65036b6127604a

 

 

0df49e89e18f09a22196485374fb3d23

 

 

d772e6533e21faa6e99515a6af7236a7

 

d206f35008bcb3fba5900abc4f66e597

 

 

 

 

验证结果
修改代码并提交仓库

git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git

cd /root/spring-boot-helloworld/src/main/java/com/neo/controller

sed  -i 's/0.11/0.12/g' HelloWorldController.java

git add .

git commit -m "v0.12"

git tag v0.12

git push -u origin main

推标签

git push --tags

 

438e1def42af21ea41ff0c5b15259ae4

 

curl 192.168.3.64:80/version

 

 

集成 Kubernetes

https://www.jenkins.io/doc/book/scaling/scaling-jenkins-on-kubernetes/

在 Kubernetes集群的实现基于 Jenkins 实现CICD流程

 

部署k8s集群

网络插件

mlb

ingres-nginx

 192.168.3.64  master

192.168.3.65  node

 

 

k8s master node加上域名解析

192.168.3.60 gitlab.ming.org
192.168.3.63 jenkins.ming.org
192.168.3.63 harbor.ming.org

2c9147705c8e2723e28db143a0e9816b

 

在部署在Kubernetes集群外的Jenkins中执行Kubernetes集群的管理任务有以下三种方式

 

方法1: 基于ssh协议直接连接kubernetes集群执行

此方式最为简单,只需要打通jenkins 到k8s的master节点的ssh key 验证即可
范例:基于ssh协议直接连接kubernetes集群执行job

 Jenkins主机加上k8s master域名解析

 

cat /var/lib/jenkins/.ssh/id_rsa.pub

把Jenkins Jenkins用户公钥 放到192.168.3.64 authorized_keys

192.168.3.64

vim /root/.ssh/authorized_keys

 

 

[root@master1 ingress-nginx]# kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.149.199   192.168.3.10   80:30080/TCP,443:30121/TCP   19s
ingress-nginx-controller-admission   ClusterIP      10.101.27.196    <none>         443/TCP                      19s

 

[root@node1 scripts]# cat /root/spring-boot-helloworld/deploy/01-namespace.yaml 
kind: Namespace
apiVersion: v1
metadata:
  name: test

 

[root@node1 scripts]# cat /root/spring-boot-helloworld/deploy/02-service.yaml 
---
kind: Service
apiVersion: v1
metadata:
  name: spring-boot-helloworld
  namespace: test
  labels:
    app: spring-boot-helloworld
spec:
  #type: NodePort
  type: LoadBalancer
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8888
      nodePort: 30666
  selector:
    app: spring-boot-helloworld

 

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: spring-boot-helloworld
  namespace: test
  labels:
    app: spring-boot-helloworld
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-boot-helloworld
  template:
    metadata:
      name: spring-boot-helloworld
      labels:
        app: spring-boot-helloworld
    spec:
      containers:
      - name: spring-boot-helloworld
        imagePullPolicy: Always
        image: 'harbor.ming.org/example/spring-boot-helloworld:__TAG__'
        ports:
        - name: http
          containerPort: 80
          protocol: TCP

 

 

 

#下面方式直接连接kubernetes集群并使用master节点的工具和kubeconfig实现,无需安装kubectl工具和创建kubeconfig文件

[root@node1 controller]# cat /data/jenkins/scripts/spring-boot-helloworld-k8s.sh      
#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

#TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld
HUB=harbor.ming.org
TAG=$(git tag --points-at HEAD)

mvn clean package -Dmaven.test.skip=true
docker build -t $HUB/example/$APP:$TAG .
docker login -u admin -p 123456 $HUB
docker push $HUB/example/$APP:$TAG

sed -i "s#__TAG__#${TAG}#" deploy/03-deployment.yaml
cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"

 

 

1d2fbb3c4bea94be300841ba50fc6489

 

 

0d9b56becf52b8c90614f87a461a52b1

 

 

 

79ce31c3fa8148c677f7ae0981e9414a

 

3bd048d1b4deb75fc765e957a4d59219

 

190756e42e52b4b2d9d04c1f1bbad406

 

796c6a1b4553e9bed5a7baea505d27e1

 

 

[root@master1 ~]# kubectl get pod -n test
NAME                                      READY   STATUS    RESTARTS   AGE
spring-boot-helloworld-5dd6f7c895-dltnz   1/1     Running   0          2m41s
spring-boot-helloworld-5dd6f7c895-tpngv   1/1     Running   0          2m41s

 

[root@master1 ~]# kubectl get svc -n test   
NAME                     TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
spring-boot-helloworld   LoadBalancer   10.99.83.85   192.168.3.11   80:30666/TCP   2m54s

curl 192.168.3.11/version

 

方法2: 基于 kubeconfig 实现

流程说明

  • 在Jenkins服务器安装kubectl工具,可以通过复制kubectl 二进制程序文件到Jenkins服务器
  • 的/usr/local/bin下实现
  • 将Kubernetes集群中的master节点的上的/etc/kubernetes/admin.conf 复制到Jenkins服务器
  • ~jenkins/.kube/config
  • 修改权限: chmod 644 ~jenkins/.kube/config
  • 编写shell,调用kubectl apply -f 执行集群管理操作

 

在Jenkins主机准备名称解析

cat /etc/hosts
192.168.3.64 kubeapi.org kubeapi
192.168.3.64 master1.org master1
192.168.3.61 master2.org master2
192.168.3.62 master3.org master3
192.168.3.63 node1.org node1
#192.168.3.64 node2.org node2
192.168.3.65 node3.org node3
192.168.3.66 ha1.org ha1
192.168.3.67 ha2.org ha2
192.168.3.60 gitlab.ming.org
192.168.3.63 jenkins.ming.org
192.168.3.63 harbor.ming.org

 

在 Jenkins主机安装kubectl工具

scp root@master1.wang.org:/usr/bin/kubectl /usr/local/bin/

ll /usr/local/bin/kubectl 

ldd /usr/local/bin/kubectl

在 Jenkins主机准备 kubeconfig 文件

scp root@master1.org:/etc/kubernetes/admin.conf .kube/config
mv .kube/config  /data/jenkins/scripts/kubeconfig

chmod +r /data/jenkins/scripts/kubeconfig

 

kubectl --kubeconfig /data/jenkins/scripts/kubeconfig get node
NAME STATUS ROLES AGE VERSION
master1.org Ready control-plane 5d19h v1.35.8
node3.org Ready <none> 5d19h v1.35.8

 

cat /data/jenkins/scripts/spring-boot-helloworld-k8s.sh            
#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

#TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld
HUB=harbor.ming.org
TAG=$(git tag --points-at HEAD)

mvn clean package -Dmaven.test.skip=true
docker build -t $HUB/example/$APP:$TAG .
docker login -u admin -p 123456 $HUB
docker push $HUB/example/$APP:$TAG

sed -i "s#__TAG__#${TAG}#" deploy/03-deployment.yaml
#cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"
kubectl --kubeconfig /data/jenkins/scripts/kubeconfig apply -f deploy/
[root@node1 ~]# 

 

 

方法3 基于Jenkins配置Kubeconfig的凭据

da1014c8740f556864a03e938b84dcc6

 添加Secet file 类型的凭据用于存放kubeconfig文件的内容

将master节点的.kube/config文件定义为kubeconfig的凭据

da7a0848523cd368e06d7e4162fc3c48

 

创建自由风格的任务

使用前面创建的凭据的内容生成一个临时文件,并将此路径用于定义环境变量KUBECONFIG的值

c00d54c437f2ee2fb4930731551c61b1

 

通过Jenkins凭据实现

 cat /data/jenkins/scripts/spring-boot-helloworld-k8s.sh 
#!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

#TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld
HUB=harbor.ming.org
TAG=$(git tag --points-at HEAD)

mvn clean package -Dmaven.test.skip=true
docker build -t $HUB/example/$APP:$TAG .
docker login -u admin -p 123456 $HUB
docker push $HUB/example/$APP:$TAG

sed -i "s#__TAG__#${TAG}#" deploy/03-deployment.yaml
#cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"
kubectl --kubeconfig $KUBECONFIG apply -f deploy/

 

 

方法4:基于 SA 帐号的 Token 实现

创建一个SA帐号并授权

#在k8s的管理节点执行

kubectl create ns jenkins

kubectl create sa jenkins -n jenkins

#方式1:集群级权限

kubectl create clusterrolebinding jenkins-admin --clusterrole=cluster-admin --serviceaccount=jenkins:jenkins

生成 SA 对应的 Secret Token

[root@master1 ~]# cat jenkins-secret.yaml           
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: jenkins-secret
  namespace: jenkins
  annotations:
    kubernetes.io/service-account.name: "jenkins"

kubectl apply -f jenkins-secret.yaml

#查看Token

kubectl describe secrets -n jenkins jenkins-secret

 

将SA对应的Secret Token 保存为Jenkins凭据
在jenkins上创建Secret Text 类型的凭据

69820c73801feb9ac750efd981700761

 

 

fcdefaf1a763af0ef391d2313f4a0140

 

在Jenkins的任务中将凭据的值赋值给变量
注意:变量名不能用横线,支持下划线

df19de26141cf97ea7e3af794d7cfc23

 

创建自由风格任务
创建Jenkins 任务,引用 SA对应的 token 进行Kubernetes 集群管理

JENKINS_TOKEN

 cat /data/jenkins/scripts/spring-boot-helloworld-k8s.sh                                                               #!/bin/bash
#rm -rf spring-boot-helloworld
#git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git
#cd spring-boot-helloworld

#TIME=$(date +%F_%H-%M-%S)
APP=spring-boot-helloworld
HUB=harbor.ming.org
TAG=$(git tag --points-at HEAD)

mvn clean package -Dmaven.test.skip=true
docker build -t $HUB/example/$APP:$TAG .
docker login -u admin -p 123456 $HUB
docker push $HUB/example/$APP:$TAG

sed -i "s#__TAG__#${TAG}#" deploy/03-deployment.yaml
#cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"
#kubectl --kubeconfig $KUBECONFIG apply -f deploy/
kubectl apply -f deploy/ --token=${JENKINS_TOKEN} -s https://kubeapi.org:6443 --insecure-skip-tls-verify=true 

 

 

 

配置webhook

推送tag 触发

Jenkins url token

 

验证结果
修改代码并提交仓库

git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git

cd /root/spring-boot-helloworld/src/main/java/com/neo/controller

sed  -i 's/0.14/0.15/g' HelloWorldController.java

git add .

git commit -m "v0.15"

git tag v0.15

git push -u origin main

推标签

git push --tags

 

 

 

Jenkins Pipeline

 Pipeline 介绍

流水线生产,又叫流水生产流水作业,指劳动对象按一定的工艺路线和统一的生产速度,连续不断地通过各个
工作地,按顺序地进行加工并生产出产品的一种生产组织形式。它是对象专业化组织形式的进一步发展,是劳
动分工较细、生产效率较高的一种生产组织形式。亨利.福特(Henry Ford)于1913年在密歇根州的
Highland Park,建立的生产系统。

 

所谓的 Pipeline 流水线,其实就是将之前的一个任务或者一个脚本就做完的工作,用 Pipeline 语法划分
为多个子任务然后分别执行,两者实现的最终效果是一样的,但是由于原始任务划分为多个子任务之
后,以流水线的方式来执行,那么就可以随时查看任意子任务的执行效果,即使在某个阶段出现问题,
我们也可以随时直接定位问题的发生点,大大提高项目的效率,即模块化完成复杂任务的思想体现
Pipeline 是帮助 Jenkins 实现CI到CD转变的重要角色,是运行在 jenkins 2.X 版本的核心插件,简单来说
Pipeline就是一套运行于 Jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起
来,实现单个任务难以完成的复杂发布流程,从而实现单个任务很难实现的复杂流程编排和任务可视化

 

官方帮助: 

https://www.jenkins.io/zh/doc/book/pipeline/
https://www.jenkins.io/doc/book/pipeline/
https://www.jenkins.io/2.0/

Pipeline基于Groovy DSL(领域特定语言Domain Specific Language )实现,任何发布流程都可以表述为一段Groovy脚本。

Groovy是一种基于JVM虚拟机的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特
性,Groovy 是用Java写的 , Groovy语法与Java语法类似,
Groovy 代码不仅能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,
Groovy也可以使用其他非Java语言编写的库

Groovy官网:http://www.groovy-lang.org/learn.html
Groovy语法:http://groovy-lang.org/syntax.html

 

 流水线和自由风格任务流程比较

image

 

Pipeline 优势

所有构建步骤代码化,统一代码语言
代码模块化,清楚显示构建一个个步骤
指定哪个步骤重新执行
临时修改,模拟执行 dry-run

缺点:学习成本

 

image

 

一致性: Pipeline 用统一语法的代码的方式实现各个CICD的阶段的任务,不仅可以被纳入版本控制,还
可以通过编辑代码实现目标效果
直观性: 构建过程中每一步都可以直接的图形化显示输出,比如每个阶段的执行时间,直观友好,pipeline
帮助我们快速的定位哪个阶段的任务出现错误
可持续性:Jenkins的重启或者中断后不影响已经执行的pipeline Job
支持暂停:Pipeline可以选择停止并等待人工输入或批准后再继续执行
支持回放: 如果失败,可以使用回放,进行临时性的修改 job ,再调试执行,如果成功,再真正修改任务即可
可扩展:通过Groovy的编程更容易的扩展插件
并行执行:通过Groovy脚本可以实现step,stage间的并行执行,和更复杂的相互依赖关系
多功能:支持复杂CD要求,包括fork/join子进程,条件判断,循环和并行执行工作的能力

 

Pipeline 语法
Pipeline 语法介绍和结构

官方文档

https://www.jenkins.io/zh/doc/book/pipeline/syntax/
http://www.jenkins.io/doc/book/pipeline/syntax/
http://www.jenkins.io/doc/pipeline/steps/
#支持docker
https://www.jenkins.io/doc/book/pipeline/docker/

 

当前 Jenkins 2.X 支持两种语法的流水线: 脚本式(命令式)和声明式
脚本式 Scripted Pipeline语法

此语法是 Jenkins最先支持pipeline语法,采用命令式风格,直接在流水线脚本中定义逻辑和程序流程

声明式 Declarative Pipeline语法
后来CloudBees公司为Jenkins引入的一种“流水线即代码”的pipeline语法
它允许用户在pipeline的定义中将更多的精力关注于期望pipeline的状态和输出之上,而非实现逻辑

声明式和脚本化的流水线从根本上是不同的。 声明式流水线的是 Jenkins 流水线更新一些的特性:

  • 相比脚本化的流水线语法,它提供更丰富的语法特性
  • 是为了使编写和读取流水线代码更容易而设计的

 

Pipeline 的基本结构
pipeline
流水线的最外层结构,代表整条pipeline,包含着pipeline的完整逻辑;是声明式流水线语法的关健
特征
node 和 agent
用于定义任务在哪里执行
每个node都是一个 Jenkins 节点,可以是 Jenkins master也可以是 Jenkins agent,node是执行
step的具体服务器。
node 代码块也是脚本式pipeline语法的关健特性,声明式pipeline使用 agent 关健字
stages
用于包含所有stage的定义
stage
属于stages的子语句块
指定 stage 的名称, 用于定义每个阶段 stage 的主要任务
一个pipeline可以划分为若干个stage,每个stage都是一个完整的操作,比如: clone代码、代码编
译、代码测试和代码部署,阶段是一个逻辑分组,可以跨多个node执行。
steps
属于stage的子语句块
每个阶段stage中定义完成该阶段功能所需要经历的一系列步骤
步骤 steps 是jenkins pipeline最基本的操作单元,从在服务器创建目录到构建容器镜像,由各类
Jenkins 插件提供实现,例如: sh “make”
能够把这些步骤steps 同该stage中的其它定义(如环境的定义,Post 等)分隔开
post
用在stage 代码块(和steps 同级)或整个pipeline执行完成后的附加步骤,此指令非必须项

 

声明式流水线语法
声明式流水线是在"Pipeline plugin"的2.5版本添加到 Jenkins 流水线的 ,它在流水线子系统之上提供了一种更简单,更常见的语法。
所有有效的声明式流水线必须包含在一个 pipeline 块中, 比如

 

官方说明

https://www.jenkins.io/zh/doc/book/pipeline/syntax/

Pipeline 的基本结构
实线部分为必须段,虚线为可选段

image

 

 

pipeline的定义有一个明确的、必须遵循的结构,它由一些directive和section组成,每一个section又可
包含其它的section、directive和step,以及一些condlition的定义
Section:用于将那些在某个时间点需要一同运行的条目(item)组织在一起
agent section:指定负责运行代码的节点
在pipeline代码块的顶部,必须要有一个agent来指定“默认”的执行节点
而一个stage的顶部也可以有一个agent的定义,用来指定负责运行该stage中的代码的节点
stages section:组织---到多个stage
steps section:组织一至多个DSL格式的步骤
post section:在stage或整个pipeline的尾部封装--些需要被执行的步骤或者检验条件
Directive(指令)︰负责完成特定功能的语句或代码块,如environment、tools、triggers、input和when


Steps : steps本身就是一个标识特定section的名称,其内部可以使用任何合法的DSL语句,例如git、
sh、 bat和echo等

 

 

 

Pipeline 的声明式语法要点
steps内部的命令,每一条单独的命令都在当前任务的工作目录下执行。

即使A命令切换到了一个新的目录,接下来的B命令并不会在对应的新目录中执行,而是在当前任务
的工作目录下执行。如果非要在切换后的目录下执行命令B,那么采用she11中的&&符号将多条命
令拼接在一起即可。
默认情况下,不支持shell里面的复杂语法,因为groovy有自己的条件表达式
如果jenkins的工作目录下存在同名目录,则获取失败

 

Pipeline 常见指令说明

Jenkins 内置语法帮助

http://jenkins-server:8080/job/<job-name>/pipeline-syntax/
#注意:需要安装pipeline插件并且是pipeline风格的任务

 agent 可接受多种形式的参数

any:任何可用节点
none:用于pipeline顶端时表示不定义默认的agent,每个stage就需要单独指定
label { label ""}:具有指定的标签的节点均为可用节点
node { label "" }:与label相似,但可以指定额外的参数customWorkspace
docker:在指定的容器中运行pipeline或stage代码,该容器动态创建并运行于预配置的可运行容器
的node上,或能够匹配到指定label的node上;可用参数如下
image、label、args、rgistryUrl和rcgistryCredentialsId
dockerfile:功能上类似于上面docker参数,但容器镜像通过指定的docker进行构建;该参数要求
Jenkinsfile必须从Multibranch Pipeline或者Pipeline from SCM中加载;可用参数如下
filename、dir、label、additionalBuildArgs、args、registryUrl和registryCredentialsId
kubernetes:于Kubernetes集群上指定的Pod中运行stage或pipeline代码,该参数同样要求
Jenkinsfile必须从Multibranch Pipeline或者Pipeline from SCM中加载
需要在kubernetes参数中指定Pod模板

 

Stages和Stage

stages是pipeline中最重要的section,stages负责描述pipeline中绝大部分的实际工作( work)
Jenkins会按照stages中定义的顺序自卞而后执行各个stage
stages:封装了用于定义pipeline主体和逻辑的所有stage的定义,它包含一个或多个stage
stages中至少需要包含一个stage,每个stage 指令来定义CD过程的每个离散部分,
stage内部还支持再嵌套一个stages或一个parallel代码块,而后在这些代码块内部再嵌套stage,
以指定stage的运行顺序
stage内部嵌套stages,用于指定以顺序(串行)方式依次运行该stages内部的各stage ;而stage内部
嵌套的parallel{}},用于指定以并行方式运行该parallel号内部的各stage
stage内部仅能定义steps、stages、parallel或matrix四者其中之一,且多层嵌套只能用在最后一
个stage中;
对于本身已经嵌套在parallel或matrix内部的stage来说,不支持在其内部再使用parallel或matrix;
但仍能使用agent、tools和when等其它指令,甚至是stages书配置段以顺序运行stage

 

stage和steps
每个stage都只包含一个steps
Pipeline的基本结构决定了pipeline的整体流程,但真正“做事”是其内部一个个具体的step,因而
steps是pipeline中最核心的组成部分
steps 负责在stage中定义一到多个DSL语句,steps 中的语句负责完成该stage中特定的功能,例如
构建、测试和部署等;但能够同其它的语句分隔开,如environment等
除了script,几乎所有的step在pipeline中都是不可拆分的原子操作
pipeline内置了大量的step,具体请参考https:// www.jenkins.io/doc/pipeline/steps
除此之外,有相当一部分插件可直接当作step来用
steps段中的script{}步骤负责将groovy脚本引入到steps{}配置段中,但它非为必要的步骤,且复杂
的脚本应该单独组织为Shared Libraries,并由Pipeline导入后使用

 

post section
在stage或pipeline的尾部定义一些step,并根据其所在stage或pipeline的完成情况来判定是否运行这些
step
post section支持的condition如下
always:总是运行
changed:其所处的stage或pipeline同前一次运行具有不同状态时,才运行该post
fixed: stage或pipeline本次运行成功,但前一次为failed或unstable时,才运行该post
regression: stage或pipeline前一次运行成功,但本次为failure、unstable或aborted时,才运行
该post
aborted: stage或pipeline的运行状态为aborted时,才运行该post;在Web UI中灰色显示
failure: stage或pipeline的运行状态为failed时,才运行该post
success: stage或pipeline的运行状态为success时,才运行该post
unstable:因测试失败或代码冲突导致stage或pipeline的运行状态为unstable时,才运行该post;在
Web UI中以黄色显示
unsuccessful: stagc或pipeline的运行不成功时,才运行该post
cleanup:在其它所有的post的条件均被评估后(无论stage或pipeline的状态为何)才运行该post

 

Jenkins Pipeline 支持常用指令
无论是脚本式语法还是声明式语法,本质上都是执行各种命令,对于不同的命令需要采用专用的语法来
实现指定的功能,常见的语法命令及其样式如下:
echo: 输出信息, echo "Building"
sh: 执行命令,sh 'command' sh([script: 'echo hello']),用三个单号可以支持多行命令,即:sh ''' 多行shell命令 '''

git: 克隆代码,git branch: 'develop', credentialsId: 'd7e3bd', url: 'git@gitlab.wang.org:example/myapp.git'

env: 设置变量, env.PATH="/usr/local/java/bin:$PATH"
environmet:设定环境变量,可用于stage或pipeline代码块中;支持credentialsl)函数,用于通过标识符访问预定义的凭证

tools:指定需要在agent上下载并配置的工具,例如git、maven、jdk等,这些工具可经由PATH环境变量指定的位置访问到;可用于stage或pipeline中

parameters:用户在触发pipeline时应该提供的参数列表;仅可用于pipeline级别
options:仅可用在pipeline级别来配置pipeline自身的选项,支持的参数可由pipeline自身提供,也可由其它插件(例如timestamps)提供

例如“retry(2)”允许在pipeline失败时重试两次
triggers:用于指定负责自动启动pipeline的触发器,对于集成了Github或Gitlab等自带触发机制的系统场景,triggers并非必须的指令;仅可用于pipeline级别

libraries:当前pipeline可以导入的共享库,该共享库内部的代码则可被该pipeline调用
input: stagc中的专用指令,用于暂停pipeline并提示用户输入内容后继续
when: stage中的专用指令,用于设定该stage的运行条件

 

文件相关指令
isUnix :判断是否为类Unix系统
deleteDir:删除当前目录
dir(" / path/ to/dir"):切换到指定目录
fileExists (" /path/ to/dir"):判断文件是否存在
pwd:打印当前目录
writeFile:将内容写入指定的文件中,支持如下几个参数
file:文件路径,支持相对路径和绝对路径,
text:要写入的内容;
encoding:目标文件的编码,空值为系统默认的编码;支持base64编码格式;可选参数
readFile:读取文件的内容;支持如下几个参数;
file:文件路径,支持相对路径和绝对路径;
encoding:读取文件内容时使用的编码格式;可选参数;

 

消息或控制指令
echo("message"'):打印指定的消息;
error("message"):主动报错,并中止当前pipeline;
retry(count):重复执行count次在{}中定义的代码块
sleep: 让pipeline休眠一段时间,支持如下参数;
time:整数值,休眠时长
unit:时间单位,支持NANOSECONDS,MICROSECONDS、MILLISECONDS、SECONDS、
MINUTES、HOURS和DAYS,可选参数
timeout:代码块的超时时长,支持如下参数
time:整数值,休眠时长
unit:时间单位,支持NANOSECONDS、MICROSECONDS,MILLISECONDS、SECONDS、
MINUTES、HOURS和DAYS,可选参数
activity:布尔类型,值为true时,表示在该代码块不再有月志活动时才算真正超时;可选参数
waitUntil:等待指定的条件满足时执行定义的代码块
initialRecurrencePeriod:初始的重试周期,即测试条件是否满足的重试周期,默认为250ms;可选
参数
quiet:是否禁止将每次的条件测试都记入日志,默认为false,即记入日志;可选参数

 

发送通知指令
mail:向指定邮箱发送邮件
subject:邮件标题;√不body:邮件正文
from (optional):发件人地址列表,逗号分隔
cc (optional) : CC email地址列表,逗号分隔
bcc (optional): BCC email地址列表,逗号分隔
charset(optional):编码格式
mimeType (optional): Email正文的MIME类型,默认为text/plain
replyTo (optional):回件地址,默认为Jenkins设置的全局配置中的邮箱地址

 

Node和Process相关指令
bat: Windows的批处理脚本
powershell:运行指定的PowerShell脚本,支持Microsoft PowerShell 3+
pwsh: PowerShell Core Script
node:在指定的节点上运行后续的脚本
ws :分配工作空间
sh:运行shell脚本,支持的参数如下
script:脚本代码块,支持指定脚本解释器,例如“#!/usr/bin/python3,否则将使用系统默认的解释
器,且使用了-xe选项
encoding (optional):脚本执行后输出的日志信息的编码格式,未定义时使用系统默认编码格式
label(optional):显示在Web UI中的详细信息
returnStdout (optional):布尔型值,true表示任务的标准输出将作为step的返回值,而不是打印到
日志中;若有错误,依然会记入日志
returnStatus (optional):正常情况了命令执行失败会返回非零状态码,设定该参数值为true时,表
示将返回该step的结果,而非状态码

 

 

 

安装 Pipeline 插件
安装 Pipeline(必须安装否则无法显示执行过程) 和 Pipeline Stage View 插件 Blue Ocean 插件

 

77b8854abcca935b0bc87a527922c1df

 

 

 实战案例: 声明式 Pipeline

 案例:变量

http://jenkins.wang.org:8080/job/job/pipeline-syntax/globals

nkins环境变量可分为内置变量和用户自定义变量两类
pipeline和stage得中用于定义环境变量的指令是environment,但定义位置的不同,也意味着其作用域
的不同
定义在pipeline顶部的环境变量可被其后的各stage所引用
Jenkins全局环境变量可被所有的pipeline引用,它们以“env.”为前缀
引用全局环境变量格式有四种:

${env.ENV_VAR_NAME}
$env.ENV_VAR_NAME
$ENV_VAR_NAME
${ENV_VAR_NAME}

 

#注意:变量引用有时要加双引号引起来,如:"${env.<ENV_VAR_NAME>}"

 

范例:构建和推送Docker镜像
准备环境:
Jenkins 主机或 Agent 需要提前安装 Docker
在Jenkins 创建凭据 credential ID为 gitlab-root-password

范例:基于变量构建spring-boot-helloworld项目和推送Docker镜像

368bf8272c876aacbbf4e9a0dec2c412

 

 

 

pipeline {
    agent any
    //tools {
    //    maven 'maven-3.9.13'
    //}
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        imageTag = "${BUILD_ID}"
        harborUserName = "admin"
        harborPassword = "123456"
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Test') {
            steps {
                //注意:不要修改hello()函数,否则会导致下面失败
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                //sh 'mvn -B -DskipTests clean package'
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                sh "echo harborUserName=${harborUserName}"
                sh "echo harborPassword=${harborPassword}"
                sh "echo ${harborPassword} | docker login -u ${harborUserName} --password-stdin ${harborServer}"
                sh "docker login -u ${harborUserName} -p ${harborPassword} ${harborServer}"
                sh "docker push ${imageUrl}:${imageTag}"
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${imageTag}\""
            }
        }
    }
}

 

 

pipeline {
    agent any
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
        harborUserName = "admin"
        harborPassword = "123456"
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    // 获取当前 HEAD 指向的标签,取第一行(如果有多个标签)
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                sh "echo harborUserName=${harborUserName}"
                sh "echo harborPassword=${harborPassword}"
                sh "echo ${harborPassword} | docker login -u ${harborUserName} --password-stdin ${harborServer}"
                sh "docker login -u ${harborUserName} -p ${harborPassword} ${harborServer}"
                sh "docker push ${imageUrl}:${env.imageTag}"
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
            }
        }
    }
}

 

 

范例:基于凭据实现构建和推送Docker镜像
准备环境:
Jenkins 主机或 Agent 需要提前安装 Docker
创建访问harbor的用户和密码的凭据 ID为 harbor-user-credential

 

f3e8e95b18a350176e7c5cef893588d8

 

 

范例:利用凭据提取用户名和密码生成变量名构建helloworld-srping-boot项目

 

pipeline {
    agent any
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
        // harborUserName = "admin"
        // harborPassword = "123456"
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    // 获取当前 HEAD 指向的标签,取第一行(如果有多个标签)
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    // sh "docker login -u ${env.harborUserName} -p ${env.harborPassword} ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
            }
        }
    }
}

 

案例:构建后操作
流水线也提供了构建后的动作,常可用于消息通知
这个动作就在post 配置段中

post部分定义了一个或多个在管道或阶段运行完成后运行的附加步骤(取决于post 部分在管道中的位
置)。
post可以支持以下任何后置条件块:
always、changed、fixed、regression、aborted、 failure、successunstable、 unsuccessful、
cleanup。
这些条件块允许根据管道或阶段的完成状态在每个条件内执行步骤。条件块按如下所示的顺序执行。

 

钉钉通知
范例:钉钉通知

注意:需要安装DingTalk插件并配置

pipeline {
    agent any
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
        // harborUserName = "admin"
        // harborPassword = "123456"
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    // 获取当前 HEAD 指向的标签,取第一行(如果有多个标签)
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    // sh "docker login -u ${env.harborUserName} -p ${env.harborPassword} ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
            }
        }
    }
    post {
        success {
            dingtalk(
                robot: 'dingtalk',
                type: 'TEXT',
                text: [
                    "Status of pipeline: ${currentBuild.fullDisplayName}",
                    "${env.BUILD_URL} has result ${currentBuild.result}."
                ]
            )
        }
        failure {
            dingtalk(
                robot: 'dingtalk',
                type: 'TEXT',
                text: [
                    "Status of pipeline: ${currentBuild.fullDisplayName}",
                    "${env.BUILD_URL} has result ${currentBuild.result}."
                ]
            )
        }
    }
}

 

 

pipeline {
    agent any
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
            }
        }
    }
    post {
        always {
            script {
                def statusEmoji = (currentBuild.currentResult == 'SUCCESS') ? '✅' : '❌'
                def statusColor = (currentBuild.currentResult == 'SUCCESS') ? 'green' : 'red'
                def atList = (currentBuild.currentResult == 'SUCCESS') ? [] : ['all']
                def buildUser = env.BUILD_USER_ID ?: 'admin'
                def currentTime = new Date().format("yyyy-MM-dd HH:mm:ss")

                // 构建消息行(List<String>)
                def textLines = [
                    "### 🚀 ${env.JOB_NAME} 构建通知",
                    "---",
                    "- **任务**: #${env.BUILD_NUMBER}",
                    "- **状态**: <font color=\"${statusColor}\">${statusEmoji} ${currentBuild.currentResult}</font>",
                    "- **持续时间**: ${currentBuild.durationString}",
                    "- **执行人**: ${buildUser}",
                    "- **部署项目**: #${env.BUILD_NUMBER}",
                    "- **项目 URL**: [点击查看](${env.BUILD_URL})",
                    "- **部署目录**: `${env.WORKSPACE}`",
                    "- **触发时间**: ${currentTime}",
                    "---",
                    "[更改记录](${env.BUILD_URL}changes) | [控制台](${env.BUILD_URL}console)"
                ]
                // 失败时追加 @所有人
                if (currentBuild.currentResult != 'SUCCESS') {
                    textLines.add("  \n@所有人 **构建失败,请尽快排查!**")
                }

                dingtalk(
                    robot: 'dingtalk',
                    type: 'MARKDOWN',
                    title: "${env.JOB_NAME} - 构建通知",
                    text: textLines,
                    at: atList
                )
            }
        }
    }
}

 

案例:触发器

https://github.com/jenkinsci/gitlab-plugin

在Pipeline中实现GitLab trigger,注意:需要安装gitlab插件
在Pipeline代码中配置GitLab Trigger会用到如下几个触发条件
triggerOnPush: GitLab仓库推送push事件时是否执行构建
triggerOnMergeRequest:GitLab仓库推送mergeRequest事件时,是否执行构建
branchFilterType:只有符合条件的分支才会被触发;必选配置,否则将无法实现触发,支持如下值
NameBaseFilter:基于分支名进行过滤,多个分支名彼此间以逗号分隔
RegexBaseFilter:基于正则表达式模式对分支名过滤
All:所有分支都会被触发
includeBranchSpec:基于branchFilterType值,输入期望包括的分支的规则
excludeBranchSpec:基于branchFilterType值,输入期望排队的分支的规则
secretToken:在回调时使用的token
GitLab触发器配置格式:

 

 

triggers {
  gitlab(
    triggerOnPush: false,
    triggerOnMergeRequest: true,
    triggerOpenMergeRequestOnPush: "never",
    triggerOnNoteRequest: true,
    noteRegex: "Jenkins please retry a build",
    skipWorkInProgressMergeRequest: true,
    ciSkip: false,
    setBuildDescription: true,
    addNoteOnMergeRequest: true,
    addCiMessage: true,
    addVoteOnMergeRequest: true,
    acceptMergeRequestOnSuccess: false,
    branchFilterType: "NameBasedFilter",
    includeBranchesSpec: "release/qat",
    excludeBranchesSpec: "",
    pendingBuildName: "Jenkins",
    cancelPendingBuildsOnUpdate: false,
    secretToken: "62dad2cd1d9ae62686ada8dc4cdOae66")
}

 

 执行一次后,会自动生成图形化的构建触发器的配置,效果如下

ae784832483227c133d04bb266679dc0

 

范例:通过 Gitlab 插件实现

 

pipeline {
    agent any
    triggers {
        gitlab(triggerOnPush: true,
               acceptMergeRequestOnSuccess: true,
               //triggerOnMergeRequest: true,
               branchFilterType: 'All',
               secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
    }
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('Run Docker') {
            steps {
                sh "ssh root@192.168.3.64 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
                sh "ssh root@192.168.3.65 \"docker rm -f ${projectName} ; docker run --name ${projectName} --pull always --restart always -p 80:8888 -d ${imageUrl}:${env.imageTag}\""
            }
        }
    }
    post {
        always {
            script {
                def statusEmoji = (currentBuild.currentResult == 'SUCCESS') ? '✅' : '❌'
                def statusColor = (currentBuild.currentResult == 'SUCCESS') ? 'green' : 'red'
                def atList = (currentBuild.currentResult == 'SUCCESS') ? [] : ['all']
                def buildUser = env.BUILD_USER_ID ?: 'admin'
                def currentTime = new Date().format("yyyy-MM-dd HH:mm:ss")

                // 构建消息行(List<String>)
                def textLines = [
                    "### 🚀 ${env.JOB_NAME} 构建通知",
                    "---",
                    "- **任务**: #${env.BUILD_NUMBER}",
                    "- **状态**: <font color=\"${statusColor}\">${statusEmoji} ${currentBuild.currentResult}</font>",
                    "- **持续时间**: ${currentBuild.durationString}",
                    "- **执行人**: ${buildUser}",
                    "- **部署项目**: #${env.BUILD_NUMBER}",
                    "- **项目 URL**: [点击查看](${env.BUILD_URL})",
                    "- **部署目录**: `${env.WORKSPACE}`",
                    "- **触发时间**: ${currentTime}",
                    "---",
                    "[更改记录](${env.BUILD_URL}changes) | [控制台](${env.BUILD_URL}console)"
                ]
                // 失败时追加 @所有人
                if (currentBuild.currentResult != 'SUCCESS') {
                    textLines.add("  \n@所有人 **构建失败,请尽快排查!**")
                }

                dingtalk(
                    robot: 'dingtalk',
                    type: 'MARKDOWN',
                    title: "${env.JOB_NAME} - 构建通知",
                    text: textLines,
                    at: atList
                )
            }
        }
    }
}

 

配置webhook

推送tag 触发

Jenkins url token

 

b28027acfdf01d2c5b8e94b88b8afffb

 

19aa1dc1dfa990f006417cc726a43ea3

 

 

验证结果
修改代码并提交仓库

git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git

cd /root/spring-boot-helloworld/src/main/java/com/neo/controller

sed  -i 's/0.15/0.16/g' HelloWorldController.java

git add .

git commit -m "v0.16"

git tag v0.16

git push -u origin main

推标签

git push --tags

 

304a2b080d6878480e8ba5bf51b77f51

 

curl 192.168.3.64:80/version

 

基于 Pipeline 部署k8s

案例:和 Kubernetes 集成基于docker实现 Jenkins 构建

pipeline {
    agent any
    triggers {
        gitlab(triggerOnPush: true,
               acceptMergeRequestOnSuccess: true,
               //triggerOnMergeRequest: true,
               branchFilterType: 'All',
               secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
    }
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    env.imageTag = sh(script: 'git tag --points-at HEAD', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('deploy k8s') {
            steps {
                sh "sed -i 's#__TAG__#${env.imageTag}#' deploy/03-deployment.yaml"
                sh 'cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"'
            }
        }
    }
    post {
        always {
            script {
                def statusEmoji = (currentBuild.currentResult == 'SUCCESS') ? '✅' : '❌'
                def statusColor = (currentBuild.currentResult == 'SUCCESS') ? 'green' : 'red'
                def atList = (currentBuild.currentResult == 'SUCCESS') ? [] : ['all']
                def buildUser = env.BUILD_USER_ID ?: 'admin'
                def currentTime = new Date().format("yyyy-MM-dd HH:mm:ss")

                // 构建消息行(List<String>)
                def textLines = [
                    "### 🚀 ${env.JOB_NAME} 构建通知",
                    "---",
                    "- **任务**: #${env.BUILD_NUMBER}",
                    "- **状态**: <font color=\"${statusColor}\">${statusEmoji} ${currentBuild.currentResult}</font>",
                    "- **持续时间**: ${currentBuild.durationString}",
                    "- **执行人**: ${buildUser}",
                    "- **部署项目**: #${env.BUILD_NUMBER}",
                    "- **项目 URL**: [点击查看](${env.BUILD_URL})",
                    "- **部署目录**: `${env.WORKSPACE}`",
                    "- **触发时间**: ${currentTime}",
                    "---",
                    "[更改记录](${env.BUILD_URL}changes) | [控制台](${env.BUILD_URL}console)"
                ]
                // 失败时追加 @所有人
                if (currentBuild.currentResult != 'SUCCESS') {
                    textLines.add("  \n@所有人 **构建失败,请尽快排查!**")
                }

                dingtalk(
                    robot: 'dingtalk',
                    type: 'MARKDOWN',
                    title: "${env.JOB_NAME} - 构建通知",
                    text: textLines,
                    at: atList
                )
            }
        }
    }
}

 

 curl 192.168.3.11/version

 

 

通过 Jenkinsfile 文件实现
可以将pipeline的语句写到代码仓库中的 Jenkinsfile 文件,这样更加方便维护和版本控制
注意:使用Jenkinsfile后无需再执行git clone代码这一步骤,直接执行后面的mvn test即可

 

创建 Jenkinsfile 文件

git clone git@gitlab.ming.org:devops/spring-boot-helloworld.git

mkdir jenkins

cd jenkins

vim Jenkinsfile

pipeline {
    agent any
    triggers {
        gitlab(triggerOnPush: true,
               acceptMergeRequestOnSuccess: true,
               //triggerOnMergeRequest: true,
               branchFilterType: 'All',
               secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
    }
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
    }
    stages {
        stage('Get Tag') {
            steps {
                script {
                    env.imageTag = sh(script: 'git describe --tags --abbrev=0', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('deploy k8s') {
            steps {
                sh "sed -i 's#__TAG__#${env.imageTag}#' deploy/03-deployment.yaml"
                sh 'cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"'
            }
        }
    }
    post {
        always {
            script {
                def statusEmoji = (currentBuild.currentResult == 'SUCCESS') ? '✅' : '❌'
                def statusColor = (currentBuild.currentResult == 'SUCCESS') ? 'green' : 'red'
                def atList = (currentBuild.currentResult == 'SUCCESS') ? [] : ['all']
                def buildUser = env.BUILD_USER_ID ?: 'admin'
                def currentTime = new Date().format("yyyy-MM-dd HH:mm:ss")

                // 构建消息行(List<String>)
                def textLines = [
                    "###  ${env.JOB_NAME} 构建通知",
                    "---",
                    "- **任务**: #${env.BUILD_NUMBER}",
                    "- **状态**: <font color=\"${statusColor}\">${statusEmoji} ${currentBuild.currentResult}</font>",
                    "- **持续时间**: ${currentBuild.durationString}",
                    "- **执行人**: ${buildUser}",
                    "- **部署项目**: #${env.BUILD_NUMBER}",
                    "- **项目 URL**: [点击查看](${env.BUILD_URL})",
                    "- **部署目录**: `${env.WORKSPACE}`",
                    "- **触发时间**: ${currentTime}",
                    "---",
                    "[更改记录](${env.BUILD_URL}changes) | [控制台](${env.BUILD_URL}console)"
                ]
                // 失败时追加 @所有人
                if (currentBuild.currentResult != 'SUCCESS') {
                    textLines.add("  \n@所有人 **构建失败,请尽快排查!**")
                }

                dingtalk(
                    robot: 'dingtalk',
                    type: 'MARKDOWN',
                    title: "${env.JOB_NAME} - 构建通知",
                    text: textLines,
                    at: atList
                )
            }
        }
    }
}

 

git add .

git commit -m 'add Jenkinsfile'

git push -u origin main

 

 

修改 Pipeline job 使用 Jenkinsfile 文件
SCM 软件配置管理(Software Configuration Management)

5ee0f16a576ca665099a084cd759bce5

 

a771dd85a1e11d73c4fab23cdd28bd91

 

 

再次执行构建验证结果

 

3637e1a31cbb08774e36531e669c05eb

 

 

把Jenkinsfile和开发代码 分开两个仓库存放

 git clone git@gitlab.ming.org:devops/pipeline-jenkins.git

mkdir jenkins

cd jenkins

vim Jenkinsfile

[root@node1 jenkins]# cat Jenkinsfile
pipeline {
    agent any
    triggers {
        gitlab(triggerOnPush: true,
               acceptMergeRequestOnSuccess: true,
               //triggerOnMergeRequest: true,
               branchFilterType: 'All',
               secretToken: '62dad2cd1d9ae62686ada8dc4cdOae66')
    }
    environment {
        codeRepo = "git@gitlab.ming.org:devops/spring-boot-helloworld.git"
        credential = "jenkins"
        harborServer = 'harbor.ming.org'
        projectName = 'spring-boot-helloworld'
        imageUrl = "${harborServer}/example/${projectName}"
        // imageTag 不要在这里定义,改为动态获取
    }
    stages {
        stage('Source') {
            steps {
                git branch: 'main', credentialsId: "${credential}", url: "${codeRepo}"
            }
        }
        stage('Get Tag') {
            steps {
                script {
                    env.imageTag = sh(script: 'git describe --tags --abbrev=0', returnStdout: true).trim()
                    echo "当前镜像标签: ${env.imageTag}"
                }
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -Dmaven.test.skip=true'
            }
        }
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${imageUrl}:${env.imageTag} ."
            }
        }
        stage('Push Docker Image') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'harbor-usercredential',
                                                  passwordVariable: 'harborPassword',
                                                  usernameVariable: 'harborUserName')]) {
                    sh "echo ${env.harborPassword} | docker login -u ${env.harborUserName} --password-stdin ${harborServer}"
                    sh "docker push ${imageUrl}:${env.imageTag}"
                    echo "username=${env.harborUserName}"
                    echo "password=${env.harborPassword}"
                }
            }
        }
        stage('deploy k8s') {
            steps {
                sh "sed -i 's#__TAG__#${env.imageTag}#' deploy/03-deployment.yaml"
                sh 'cat deploy/*.yaml | ssh root@master1.org "kubectl apply -f -"'
            }
        }
    }
    post {
        always {
            script {
                def statusEmoji = (currentBuild.currentResult == 'SUCCESS') ? '✅' : '❌'
                def statusColor = (currentBuild.currentResult == 'SUCCESS') ? 'green' : 'red'
                def atList = (currentBuild.currentResult == 'SUCCESS') ? [] : ['all']
                def buildUser = env.BUILD_USER_ID ?: 'admin'
                def currentTime = new Date().format("yyyy-MM-dd HH:mm:ss")

                // 构建消息行(List<String>)
                def textLines = [
                    "###  ${env.JOB_NAME} 构建通知",
                    "---",
                    "- **任务**: #${env.BUILD_NUMBER}",
                    "- **状态**: <font color=\"${statusColor}\">${statusEmoji} ${currentBuild.currentResult}</font>",
                    "- **持续时间**: ${currentBuild.durationString}",
                    "- **执行人**: ${buildUser}",
                    "- **部署项目**: #${env.BUILD_NUMBER}",
                    "- **项目 URL**: [点击查看](${env.BUILD_URL})",
                    "- **部署目录**: `${env.WORKSPACE}`",
                    "- **触发时间**: ${currentTime}",
                    "---",
                    "[更改记录](${env.BUILD_URL}changes) | [控制台](${env.BUILD_URL}console)"
                ]
                // 失败时追加 @所有人
                if (currentBuild.currentResult != 'SUCCESS') {
                    textLines.add("  \n@所有人 **构建失败,请尽快排查!**")
                }

                dingtalk(
                    robot: 'dingtalk',
                    type: 'MARKDOWN',
                    title: "${env.JOB_NAME} - 构建通知",
                    text: textLines,
                    at: atList
                )
            }
        }
    }
}

 

git add .

git commit -m 'add Jenkinsfile'

git push -u origin main

 git@gitlab.ming.org:devops/pipeline-jenkins.git

 

a0de58b6ebe2a319ddffbde34f34049b

 

 

3c1466c1b918958a79681a870281e882

 

 

a771dd85a1e11d73c4fab23cdd28bd91

 

再次执行构建验证结果

 

0b37762bbb54774baedf2bf8ef5f8796

 

 

 

 

posted @ 2026-09-22 00:09  minger_lcm  阅读(6)  评论(0)    收藏  举报