【k8s】Jenkins实现springcloud应用CI、CD实践 【三】

news2024/12/23 18:37:56

一、运行Jenkins流水线流程思路:

场景:java微服务应用, 单体仓库,多个微服务模块,(并行构建、自动化构建、根据模块变更仅更新特定模块)    java、nodejs

CI阶段          并行方式; 根据模块变更仅更新特定模块
1、准备项日;
	目录结构 (源代码 、dockerfile 、deploy.yaml资源清单文件)
2、下载代码
3、漏洞检测;
4、项目编译;
5、镜像制作;
6、部署测试环境;

CD阶段:
1、拉取harbor中的镜像;
2、交付到生产环境;
3、添加回退阶段;

二、部署springcloud 若依项目

本文不做详细的部署细节,可参考以前的写的: https://blog.csdn.net/Nightwish5/article/details/130308650
在这里插入图片描述

1、准备项目;
			目录结构   (源代码、Dockerfile、deploy.yaml资源清单文件)
			deploy.yaml中要替换的变量:
			{namespace}
			{image}
		
			ui  {host}
			monitor    {host}    ingress
	
		前置条件:
			1、harbor.oldxu.net/springcloud/skywalking-java-agent:8.8
			2、依赖的MySQL、Redis、Skywalking、Nacos(配置)得有;
		
		ruoyi-gateway-dev.yml
		ruoyi-auth-dev.yml
		
		ruoyi-monitor-dev.yml
		ruoyi-system-dev.yml
		
		1、提交代码到gitlab
		2、下载代码

大致说明:

部署基础组件: ruoyi的springcloud部署顺序
1、mysql (若依项目的DB库)
2、redis
3、nacos 、 mysql(nacos依赖的库)
配置nacos中的对应的yml文件

4、sentinel  
5、skywalking (oap和ui)

#part 6可以在CICD过程中部署
6、service-all
  1、system-dp
  2、auth-dp
  3、gateway-dp
  4、monitor-dp-ingress
  5、ui-dp-ingress

三、将代码提交到gitlab

gitlab创建项目:RuoYiCloud

git init 

git config --global user.email "123456@qq.com"
git config --global user.name  "old133"

git  remote add origin http://gitlab.oldxu.net:30080/root/ruoyicloud.git
git add .
git commit -m "初始化"

git checkout -b k8s
git push --set-upstream origin k8s


#方式2:
代码上传到gitee , gitlab导入项目 -> 从URL导入仓库

四、创建流水线 springcloud-ruoyi-CI

2、下载代码
		3、漏洞检测;
		
			并行;
			进入到对应的微服务文件夹中;
			不规则的,需要再cd进个子目录:
			cd ruoyi-monitor
			cd ruoyi-system

在这里插入图片描述

4.1 获取代码和代码扫描和漏洞扫描结果

pipeline{
    agent {
    kubernetes {
      cloud 'kubernetes'
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          imagePullSecrets:
          - name: harbor-admin
          volumes:
          - name: data
            nfs:
              server: 192.168.79.33
              path: /data/maven
          - name: dockersocket
            hostPath:
              path: /run/docker.sock
          containers:
          - name: maven
            image: harbor.oldxu.net/ops/maven:3.8.6
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
            volumeMounts:
            - name: data
              mountPath: /root/.m2
          - name: nodejs
            image: harbor.oldxu.net/ops/nodejs:14.20
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
          - name: sonar
            image: harbor.oldxu.net/ops/sonar-scanner:2.3.0
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
          - name: docker
            image: harbor.oldxu.net/ops/docker:20.10
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
            volumeMounts:
            - name: dockersocket
              mountPath: /run/docker.sock
          - name: kubectl
            image: harbor.oldxu.net/ops/kubectl:1.18.0
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
      '''
    } //kubernetes ned
  }	//agent end

		environment{
			Gitlab_Id = "gitlab-root-token"
			Gitlab_Pro = "http://gitlab.oldxu.net:30080/root/ruoyi-cloud.git"
			
		} //environment end
		
		stages{
		    
			stage('获取代码'){
				steps{
					container('maven'){
					   //注意这里的分支是k8s
					   checkout([$class: 'GitSCM', branches: [[name: '*/k8s']], extensions: [], userRemoteConfigs: [[credentialsId: "${Gitlab_Id}", url: "${Gitlab_Pro}"]]])
                       sh 'pwd && ls -l'
					
					}
				}
			
			} //获取代码 end
			
			stage('代码扫描'){
				//并行处理
				parallel{
				    // 1 检测Gateway
					stage('检测Gateway'){
						environment{
							AppName="ruoyi-gateway"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ type -d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 2 Auth
					stage('检测Auth'){
						environment{
							AppName="ruoyi-Auth"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ type -d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 3 system
					stage('检测system'){
						environment{
							AppName="ruoyi-system"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ type -d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 4 monitor
					stage('检测monitor'){
						environment{
							AppName="ruoyi-monitor"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ type -d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 5 UI
					stage('检测UI'){
						environment{
							AppName="ruoyi-ui"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ type -d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
				}//parallel end					
			} //代码扫描 stage end
			
			
			stage('检查漏洞扫描结果'){
				steps{
					container('sonar'){
						script{
							timeout(5){
								def qg = waitForQualityGate()
								if (qg.status != 'OK'){
									error "Sonarqube 代码检查失败, error的原因 ${qg.status}"
								}
							}
						}
					}
				}
			
			}//检查漏洞扫描结果 stage end
			
			
		}//stages end

}//pipeline end

在这里插入图片描述

五、项目编译(maven和nodejs)

#思路:
也是要并行构建
				Java:
					找到这个微服务模块的路径 (使用刚才的find方法)
					mvn package -Dmaven.test.skip=true -pl ${模块路径} -am
				NodeJS:
				npm install --registry=https://registry.npmmirror.com
				npm run build:prod
				

maven编译举例:
在这里插入图片描述

5.1 对应的pipeline代码:

		stages{
		    			
			stage('代码编译'){
				parallel{
					// 1、编译Gateway
					stage('编译Gateway'){
						environment{
							AppName = "ruoyi-gateway"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}
							
						}
					}
					
					// 2、编译Auth
					stage('编译Auth'){
						environment{
							AppName = "ruoyi-auth"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 3、编译system
					stage('编译system'){
						environment{
							AppName = "ruoyi-system"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 4、编译monitor
					stage('编译monitor'){
						environment{
							AppName = "ruoyi-monitor"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 5、编译UI
					stage('编译UI'){
						environment{
							AppName = "ruoyi-ui"
						}
						
						steps{
							container('nodejs'){
								sh '''
									cd $(find ./ -type d -name "${AppName}") && \
									npm install --registry=https://registry.npmmirror.com && \
									npm run build:prod
								'''
							}							
						}
					}
					
				} // parallel end
			}//代码编译 stage end
			
		}//总stages end

运行结果:
在这里插入图片描述

六、制作Docker镜像

6.1 对应的pipeline代码


执行结果:
在这里插入图片描述

七、部署至K8s测试环境

之前部署成功的效果:
在这里插入图片描述

//交付至K8s
			stage('交付微服务至k8s'){
				parallel{
					// 1 gateway
					stage('交付Gateway'){
						environment{
							AppName = "ruoyi-gateway"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 2 交付auth
					stage('交付auth'){
						environment{
							AppName = "ruoyi-auth"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 3 交付system
					stage('交付system'){
						environment{
							AppName = "ruoyi-system"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 4 交付monitor
					stage('交付monitor'){
						environment{
							AppName = "ruoyi-monitor"
							ImageName = "$Url/${Pro}/${AppName}"
							IngressHost = "monitor-dev.oldxu.net"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										sed -i "s#{host}#${IngressHost}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 5 交付UI
					stage('交付UI'){
						environment{
							AppName = "ruoyi-ui"
							ImageName = "$Url/${Pro}/${AppName}"
							IngressHost = "ui-dev.oldxu.net"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										sed -i "s#{host}#${IngressHost}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
				}//parallel end
			
			}//交付至K8s ,stage end

执行结果:
在这里插入图片描述

八、全自动化CI (问题一)

8.1 自动化构件操作

在这里插入图片描述
构建触发器 -> build when a change …
高级 -> 生成Secret token -> f5b73ada8be70c2259e587eb787e5a4b

gitlab界面:
地址:http://gitlab.oldxu.net:30080/root/ruoyi-cloud/-/hooks
Administrator -> Ruoyi Cloud -> Webhook设置
在这里插入图片描述
在这里插入图片描述
更新代码内容,提交。测试是否自动触发构建CI
ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java
在这里插入图片描述
web页面效果
在这里插入图片描述

全自动化CI的问题:
		如果开发只更新了某个模块,就检测这个某块,编译这个某块、以及发布这个模块;
			
		解决方案:
			1、获取所有更新的模块名称,与我们的微服务名称进行比对,将更新的模块名称写入到文件中 [模块名=ok]
			2、过滤文件中模块的名称,提取OK,将这个OK赋值给新的变量,将新的变量提供给Jenkins的Stage进行判断;

主要逻辑判断脚本:

#!/usr/bin/bash
# 定义微服务模块名称
Init_Module=(ruoyi-gateway ruoyi-auth ruoyi-system ruoyi-monitor ruoyi-ui)

# 变动的模块
Change_Module=$(git diff --name-only $(git log -n2 --pretty=format:"%h") |cut -d / -f1,2)

# 外循环{提取变动的模块名称}
for Change in ${Change_Module}
do
	# echo "改变的微服务模块是: $Change"
	# 内循环
	for init in ${Init_Module[@]}
	do
		if [[ "${Change}" =~ "${init}" ]];then
		    # 正则匹配的逻辑 ->  ruoyi-module/ruoyi-system =~ ruoyi-system
		    echo "更新的模块是: $init"
			echo "${init}=ok" >> var.txt
		fi
	done
done

解释:
在这里插入图片描述

8.2 上述脚本的细节:

1、git diff对比更新的模块
在这里插入图片描述

2、对更新模块做ok判断的逻辑
在这里插入图片描述

8.3 本小节对pipeline的改动有:

新增:
    1、stage('检查变动的模块名称') {
    2、stage('输出更新的模块状态'){
    3、在parallel的各个stage中,加上when判断,如 when{ expression {GATEWAY == "ok"} }

运行效果:
在这里插入图片描述

九、全自动化CI (问题二)

	全自动化CI的问题2:
		自动化触发的时候,就更新那特定的模块;
		**手动点击**的时候是希望**所有的微服务组件都进行构建**;

9.1 新增参数化构建

Deploy_All 自动化构建的时候默认是false,手动构建的时候,选择true.
和
when { expression {UI == "ok" || Deploy_All == "true" } }


parameters {
  choice choices: ['false', 'true'], description: '是否部署所有微服务', name: 'Deploy_All'
}

在这里插入图片描述

完整的springcloud-ruoyi-CI代码:

pipeline{
    agent {
    kubernetes {
      cloud 'kubernetes'
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          imagePullSecrets:
          - name: harbor-admin
          volumes:
          - name: data
            nfs:
              server: 192.168.79.33
              path: /data/maven
          - name: dockersocket
            hostPath:
              path: /run/docker.sock
          containers:
          - name: maven
            image: harbor.oldxu.net/ops/maven:3.8.6
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
            volumeMounts:
            - name: data
              mountPath: /root/.m2
          - name: nodejs
            image: harbor.oldxu.net/ops/nodejs:14.20
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
          - name: sonar
            image: harbor.oldxu.net/ops/sonar-scanner:2.3.0
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
          - name: docker
            image: harbor.oldxu.net/ops/docker:20.10
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
            volumeMounts:
            - name: dockersocket
              mountPath: /run/docker.sock
          - name: kubectl
            image: harbor.oldxu.net/ops/kubectl:1.18.0
            imagePullPolicy: IfNotPresent
            command: ["cat"]
            tty: true
      '''
    } //kubernetes ned
  }	//agent end
  
		//参数话构建 Deploy_All
		parameters{
			choice choices: ['false', 'true'], description: '是否部署所有的微服务', name: 'Deploy_All'

		}

		environment{
			Gitlab_Id = "gitlab-root-token"
			Gitlab_Pro = "http://gitlab.oldxu.net:30080/root/ruoyi-cloud.git"
			
			//Harbor相关的全局变量
			Url = "harbor.oldxu.net"
			Pro = "base"
			HARBOR_ID = "harbor-auth"
			
			//对外暴露的域名
			Ingress_Host = "spring-dev.oldxu.net"
			
		} //environment end
		
		stages{
		    
			stage('获取代码'){
				steps{
					container('maven'){
					   //注意这里的分支是k8s
					   checkout([$class: 'GitSCM', branches: [[name: '*/k8s']], extensions: [], userRemoteConfigs: [[credentialsId: "${Gitlab_Id}", url: "${Gitlab_Pro}"]]])
                       sh 'pwd && ls -l'
					
					}
				}
			
			} //获取代码 end
			
			//检查变动的模块名称
			stage('检查变动的模块名称'){
				steps{
					sh '''#!/bin/bash
						#微服务模块名称
						Init_Module=(ruoyi-gateway ruoyi-auth ruoyi-system ruoyi-monitor ruoyi-ui)
						
						#变动的模块
						Change_Module=$(git diff --name-only $(git log -n2 --pretty=format:"%h") | cut -d / -f1,2 )
						
						# 外循环{提取变动的模块名称}
						for Change in ${Change_Module}
						do
							echo "改变的微服务模块是: $Change"
							#内循环 
							for init in ${Init_Module[@]}
							do
								if [[ "${Change}" =~ "${init}" ]];then
									echo "${init}=ok" >> var.txt
								fi
							done
						done
					'''
					
					//制作自定义变量
					script{
						env.GATEWAY = sh(returnStdout: true,script: "grep 'ruoyi-gateway' var.txt | awk -F '=' '{print \$2}' ").trim()
						env.AUTH = sh(returnStdout: true, script: "grep 'ruoyi-auth' var.txt | awk -F '=' '{print \$2}'").trim()
						env.SYSTEM = sh(returnStdout: true,script: "grep 'ruoyi-system' var.txt | awk -F '=' '{print \$2}'").trim()
						env.MONITOR = sh(returnStdout: true,script: "grep 'ruoyi-monitor' var.txt | awk -F '=' '{print \$2}'").trim()
						env.UI = sh(returnStdout: true,script: "grep 'ruoyi-ui' var.txt | awk -F '=' '{print \$2}'").trim()
					}
			
				}
			
			}//检查变动的模块名称 stage end
			
			stage('输出更新的模块状态'){
				steps{
					sh 'echo "gateway: ${GATEWAY}"'
					sh 'echo "auth: ${AUTH}"'
					sh 'echo "system: ${SYSTEM}"'
					sh 'echo "monitor: ${MONITOR}"'
					sh 'echo "ui: ${UI}"'
				}
			}//输出更新的模块状态 stage end
			
			stage('代码扫描'){
				//并行处理
				parallel{
				    // 1 检测Gateway
					stage('检测Gateway'){
						when { expression {GATEWAY == "ok" || Deploy_All == "true"} }
						environment{
							AppName="ruoyi-gateway"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ -type d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 2 Auth
					stage('检测Auth'){
   						when { expression {AUTH == "ok" || Deploy_All == "true" } }
						environment{
							AppName="ruoyi-auth"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ -type d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 3 system
					stage('检测system'){
						when { expression {SYSTEM == "ok" || Deploy_All == "true"} }
						environment{
							AppName="ruoyi-system"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ -type d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 4 monitor
					stage('检测monitor'){
						when { expression {MONITOR == "ok" || Deploy_All == "true"} }
						environment{
							AppName="ruoyi-monitor"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ -type d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
					// 5 UI
					stage('检测UI'){
						when { expression {UI == "ok"|| Deploy_All == "true"} }
						environment{
							AppName="ruoyi-ui"
						}
						
						steps{
							withSonarQubeEnv('sonar-k8s'){
								container('sonar'){
									sh ' cd $(find ./ -type d -name "${AppName}") && \
										sonar-scanner \
										-Dsonar.projectKey=${AppName} \
										-Dsonar.java.binaries=src \
										-Dsonar.sources=.'
								}
							}
						}
					}
					
				}//parallel end					
			} //代码扫描 stage end
			
			
			stage('检查漏洞扫描结果'){
				steps{
					container('sonar'){
						script{
							timeout(5){
								def qg = waitForQualityGate()
								if (qg.status != 'OK'){
									error "Sonarqube 代码检查失败, error的原因 ${qg.status}"
								}
							}
						}
					}
				}
			
			}//检查漏洞扫描结果 stage end
			
			stage('代码编译'){
				parallel{
					// 1、编译Gateway
					stage('编译Gateway'){
						when { expression {GATEWAY == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-gateway"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}
							
						}
					}
					
					// 2、编译Auth
					stage('编译Auth'){
						when { expression {AUTH == "ok" || Deploy_All == "true"} }

						environment{
							AppName = "ruoyi-auth"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 3、编译system
					stage('编译system'){
						when { expression {SYSTEM == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-system"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 4、编译monitor
					stage('编译monitor'){
						when { expression {MONITOR == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-monitor"
						}
						
						steps{
							container('maven'){
								sh '''
									Build_Path=$(find ./ -type d -name "${AppName}")
									mvn package -Dmaven.test.skip=true -pl ${Build_Path} -am
								'''
							}							
						}
					}
					
					// 5、编译UI
					stage('编译UI'){
						when { expression {UI == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-ui"
						}
						
						steps{
							container('nodejs'){
								sh '''
									cd $(find ./ -type d -name "${AppName}") && \
									npm install --registry=https://registry.npmmirror.com && \
									npm run build:prod
								'''
							}							
						}
					}
					
				} // parallel end
			}//代码编译 stage end
			
			//生产镜像tag
			stage('生产镜像tag'){
				steps{
					container('maven'){
						script{
							//本次git提交的commid   (git log -n1 --pretty=format:'%h')
							env.COMMITID = sh(returnStdout: true, script: "git log -n1 --pretty=format:'%h'").trim()
							//构建时间
							env.BuildTime = sh(returnStdout: true, script: "date +%Y%m%d_%H%M%S").trim()
							//完整的镜像Tag   (c106654_20221115_133911)
							env.ImageTag = COMMITID + "_" + BuildTime
						}
						sh 'echo "镜像的Tag: ${ImageTag}"'
					}
				}
			}//生产镜像tag , stage end
			
			//制作Docker镜像
			stage('制作Docker镜像'){
				parallel{
					//1 gateway
					stage('构建Gateway镜像'){
						when { expression {GATEWAY == "ok" || Deploy_All == "true" } }
						environment{
							AppName = "ruoyi-gateway"
							ImageName = "${Url}/${Pro}/${AppName}"
						}
						steps{
							container('docker'){
								withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
									//登录harbor
									sh 'echo "${HARBOR_PASSWORD}" | docker login ${Url} -u "${HARBOR_USER}" --password-stdin '
									// 构建 、推送 、删除本地镜像
									sh 'cd $(find ./ -type d -name "${AppName}") && docker build -t ${ImageName}:${ImageTag} .'
									sh 'docker push ${ImageName}:${ImageTag}'
									sh 'docker rmi ${ImageName}:${ImageTag}'
								}

							}
						}
					}
					
					//2 system
					stage('构建system镜像'){
						when { expression {SYSTEM == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-system"
							ImageName = "${Url}/${Pro}/${AppName}"
						}
						steps{
							container('docker'){
								withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
									//登录harbor
									sh 'echo "${HARBOR_PASSWORD}" | docker login ${Url} -u "${HARBOR_USER}" --password-stdin '
									// 构建 、推送 、删除本地镜像
									sh 'cd $(find ./ -type d -name "${AppName}") && docker build -t ${ImageName}:${ImageTag} .'
									sh 'docker push ${ImageName}:${ImageTag}'
									sh 'docker rmi ${ImageName}:${ImageTag}'
								}

							}
						}
					}
					
					//3 auth
					stage('构建auth镜像'){
						when { expression {AUTH == "ok" || Deploy_All == "true" } }
						environment{
							AppName = "ruoyi-auth"
							ImageName = "${Url}/${Pro}/${AppName}"
						}
						steps{
							container('docker'){
								withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
									//登录harbor
									sh 'echo "${HARBOR_PASSWORD}" | docker login ${Url} -u "${HARBOR_USER}" --password-stdin '
									// 构建 、推送 、删除本地镜像
									sh 'cd $(find ./ -type d -name "${AppName}") && docker build -t ${ImageName}:${ImageTag} .'
									sh 'docker push ${ImageName}:${ImageTag}'
									sh 'docker rmi ${ImageName}:${ImageTag}'
								}

							}
						}
					}
					
					//4 monitor
					stage('构建monitor镜像'){
						when { expression {MONITOR == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-monitor"
							ImageName = "${Url}/${Pro}/${AppName}"
						}
						steps{
							container('docker'){
								withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
									//登录harbor
									sh 'echo "${HARBOR_PASSWORD}" | docker login ${Url} -u "${HARBOR_USER}" --password-stdin '
									// 构建 、推送 、删除本地镜像
									sh 'cd $(find ./ -type d -name "${AppName}") && docker build -t ${ImageName}:${ImageTag} .'
									sh 'docker push ${ImageName}:${ImageTag}'
									sh 'docker rmi ${ImageName}:${ImageTag}'
								}

							}
						}
					}
					
					//5 ui
					stage('构建ui镜像'){
						when { expression {UI == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-ui"
							ImageName = "${Url}/${Pro}/${AppName}"
						}
						steps{
							container('docker'){
								withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'HARBOR_PASSWORD', usernameVariable: 'HARBOR_USER')]) {
									//登录harbor
									sh 'echo "${HARBOR_PASSWORD}" | docker login ${Url} -u "${HARBOR_USER}" --password-stdin '
									// 构建 、推送 、删除本地镜像
									sh 'cd $(find ./ -type d -name "${AppName}") && docker build -t ${ImageName}:${ImageTag} .'
									sh 'docker push ${ImageName}:${ImageTag}'
									sh 'docker rmi ${ImageName}:${ImageTag}'
								}

							}
						}
					}
					
				} //parallel end
			
			}//制作Docker镜像 stage end
			
			
			//交付至K8s
			stage('交付微服务至k8s'){
				parallel{
					// 1 gateway
					stage('交付Gateway'){
						when { expression {GATEWAY == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-gateway"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 2 交付auth
					stage('交付auth'){
						when { expression {AUTH == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-auth"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 3 交付system
					stage('交付system'){
						when { expression {SYSTEM == "ok" || Deploy_All == "true" } }
						environment{
							AppName = "ruoyi-system"
							ImageName = "$Url/${Pro}/${AppName}"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 4 交付monitor
					stage('交付monitor'){
						when { expression {MONITOR == "ok" || Deploy_All == "true"} }
						environment{
							AppName = "ruoyi-monitor"
							ImageName = "$Url/${Pro}/${AppName}"
							IngressHost = "monitor-dev.oldxu.net"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										sed -i "s#{host}#${IngressHost}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
					// 5 交付UI
					stage('交付UI'){
						when { expression {UI == "ok" || Deploy_All == "true" } }
						environment{
							AppName = "ruoyi-ui"
							ImageName = "$Url/${Pro}/${AppName}"
							IngressHost = "ui-dev.oldxu.net"
						}
						
						steps{
							container('kubectl'){
								withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
									sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
									
									sh '''
										cd $(find ./ -type d -name "${AppName}") && \
										sed -i "s#{namespace}#dev#" deploy.yaml
										sed -i "s#{image}#${ImageName}:${ImageTag}#g" deploy.yaml
										sed -i "s#{host}#${IngressHost}#g" deploy.yaml
										
										cat deploy.yaml
										kubectl apply -f deploy.yaml
									'''
								}

							}
						}
					}
					
				}//parallel end
			
			}//交付至K8s ,stage end
			
			
			
		}//总stages end

}//pipeline end

springcloud-CI阶段 END

十、Jenkins交付微服务-CD流水线设计与实现

		1、拉取harbor中的镜像;
		2、交付到生产环境;
		3、添加回退阶段;
		
可复用上次写的springboot-CD 。 由于环境内存有限,这里就没有部署prod环境下的springcloud ruoyi环境, 仍然使用dev环境。
复用后,需要修改的地方:

在这里插入图片描述
构建界面:
在这里插入图片描述
部署与回滚:
在这里插入图片描述
在这里插入图片描述
回滚:
在这里插入图片描述

10.1 对应的springcloud-ruoyi-CD代码:

pipeline{
    agent{
        kubernetes{
            cloud 'kubernetes'
            yaml '''
              apiVersion: v1
              kind: Pod
              spec:
                imagePullSecrets:
                - name: harbor-admin
                containers:
                - name: kubectl
                  image: harbor.oldxu.net/ops/kubectl:1.18.0
                  imagePullPolicy: IfNotPresent
                  command: ["cat"]
                  tty: true
            '''
        }
    }
    
    environment{
        Full_Image = "${Harbor_Url}/${Harbor_Pro}/${Image_Name}:${Image_Tag}"
       
    }//environment end
    
    stages{
        stage('输出完整的镜像名称'){
            steps{
                sh 'echo 镜像名称-tag: ${Full_Image}'
           

            }
            
        }
        
        stage('部署应用至生产 K8S'){
            steps{
                withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                    container('kubectl'){
                        sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
                        sh 'kubectl set image deployment/${Image_Name} ${Image_Name}=${Full_Image} -n dev'
                    }
                }
    
            
            }
        }
        
        stage('快速回滚'){
            steps{
              withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                container('kubectl'){
                    script{
                        timeout(time: 1, unit: 'HOURS'){
                            def UserInput = input message: '是否回退到上个版本', parameters: [choice(choices: ['No', 'Yes'], name: 'rollback')]
                            if (UserInput == "Yes"){
                               sh 'mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config'
                               sh 'kubectl rollout undo deployment $Image_Name -n dev'
 
                            }
                            
                        }
                    }
                }
              }

            }
        }
        
    }//stages end
    
    
    
}//pipeline end

本文多数是贴代码,沿用了上篇Jenkins文章的基础/操作 https://blog.csdn.net/Nightwish5/article/details/130761785

END

2023年5月24日。 队友崔了,我什么时候提崔呢?88133

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/591854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AUTOSAR】Com通讯栈配置说明(三)---- CanSM模块

CanNm模块 该项目中的Nm 并非 autosar nm, 不适用ETAS 配置 CanSM CanSMConfiguration CanSMModeRequestRepetitionMax: 模式请求失败后最大的重试次数 CanSMModeRequestRepetitionTime:模式请求重试时间间隔 CanSMManagerNetworks CanSMBorCounterL1…

数据结构与算法-递归

2.3 递归 概述 定义 计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集 In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller in…

分享一个下载软件的html

主要是html与js,css实现 页面如下: 点击软件后会滑动到,软件介绍,然后弹出二维码: <!DOCTYPE HTML> <!--HTML for wufuba.comAuthor: www.wufuba.com --> <html lang="zh-cmn-Hans"><head><title>xxx软件</title><m…

Ora提示词版ChatGPT机器人

Ora可以自己创建一个ChatGPT机器人&#xff0c;可以设置自己的提示词例如我创建的AI佛祖https://ora.ai/aesthetic-red-nfa4/ai%E4%BD%9B%E7%A5%96 提示词 创建机器人的时候&#xff0c;需要设定自己的提示词&#xff0c;例如&#xff1a; 假设你是佛祖&#xff0c;名字叫做释迦…

设计模式之~享元模式

定义&#xff1a; 享元模式英文称为“Flyweight Pattern”&#xff0c;又译为羽量级模式或者蝇量级模式。 享元模式&#xff08;Flyweight Pattern&#xff09;主要用于减少创建对象的数量&#xff0c;以减少内存占用和提高性能。这种类型的设计模式属于结构型模式&#xff0c…

git 分支管理:009

1. 分支的(创建、切换、删除) 注意&#xff1a;进行分支操作之前&#xff0c; 需要将当前分支需要提交的文件全部提交&#xff0c; 否则会将未处理的内容一并带到新分支下&#xff0c;这样容易造成分支内容混乱。 查看分支&#xff1a;git branch 创建分支&#xff1a;git bran…

氟化物超标怎么处理

项目基本信息 工艺及产品信息 甲方 / 采用工艺 过滤系统螯合树脂除氟系统 工程公司 / 工艺原理 废水除氟&#xff0c;耐受氯离子、硫酸根等阴离子的干扰 开始时间 2020/11/12 工艺特点 再生周期长 结束时间 2020/11/30 型号 CH-87 项目周期及项目地 15天 江…

力扣高频SQL50题(基础版)——第一天

力扣高频SQL50题(基础版)——第一天 1 可回收且低脂的产品 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # WHERE子句中使用多条件 SELECT product_id FROM Products WHERE low_fatsY AND recyclableY1.3 运行截图 2 寻找用户推荐人 2.1 题目内容…

多家快递如何同时跟踪物流信息并分析出问题件

在这个快节奏的时代&#xff0c;我们经常需要查询快递信息&#xff0c;但是传统的快递查询方式可能需要花费大量的时间和精力。那么&#xff0c;有没有一种更加高效的方法可以同时分析多家快递物流信息并分析出问题呢&#xff1f;答案是肯定的&#xff01;快递批量查询高手应运…

Latex

Latex 文章目录 Latex安装VSCode 配置 latexReference 安装 sudo apt-get install texlive-full # 安装时间有点长,其中的xetex集成了中文字体的环境&#xff0c;使得中文文档的生成变得很容易&#xff0c;可以使用系统自带得字体&#xff0c;使用更好看得字体。 apt-cache se…

Django实现接口自动化平台(三)实现注册功能【持续更新中】

上一章&#xff1a; Django实现接口自动化平台&#xff08;二&#xff09;认证&授权&登录【持续更新中】_做测试的喵酱的博客-CSDN博客 下一章&#xff1a; 一、背景 1.1 实现功能 1、一共提供三个接口&#xff1a; 使用Django 自带的User模型&#xff0c;实现注…

《计算机组成原理》唐朔飞 第7章 指令系统 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习计算机组成原理时的个人笔记&#xff0c;分享出来与大家学习交流。使用教材为唐朔飞第3版&#xff0c;笔记目录大体与教材相同。 网课 计算机组成原理&#xff08;哈工大刘宏伟&#xff09;135讲&#xff08;全&#xff09;高清_…

ChatGPT是什么,一文读懂ChatGPT

ChatGPT是个啥&#xff1f; 近期很多朋友后台私信GPT如何访问&#xff0c;我在网上找到一个免梯子的GPT&#xff0c;使用起来还是挺顺畅的&#xff0c;有需要的可以尝试使用&#xff0c;传送门&#xff0c;界面也挺清新的 近期&#xff0c;OpenAI 发布了 ChatGPT&#xff0c;是…

【JavaSE】Java基础语法(三十八):并发工具类

文章目录 1. Hashtable2. ConcurrentHashMap基本使用3. ConcurrentHashMap1.7原理4. ConcurrentHashMap1.8原理5. CountDownLatch6. Semaphore 1. Hashtable Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象&#xff0c;但是HashMap是线程不安全的(多线程环境下…

chatgpt赋能python:Python中的乘方计算:介绍和应用

Python中的乘方计算&#xff1a;介绍和应用 乘方是数学运算中的重要部分&#xff0c;表示一个数&#xff08;称为底数&#xff09;被另一个数&#xff08;称为指数&#xff09;乘以若干次。在Python编程中&#xff0c;乘方运算也是非常常见的。本文将为您介绍Python中的乘方计…

读数据压缩入门笔记03_VLC

1. 概率、熵与码字长度 1.1. 数据压缩的目的 1.1.1. 给定一个数据集中的符号&#xff0c;将最短的编码分配给最可能出现的符号 1.2 1.2.1. 当P(A)P(B)&#xff0c;也就是两个符号等可能出现时&#xff0c;数据集对应的熵取最大值LOG2&#xff08;符号的个数&#xff09;&…

设计模式之~组合模式

组合模式&#xff1a; 将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 结构图&#xff1a; 实例&#xff1a; 透明方式&#xff1a; leaf中也有add和remove叫做透明方式&#xff0c;在component中声明所有用来管…

数组及详解冒泡排序

数组及详解冒泡排序 一维数组的创建和初始化一维数组的创建一维数组的初始化一维数组的应用一维数组在内存中的存储 二维数组的创建和初始化二维数组的创建二维数组的初始化二维数组的应用二维数组在内存中的存储 数组越界问题数组作为函数参数数组名的含义及特殊两个例子 冒泡…

PACS影像解决方案

现代医学影像技术的迅猛发展&#xff0c;使得PACS系统已逐渐成为各级医院实现信息化建设的重要组成部分。医学影像技术的进步也提升了医学影像的清晰度&#xff0c;推动二维PACS向三维升级转变。这一切都使得医学影像数据量激增&#xff0c;加之医疗行业法规的数据保存要求&…

对DataFrame指定字段进行整数编码df[‘字段名称‘].factorize()[0]

【小白从小学Python、C、Java】 【等级考试500强双证书考研】 【Python-数据分析】 对DataFrame指定字段进行整数编码 df[字段名称].factorize()[0] 选择题 关于以下代码说法错误的是&#xff1a; import pandas as pd myData pd.DataFrame({编码前: [A, B, C, A, B]}) …