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