# Jenkins Pipeine 实践
# 1. pipeline 基本概述
# 1.1 什么是 pipeline
简单来说, Pipeline 就是通过 “代码的⽅式” 将多个任务连接起来。共同来完成⼀件事;

# 1.2 为什么需要 pipeline
- 1、pipeline 能直观展示每个阶段的任务
- 2、pipeline 能直观展示每个阶段的执⾏时间
- 3、pipeline 能快速的定位哪个阶段的任务出现错误
# 1.3 pipeline 语法示例
agent: 指定在哪个节点运⾏这个任务
stage: 阶段
steps: 动作

# 2. pipeline 基本应⽤
# 2.1 pipeline 构建示例
通过 Jenkins 创建⼀个流⽔线项⽬,执⾏如下代码;可以清晰看到,每个阶段的内容以及花费的时⻓;
pipeline{ | |
agent any | |
stages { | |
stage('1.下载代码'){ | |
steps { | |
echo "Get Gitlab Code" | |
} | |
} | |
stage('2.编译代码'){ | |
steps { | |
echo "Build Code" | |
} | |
} | |
stage('3.检测代码'){ | |
steps { | |
echo "Unit Test" | |
} | |
} | |
stage('4.上传代码'){ | |
steps { | |
echo "Build Code" | |
} | |
} | |
stage('5.部署代码'){ | |
steps { | |
echo "Deploy Code" | |
} | |
} | |
} | |
} |
# 2.2 Jenkinsfile 调⽤
在代码仓库中创建⼀个 jenkinsfile ⽂件,然后通过 pipeline 直接调⽤即可。
# 3. pipeline 实现 CI/CD
# 3.1 pipeline 实现 CI
1、创建⼀个名称为 pipeline-springboot-devops-CI 的 Pipeline 流⽔线,⼤体代码实现如下功能;
- 1、拉取代码
- 2、Maven 进⾏编译构建
- 3、进⾏质量检测
- 4、上传编译后的 jar 包⾄制品库
pipeline { | |
agent any | |
environment{ | |
//定义git变量 | |
Git_Id = "jenkins_private_key" | |
Git_Url = "git@gitlab.hmallleasing.com:root/springboot-devops-demo-jar.git" | |
Sonarqube_Url = "http://sonar.hmallleasing.com:9000" | |
} | |
tools { | |
maven 'Maven3.9.11' // 对应全局工具配置中 Maven 的名称 | |
jdk 'jdk17' | |
} | |
stages { | |
stage('1、下载代码') { | |
steps { | |
git branch: 'main', credentialsId: "${Git_Id }", url: "${Git_Url}" | |
//git branch: 'main', credentialsId: 'jenkins_private_key', url: 'git@gitlab.hmallleasing.com:root/springboot-devops-demo-jar.git' | |
} | |
} | |
stage('2、编译代码'){ | |
steps { | |
sh 'mvn package -Dmaven.test.skip=true' | |
} | |
} | |
stage('3、质量扫描'){ | |
steps { | |
//系统管理->系统配置->SonarQube servers配置名称 | |
withSonarQubeEnv('SonarQube') { | |
sh ''' | |
mvn clean verify sonar:sonar \ | |
-Dsonar.projectKey=${JOB_NAME} \ | |
-Dsonar.projectName=${JOB_NAME} \ | |
-Dsonar.host.url=${Sonarqube_Url} \ | |
-Dsonar.token=sqa_b14c221e164b33d4733049072739739d51fbed04 | |
''' | |
} | |
} | |
} | |
stage('4、上传代码至制品库'){ | |
steps { | |
sh 'sh -x /scripts/push_nexus.sh' | |
} | |
} | |
} | |
} |
2、执⾏ CI 流程检查是否符合预期

# 3.2 pipeline Shell 实现 CD
1、创建⼀个名称为 pipeline-springboot-devops-Shell-CD 的 Pipeline 流⽔线,⼤体代码实现如下功能:
2、创建项⽬,然后配置 Extensible Choice Parameter ,⾸先配置 Nexus 仓库地址、⽤户名、密码等信息
变量名: jar_deploy_url
Choice Provider: Nexus3 Artifact Choice Parameter
Nexus Server URL: http://nexus.hmallleasing.com (该域名要确保 jenkins 服务器能正常解析)
Credentials: nexus ⽤户名和密码

2、然后继续配置(项⽬的 GroupID、ArtifactID、Packaging 等信息)
RepositoryId: maven-releases (war 包存放的具体仓库,可以不填写)
GroupId: devops
ArtifactId: xuyong-war
Packaging: war

3、在 Jenkins 前端⻚⾯就可以选择要部署的项⽬所对应的版本

4、依赖 CI 是否构建成功,如果成功则触发 CD;

5、Jenkins 前端构建效果(Shell 脚本需要额外传递 lbservers 和 webservers 变量)、

6、CD 流水线脚本
pipeline { | |
agent any | |
stages { | |
stage('1、部署应用'){ | |
steps { | |
sh 'sh -x /scripts/shell_deploy_jar.sh' | |
} | |
} | |
stage('2、钉钉通知') { | |
steps { | |
echo '消息...' | |
} | |
post { | |
success { | |
dingtalk ( | |
//robot是钉钉机器人的ID,系统管理->钉钉->机器人->id | |
robot: '2f9b96eb-d5ea-498d-9410-92968d2b2da9', | |
type: 'MARKDOWN', | |
title: '${JOB_NAME} 构建成功', | |
text: [ | |
'# ${JOB_NAME} 构建成功', | |
'- 部署的项目: ${JOB_NAME}', | |
'- 部署的环境:${env_deploy_file}', | |
'- 部署的版本: ${jar_deploy_url}' | |
], | |
at: [ | |
'admin' | |
] | |
) | |
} | |
} | |
} | |
} | |
} |
7、CD 流水线部署脚本
[root@jenkins ~]# cat /scripts/shell_deploy_jar.sh | |
#!/usr/bin/bash | |
#1. 定义变量 | |
# 基于完整的 URL 提取对应的 Jar 包名称 | |
jar_name=$(echo ${jar_deploy_url} | awk -F '/' '{print $NF}') | |
jar_dir=/opt | |
jar_port=8081 | |
nexus_user=admin | |
nexus_passwd=talent | |
#2.haproxy 的函数 | |
lb_server_disable(){ | |
for i in ${lbservers} | |
do | |
ssh root@${i} "echo 'disable server jar_cluster/$1' | socat stdio /var/lib/haproxy/stats" | |
done | |
} | |
lb_server_enable(){ | |
for i in ${lbservers} | |
do | |
ssh root@${i} "echo 'enable server jar_cluster/$1' | socat stdio /var/lib/haproxy/stats" | |
done | |
} | |
#3. 下载对应的 Jar 包到指定的路径 | |
wget -O ${jar_dir}/${jar_name} --http-user=${nexus_user} --http-passwd=${nexus_passwd} ${jar_deploy_url} | |
#4. 将 jar 包推送给对应的节点 | |
for host in ${webservers} | |
do | |
scp ${jar_dir}/${jar_name} root@${host}:${jar_dir} | |
done | |
#5. 部署服务 | |
for host in ${webservers} | |
do | |
# 将节点从集群环境中摘除 | |
lb_server_disable ${host} | |
# 停止 jar 服务(注意外单、内双、awk 的 $ 需要转义) | |
ssh root@${host} 'jar_proc=$(ps aux|grep oldxu-jar | grep -v grep | awk "{print \$2}") && \ | |
echo ${jar_proc} && \ | |
kill ${jar_proc}' | |
# 启动 jar 服务 | |
ssh root@${host} "java -jar ${jar_dir}/${jar_name} --server.port=${jar_port} &>/var/log/jar.log &" | |
sleep 3 | |
# 将节点加入集群环境 | |
lb_server_enable ${host} | |
done |
8、检查部署结果

# 3.3 pipeline Ansible 实现 CD
1、创建⼀个名称为 pipeline-springboot-devops-Ansible-CD 的 Pipeline 流⽔线,⼤体代码实现如下功能;
2、配置 Extensible Choice 插件获取 Nexus 制品库中对应的 jar 包版本,变量 jar_deploy_url ;(⽚段⽣成器 -->properties)
变量名: jar_deploy_url
Choice Provider: Nexus3 Artifact Choice Parameter
Nexus Server URL: http://nexus.hmallleasing.com (该域名要确保 jenkins 服务器能正常解析)
Credentials: nexus ⽤户名和密码

2、然后继续配置(项⽬的 GroupID、ArtifactID、Packaging 等信息)
RepositoryId: maven-releases (war 包存放的具体仓库,可以不填写)
GroupId: devops
ArtifactId: xuyong-war
Packaging: war

3、在 Jenkins 前端⻚⾯就可以选择要部署的项⽬所对应的版本

4、调⽤ Ansible 完成发布,需要配置参数化构建 deploy_env_file ;

5、CD 流水线脚本
pipeline { | |
agent any | |
stages { | |
stage('1、部署应用'){ | |
steps { | |
ansiblePlaybook inventory: '${deploy_env_file}', playbook: '/scripts/deploy_nexus_jar.yml', vaultTmpPath: '' | |
} | |
} | |
stage('2、钉钉通知') { | |
steps { | |
echo '消息...' | |
} | |
post { | |
success { | |
dingtalk ( | |
//robot是钉钉机器人的ID,系统管理->钉钉->机器人->id | |
robot: '2f9b96eb-d5ea-498d-9410-92968d2b2da9', | |
type: 'MARKDOWN', | |
title: '${JOB_NAME} 构建成功', | |
text: [ | |
'# ${JOB_NAME} 构建成功', | |
'- 部署的项目: ${JOB_NAME}', | |
'- 部署的环境:${deploy_env_file}', | |
'- 部署的版本: ${jar_deploy_url}' | |
], | |
at: [ | |
'admin' | |
] | |
) | |
} | |
} | |
} | |
} | |
} |
6、CD 流水线部署 Playbook
[root@jenkins ~]# cat /scripts/deploy_nexus_jar.yml
- hosts: "webservers"
vars:
- jar_dir: /opt
- java_port: "8081"
- web_backend: jar_cluster
- nexus_user: admin
- nexus_passwd: talent
serial: 1 # 控一次操作多少台主机
tasks:
- name: "1.获取jar包的完整路径"
shell:
cmd: "echo ${jar_deploy_url}"
register: jar_deploy_url
delegate_to: "127.0.0.1"
- name: "1.获取jar包的完整URL路径"
shell:
cmd: if [ -z ${jar_deploy_url} ];then
curl -s -uadmin:talent "http://nexus.hmallleasing.com/service/rest/v1/search?repository=maven-releases&maven.groupId=devops&maven.artifactId=oldxu-jar&maven.extension=jar" | grep downloadUrl | grep 'jar\",' | tail -1 | awk -F '"' '{print $4}';
else
echo ${jar_deploy_url};
fi
register: jar_deploy_url
delegate_to: 127.0.0.1
- name: "debug"
debug:
msg: "获取最终的值:{{ jar_deploy_url.stdout }}"
- name: "2.获取jar包的名称"
debug:
msg: "{{ jar_deploy_url.stdout.split('/')[-1] }}"
register: jar_name
delegate_to: 127.0.0.1
- name: "debug"
debug:
msg: "获取最终的值:{{ jar_name.msg }}"
- name: "3.通过http协议下载Jar包"
get_url:
url: "{{ jar_deploy_url.stdout }}"
dest: "{{ jar_dir }}/{{ jar_name.msg }}"
username: "{{ nexus_user }}"
password: "{{ nexus_passwd }}"
delegate_to: 127.0.0.1
- name: "4.拷贝Jar包至对应节点"
copy:
src: "{{ jar_dir }}/{{ jar_name.msg }}"
dest: "{{ jar_dir }}/{{ jar_name.msg }}"
- name: "5.摘除需要更新的节点( {{ inventory_hostname }} ),将操作委派给Haproxy负载均衡"
haproxy:
socket: /var/lib/haproxy/stats
backend: "{{ web_backend }}"
state: disabled
host: "{{ inventory_hostname }}" # 获取当前操作节点主机名称
delegate_to: "{{ item }}" # 下线节点任务委派给负载均衡节点
loop: "{{ groups['lbservers']}}"
- name: "6.Check Jar Process Status"
shell:
cmd: "ps -ef | grep -v grep | grep {{ java_port }} | awk '{print $2}'"
register: jar_process
ignore_errors: yes
- name: "7.停止Jar进行运行"
shell:
cmd: "kill {{ jar_process.stdout }}"
ignore_errors: yes
- name: "8.检查进程是否存活"
wait_for:
port: "{{ java_port }}"
state: stopped
- name: "9.Start Jar Server"
shell:
cmd: "nohup java -jar {{ jar_dir }}/{{ jar_name.msg }} --server.port={{ java_port }} &>/dev/null &"
- name: "10.检查Jar程序是否启动成功"
wait_for:
port: "{{ java_port }}"
state: started
- name: "11.上线节点( {{ inventory_hostname }} ),将操作委派给Haproxy负载均衡"
haproxy:
socket: /var/lib/haproxy/stats
backend: "{{ web_backend }}"
state: enabled
host: "{{ inventory_hostname }}"
delegate_to: "{{ item }}" # 上线节点任务委派给负载均衡节点
loop: "{{ groups['lbservers']}}"
7、检查部署结果

# 4. CI 阶段添加质量阀检测
# 4.1 检测结果示意图

# 4.2 配置 Sonarqube
配置 sonarqube web 回调接⼝,将扫描的结果通知 Jenkins 服务器。
Name: jenkins (不是固定)
URL: http://admin:Superman*2025@jenkins.hmallleasing.com/sonarqube-webhook


# 4.3 修改 Pipeline 代码
在 CI 的流⽔线中,新增⼀个阶段,接收 sonarqube 返回的检查结果,然后就⾏验证
pipeline { | |
agent any | |
environment{ | |
//定义git变量 | |
Git_Id = "jenkins_private_key" | |
Git_Url = "git@gitlab.hmallleasing.com:root/springboot-devops-demo-jar.git" | |
Sonarqube_Url = "http://sonar.hmallleasing.com:9000" | |
} | |
tools { | |
maven 'Maven3.9.11' // 对应全局工具配置中 Maven 的名称 | |
jdk 'jdk17' | |
} | |
stages { | |
stage('1、下载代码') { | |
steps { | |
git branch: 'main', credentialsId: "${Git_Id }", url: "${Git_Url}" | |
//git branch: 'main', credentialsId: 'jenkins_private_key', url: 'git@gitlab.hmallleasing.com:root/springboot-devops-demo-jar.git' | |
} | |
} | |
stage('2、编译代码'){ | |
steps { | |
sh 'mvn package -Dmaven.test.skip=true' | |
} | |
} | |
stage('3、质量扫描'){ | |
steps { | |
//系统管理->系统配置->SonarQube servers配置名称 | |
withSonarQubeEnv('SonarQube') { | |
sh ''' | |
mvn clean verify sonar:sonar \ | |
-Dsonar.projectKey=${JOB_NAME} \ | |
-Dsonar.projectName=${JOB_NAME} \ | |
-Dsonar.host.url=${Sonarqube_Url} \ | |
-Dsonar.token=sqa_b14c221e164b33d4733049072739739d51fbed04 | |
''' | |
} | |
} | |
} | |
stage('4、质量检测结果') { | |
steps { | |
script { | |
timeout(time: 1, unit: 'HOURS') { | |
def qg = waitForQualityGate() | |
if (qg.status != 'OK') { | |
error "Sonarqube代码检查失败,failure: ${qg.status}" | |
} | |
} | |
} | |
} | |
} | |
stage('5、上传代码至制品库'){ | |
steps { | |
sh 'sh -x /scripts/push_nexus.sh' | |
} | |
} | |
} | |
} |
# 4.4 Pipeline 执⾏结果
1、没有配置质量阀,执⾏结果如下

2、为了模拟出错误效果,我们可以将质量阀的条件配置为(漏洞为 0 则⽆法通过,或者,代码的⾏数⼤于 10 则⽆法通过等)质量⻔禁 --> 创建(sonar-test)--> 条件 --> 添加条件:
● 全部代码:行数 ⼤于 0
● 全部代码:漏洞 ⼤于 0
最后将创建⻔禁设置为默认

3、调整质量阀,根据公司实际情况进⾏配置,如代码不符合设定的质量阀,则会报错。⽽后终⽌ Pipline 的执⾏。


