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

一、运行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代码

   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()
            //构建的时间   (date +%Y%m%d_%H%M%S)
            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}"'
        }
      }
    }

stage('制作Docker镜像'){

      parallel{
        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}' 
              }
            }
          }
        }

        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}' 
              }
            }
          }
        }

        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}' 
              }
            }
          }
        }

        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}' 
              }
            }
          }
        }

        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}' 
              }
            }
          }
        }



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

七、部署至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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值