# Jenkins Pipeine 实践

# 1. pipeline 基本概述

# 1.1 什么是 pipeline

简单来说, Pipeline 就是通过 “代码的⽅式” 将多个任务连接起来。共同来完成⼀件事;

4.png

# 1.2 为什么需要 pipeline
  • 1、pipeline 能直观展示每个阶段的任务
  • 2、pipeline 能直观展示每个阶段的执⾏时间
  • 3、pipeline 能快速的定位哪个阶段的任务出现错误
# 1.3 pipeline 语法示例

agent: 指定在哪个节点运⾏这个任务

stage: 阶段

steps: 动作

2.png

# 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 流程检查是否符合预期

1.png

# 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.png

2、然后继续配置(项⽬的 GroupID、ArtifactID、Packaging 等信息)

  • RepositoryId: maven-releases (war 包存放的具体仓库,可以不填写)

  • GroupId: devops

  • ArtifactId: xuyong-war

  • Packaging: war

3.png

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

null

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

4.png

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

2.png

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、检查部署结果

4.png

# 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.png

2、然后继续配置(项⽬的 GroupID、ArtifactID、Packaging 等信息)

  • RepositoryId: maven-releases (war 包存放的具体仓库,可以不填写)

  • GroupId: devops

  • ArtifactId: xuyong-war

  • Packaging: war

3.png

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

null

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

5.png

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、检查部署结果

6.png

# 4. CI 阶段添加质量阀检测

# 4.1 检测结果示意图

7.png

# 4.2 配置 Sonarqube

配置 sonarqube web 回调接⼝,将扫描的结果通知 Jenkins 服务器。

Name: jenkins (不是固定)

URL: http://admin:Superman*2025@jenkins.hmallleasing.com/sonarqube-webhook

1.png

3.png

# 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、没有配置质量阀,执⾏结果如下

4.png

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

● 全部代码:行数 ⼤于 0

● 全部代码:漏洞 ⼤于 0

最后将创建⻔禁设置为默认

5.png

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

6.png

8.png

此文章已被阅读次数:正在加载...更新于

请我喝[茶]~( ̄▽ ̄)~*

Xu Yong 微信支付

微信支付

Xu Yong 支付宝

支付宝