公众号关注 「奇妙的 Linux 世界」
设为「星标」,每天带你玩转 Linux !
案例介绍
本案例通过若依项目作为案例,通过Jenkins构建企业级CI/CD平台。
若依服务列表:
ruoyi-auth
ruoyi-system
ruoyi-gateway
ruoyi-ui
若依环境列表:
DEV
UAT
PROD
环境准备工作:
nacos安装并配置完成
MySQL部署完成并初始化
Redis部署完成
Harbor镜像仓库
Gitlab部署完成
Kubernetes部署完成
Ingress部署完成
设计思路
触发构建设计:
本设计通过Jenkins Generic Webhook Trigger 插件实现了基于Webhook自动触发流水线构建。
![16e208785737fa999204f75f1ed18222.png](https://img-blog.csdnimg.cn/img_convert/16e208785737fa999204f75f1ed18222.png)
流程说明:
研发项目负责人代码开发完成后进行合并代码并生成Tag
Gitlab通过Webhook自动触发Jenkins Pipeline构建
流水线设计:
![baab779f98a115504d0cecc406a55daa.png](https://img-blog.csdnimg.cn/img_convert/baab779f98a115504d0cecc406a55daa.png)
Jenkins流水线完整图:
![8f95ef430c9cbf5c77ea6202d2ce7698.png](https://img-blog.csdnimg.cn/img_convert/8f95ef430c9cbf5c77ea6202d2ce7698.png)
自定义基础镜像
在实际企业环境中,基础镜像都会根据具体得需求定义适合自己得基础镜像。
定义Maven镜像:
用于代码构建编译打包,会把Ruoyi相关依赖包打到基础镜像内,避免分层构建失败。
# 拉取源代码并切换分支
$ https://gitee.com/y_project/RuoYi-Cloud.git
$ git checkout v3.6.3
$ cd ..
# 定义Dockerfile
$ cat Dockerfile
FROM maven:3.8.6-openjdk-8
ADD RuoYi-Cloud /opt/RuoYi-Cloud
RUN cd /opt/RuoYi-Cloud && mvn clean install -DskipTests
RUN rm -rf /opt/RuoYi-Cloud
# 构建镜像
$ docker build uhub.service.ucloud.cn/kubesre/maven:jdk8 .
$ docker push uhub.service.ucloud.cn/kubesre/maven:jdk8
定义Java基础镜像:
根据需求定义适合自己的基础镜像。通过变量传递让配置变得更灵活!
# 创建个目录
$ mkdir base && cd base
# 创建启动脚本
$ cat docker-entrypoint.sh
#!/bin/sh
java -server -Xms$JVM_XMS -Xmx$JVM_XMX -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/heapdump.hprof -jar app.jar --server.port=$SERVICE_PORT --spring.profiles.active=$PROFILES_ACTIVE --spring.cloud.nacos.config.server-addr=$NACOS_ADDRESS --spring.cloud.nacos.config.namespace=$NACOS_NAMESPACE_ID --spring.cloud.nacos.config.username=$NACOS_USERNAME --spring.cloud.nacos.config.password=$NACOS_PASSWORD --spring.cloud.nacos.discovery.server-addr=$NACOS_ADDRESS --spring.cloud.nacos.discovery.namespace=$NACOS_NAMESPACE_ID --spring.cloud.nacos.discovery.username=$NACOS_USERNAME --spring.cloud.nacos.discovery.password=$NACOS_PASSWORD
# 创建down-nacos脚本
cat down-nacos.sh
#!/bin/sh
ipTrue=false
java_service_ip=""
code=false
getPodIp() {
java_service_ip=`ip a | grep inet | grep -v inet6 | grep -v '127.0.0.1' | awk '{print $2}' | awk -F / '{print$1}'`
grep -w "${java_service_ip}" /etc/hosts > /dev/null
if [ $? -eq 0 ];then
echo "get java service ip success"
ipTrue=true
else
echo "get java service ip failed"
fi
}
downService(){
accessToken=`curl -s -X POST http://$NACOS_ADDRESS/nacos/v1/auth/users/login --form username=$NACOS_USERNAME --form password=$NACOS_PASSWORD|jq -r .accessToken`
curl -s -X PUT "$NACOS_ADDRESS/nacos/v1/ns/instance?language=zh-CN&accessToken=$accessToken&username=$NACOS_USERNAME&serviceName=$JAVA_SERVICE_NAME&ip=$java_service_ip&port=$SERVICE_PORT&enabled=false&namespaceId=$NACOS_NAMESPACE_ID"
if [ "$code" = "ok" ];then
echo "java service down from nacos success"
code=true
else
echo "java service down from nacos failed"
fi
}
start(){
getPodIp
if $ipTrue;then
downService
sleep 30
else
echo "down $JAVA_SERVICE_NAME failed" >> down_service.log
fi
}
start
# 定义Dockerfile
$ cat Dockerfile
FROM openjdk:8-jre
WORKDIR /data
COPY ./down-nacos.sh .
COPY ./docker-entrypoint.sh .
RUN chmod +x docker-entrypoint.sh && chmod +x down-nacos.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
# 构建镜像
$ docker build uhub.service.ucloud.cn/kubesre/java-base:v8 .
$ docker push uhub.service.ucloud.cn/kubesre/java-base:v8
变量说明:
JVM_XMS:最小JVM堆栈内存
JVM_XMX:最大JVM堆栈内存
SERVICE_PORT:应用服务端口
NACOS_ADDRESS:Nacos地址
NACOS_USERNAME:Nacos用户名
NACOS_PASSWORD:Nacos密码
NACOS_NAMESPACE_ID:Nacos命名空间ID
PROFILES_ACTIVE:环境名称
Dockerfile编写
分层构建好处:
不依赖本地环境
减小容器镜像大小
Java Dockerfile(分层构建):
FROM uhub.service.ucloud.cn/kubesre/maven:jdk8 AS build
COPY src /opt/src/
COPY pom.xml /opt/
RUN cd /opt/ && mvn clean install -DskipTests
FROM uhub.service.ucloud.cn/kubesre/java-base:v8
# 复制jar文件到路径
COPY --from=build /opt/target/*.jar /data/app.jar
Vue Dockerfile(分层构建):
FROM node:16 AS builder
# 设置工作目录
WORKDIR /usr/src/app
# 将项目文件复制到 Docker 镜像中
COPY . .
# 安装项目依赖
RUN npm install --registry=https://registry.npmmirror.com
# 构建静态文件
RUN npm run build:prod
# 使用 Nginx 镜像作为基础镜像,用于托管静态文件
FROM nginx:stable-alpine
WORKDIR /home/ruoyi/projects/ruoyi-ui
# 将 VuePress 构建的静态文件复制到 Nginx 的网站目录
COPY --from=builder /usr/src/app/dist /home/ruoyi/projects/ruoyi-ui
COPY ./nginx/conf/nginx.conf /etc/nginx/nginx.conf
# 暴露 80 端口
EXPOSE 80
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
Pipeline编写
如下所有Pipeline文件,需要自行修改内容:
credentialsId
robot
镜像仓库地址
如何查找credentialsId:
![5b1c10185d6fce40743afae9bf0be8f8.png](https://img-blog.csdnimg.cn/img_convert/5b1c10185d6fce40743afae9bf0be8f8.png)
如何查找robot:
![ecd9d2c4a3b9a7ce459c75230daf9c0f.png](https://img-blog.csdnimg.cn/img_convert/ecd9d2c4a3b9a7ce459c75230daf9c0f.png)
Java Pipeline:
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'ref', value: '$.ref'], //获取分支
[key: 'user_username', value: '$.user_username'], //获取自动构建用户名
[key: 'GitRepository', value: '$.project.git_http_url'], //获取gitlab ssh项目地址
[key: 'project', value: '$.project.name'], //获取项目名称
[key: 'repository', value: '$.repository.name'],
],
token: "$JOB_NAME",
causeString: 'Triggered on $branch',
printContributedVariables: true,
printPostContent: true,
silentResponse: false,
)
}
environment {
// pipeline配置路径
pipeline_dir="/var/lib/jenkins/workspace/pipeline"
// 项目版本
Tag=sh(script: 'echo "${ref}" | awk -F"/" \'{print $3}\'', returnStdout: true).trim()
// 项目名称
Project_Name="${project}"
// 上一次版本
Revsion_Prod=''
//Depolyment名称
DeploymentName=''
// 生产名称空间
Namespace_Prod=''
// 灰度模式
GrayHeaderMode=''
// 灰度Depolyment名称
GrayDeploymentName=''
// 灰度Service名称
GrayServiceName=''
// 灰度Ingress名称
GrayIngressName=''
// 是否灰度
GrayEnable='yes'
}
options {
// 表示保留10次构建历史
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Pull Code') {
// 拉取代码
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "$ref"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
userRemoteConfigs: [[
credentialsId: 'ac66550d-6999-485c-af3a-7e6189f765f0',
url: "$GitRepository"
]]
])
script{
currentBuild.displayName = "#${BUILD_NUMBER} - ${Project_Name} - ${Tag}"
}
}
}
// // 代码构建
// stage('Build Code') {
// steps {
// sh '/application/maven/bin/mvn -f pom.xml -s settings.xml clean package -DskipTests'
// }
// }
// 镜像构建
stage('Build Image') {
steps {
sh '''
/usr/bin/docker build -t uhub.service.ucloud.cn/kubesre/$Project_Name:$Tag .
/usr/bin/docker push uhub.service.ucloud.cn/kubesre/$Project_Name:$Tag
'''
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "📢 Jenkins 镜像构建成功",
text: [
"📋 **任务名称**:[${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **构建状态**: <font color='green'>成功</font>",
"🤩 **镜像版本**: $Tag",
"😎 **镜像仓库**: uhub.service.ucloud.cn/kubesre/$Project_Name",
"🕐 **构建用时**: ${currentBuild.duration} ms",
"👤 **执 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
stage('DeployDev'){
steps {
echo "部署开发环境"
script {
def userInput = input (
message: '确定要发布到DEV环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "部署Dev环境开始"
sh '''
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/dev/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/dev/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/dev/$Project_Name/service.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/service.yml
cat $pipeline_dir/dev/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/dev/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/ingress.yml
cat $pipeline_dir/dev/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
} else {
echo "不发布"
}
}
}
}
stage('DeployUat'){
steps {
echo "部署测试环境"
script {
def userInput = input (
message: '确定要发布到UAT环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "发布"
sh '''
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/uat/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/uat/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/uat/$Project_Name/service.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/service.yml
cat $pipeline_dir/uat/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/uat/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/ingress.yml
cat $pipeline_dir/uat/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
} else {
echo "不发布"
}
}
}
}
stage('DeployGray'){
steps {
echo "部署灰度环境"
script {
def GraysMode = input (
message: '确定要灰度验证吗?',
parameters:[
choice(name: 'operation', choices: ['基于权重灰度','基于请求头灰度','跳过'])
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (GraysMode['operation'] == '基于权重灰度'){
def WeightMode = input (
message: '请输入权重比例!',
parameters:[
string(name: 'workload_weight',defaultValue: '',description: ''),
string(name: 'grayload_weight',defaultValue: '',description: '')
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
sh """
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/prod/$Project_Name/deployment-gray.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
echo "配置权重"
echo ${WeightMode['grayload_weight']}
if [ -e "$pipeline_dir/prod/$Project_Name/ingress-gray-weight.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/ingress-gray-weight.yml | sed "s/WEIGHT-VALUE/${WeightMode['grayload_weight']}/g"
cat $pipeline_dir/prod/$Project_Name/ingress-gray-weight.yml | sed "s/WEIGHT-VALUE/${WeightMode['grayload_weight']}/g" | /usr/bin/kubectl apply -f -
fi
"""
}
if (GraysMode['operation'] == '基于请求头灰度'){
GrayHeaderMode = input (
message: '请输入请求头!',
parameters:[
string(name: 'header_key',defaultValue: '',description: ''),
string(name: 'header_value',defaultValue: '',description: '')
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
sh """
echo ${GrayHeaderMode['header_value']}
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/prod/$Project_Name/deployment-gray.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
echo "配置请求头"
echo ${GrayHeaderMode['header_key']}
echo ${GrayHeaderMode['header_value']}
if [ -e "$pipeline_dir/prod/$Project_Name/ingress-gray-header.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/ingress-gray-header.yml | sed "s/header-key/${GrayHeaderMode['header_key']}/g" | sed "s/header-value/${GrayHeaderMode['header_value']}/g"
cat $pipeline_dir/prod/$Project_Name/ingress-gray-header.yml | sed "s/header-value/${GrayHeaderMode['header_key']}/g" | sed "s/header-value/${GrayHeaderMode['header_value']}/g" | /usr/bin/kubectl apply -f -
fi
"""
}
// 默认模式为yes,如果跳过为no
if (GraysMode['operation'] == '跳过'){
GrayEnable='no'
}
}
}
}
stage('DeployProd'){
steps {
echo "部署生产环境"
script {
def userInput = input (
message: '确定要发布到生产环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'test',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "发布"
Namespace_Prod = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment.yml | grep namespace | awk -F ':' '{print \$2}'", returnStdout: true).trim()
DeploymentName = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
Revsion_Prod = sh(script: "kubectl get deployment $DeploymentName -n ${Namespace_Prod} -o=jsonpath='{.spec.template.spec.containers[*].image}' | awk -F ':' '{print \$NF}'", returnStdout: true).trim()
GrayDeploymentName = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
GrayServiceName = sh(script: "cat $pipeline_dir/prod/$Project_Name/service-gray.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
GrayIngressName = sh(script: "cat $pipeline_dir/prod/$Project_Name/ingress-gray-header.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
sh '''
echo $pipeline_dir
echo "开始部署生产环境"
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/prod/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/prod/$Project_Name/service.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/service.yml
cat $pipeline_dir/prod/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/prod/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/ingress.yml
cat $pipeline_dir/prod/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
if (GrayEnable == 'yes'){
sh """
kubectl delete deployment ${GrayDeploymentName} -n ${Namespace_Prod}
kubectl delete service ${GrayServiceName} -n ${Namespace_Prod}
kubectl delete ingress ${GrayIngressName} -n ${Namespace_Prod}
"""
}
} else {
echo "不发布"
}
}
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "📢 Jenkins 应用发布成功",
text: [
"😋 **应用名称**:[${JOB_NAME}](${JOB_URL})",
"😜 **应用环境**:Prod",
"🤪 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"😘 **发布状态**: <font color='green'>成功</font>",
"🤩 **镜像版本**: $Tag",
"😎 **镜像仓库**: harbor.kubesre.com:8443/kubesre/$Project_Name",
"😝 **执 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
stage('RollBack'){
steps {
echo "版本回滚操作"
script {
def RollBack = input (
message: '确定要执行回滚操作吗?',
parameters:[
choice(name: '是否回滚', choices: ['是', '否']),
string(name: '回滚版本', defaultValue: Revsion_Prod,description: '默认上一个版本')
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (RollBack['是否回滚'] == '是'){
echo "版本回滚成功"
echo RollBack['回滚版本']
sh """
if [ -e "$pipeline_dir/prod/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${RollBack['回滚版本']}/g"
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${RollBack['回滚版本']}/g" | /usr/bin/kubectl apply -f -
fi
"""
} else {
echo "放弃版本回滚"
echo RollBack['回滚版本']
}
}
}
}
}
}
Vue Pipeline:
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'ref', value: '$.ref'], //获取分支
[key: 'user_username', value: '$.user_username'], //获取自动构建用户名
[key: 'GitRepository', value: '$.project.git_http_url'], //获取gitlab ssh项目地址
[key: 'project', value: '$.project.name'], //获取项目名称
[key: 'repository', value: '$.repository.name'],
],
token: "$JOB_NAME",
causeString: 'Triggered on $branch',
printContributedVariables: true,
printPostContent: true,
silentResponse: false,
)
}
environment {
// pipeline配置路径
pipeline_dir="/var/lib/jenkins/workspace/pipeline"
// 项目版本
Tag=sh(script: 'echo "${ref}" | awk -F"/" \'{print $3}\'', returnStdout: true).trim()
// 项目名称
Project_Name="${project}"
// 上一次版本
Revsion_Prod=''
//Depolyment名称
DeploymentName=''
// 生产名称空间
Namespace_Prod=''
// 灰度模式
GrayHeaderMode=''
// 灰度Depolyment名称
GrayDeploymentName=''
// 灰度Service名称
GrayServiceName=''
// 灰度Ingress名称
GrayIngressName=''
// 是否灰度
GrayEnable='yes'
}
options {
// 表示保留10次构建历史
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('Pull Code') {
// 拉取代码
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "$ref"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
userRemoteConfigs: [[
credentialsId: 'ac66550d-6999-485c-af3a-7e6189f765f0',
url: "$GitRepository"
]]
])
script{
currentBuild.displayName = "#${BUILD_NUMBER} - ${Project_Name} - ${Tag}"
}
}
}
// // 代码构建
// stage('Build Code') {
// steps {
// sh '/application/maven/bin/mvn -f pom.xml -s settings.xml clean package -DskipTests'
// }
// }
// 镜像构建
stage('Build Image') {
steps {
sh '''
/usr/bin/docker build -t uhub.service.ucloud.cn/kubesre/$Project_Name:$Tag .
/usr/bin/docker push uhub.service.ucloud.cn/kubesre/$Project_Name:$Tag
'''
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "📢 Jenkins 镜像构建成功",
text: [
"📋 **任务名称**:[${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **构建状态**: <font color='green'>成功</font>",
"🤩 **镜像版本**: $Tag",
"😎 **镜像仓库**: uhub.service.ucloud.cn/kubesre/$Project_Name",
"🕐 **构建用时**: ${currentBuild.duration} ms",
"👤 **执 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
stage('DeployDev'){
steps {
echo "部署开发环境"
script {
def userInput = input (
message: '确定要发布到DEV环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "部署Dev环境开始"
sh '''
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/dev/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/dev/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/dev/$Project_Name/service.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/service.yml
cat $pipeline_dir/dev/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/dev/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/dev/$Project_Name/ingress.yml
cat $pipeline_dir/dev/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
} else {
echo "不发布"
}
}
}
}
stage('DeployUat'){
steps {
echo "部署测试环境"
script {
def userInput = input (
message: '确定要发布到UAT环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "发布"
sh '''
echo $pipeline_dir
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/uat/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/uat/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/uat/$Project_Name/service.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/service.yml
cat $pipeline_dir/uat/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/uat/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/uat/$Project_Name/ingress.yml
cat $pipeline_dir/uat/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
} else {
echo "不发布"
}
}
}
}
stage('DeployProd'){
steps {
echo "部署生产环境"
script {
def userInput = input (
message: '确定要发布到生产环境吗?',
parameters:[
choice(name: '操作', choices: ['发布', '跳过'])
],
ok: '确定',
submitter: 'test',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '发布'){
echo "发布"
Namespace_Prod = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment.yml | grep namespace | awk -F ':' '{print \$2}'", returnStdout: true).trim()
DeploymentName = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
Revsion_Prod = sh(script: "kubectl get deployment $DeploymentName -n ${Namespace_Prod} -o=jsonpath='{.spec.template.spec.containers[*].image}' | awk -F ':' '{print \$NF}'", returnStdout: true).trim()
GrayDeploymentName = sh(script: "cat $pipeline_dir/prod/$Project_Name/deployment-gray.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
GrayServiceName = sh(script: "cat $pipeline_dir/prod/$Project_Name/service-gray.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
GrayIngressName = sh(script: "cat $pipeline_dir/prod/$Project_Name/ingress-gray-header.yml | grep name: | head -n 1 | awk -F ':' '{print \$2}'", returnStdout: true).trim()
sh '''
echo $pipeline_dir
echo "开始部署生产环境"
echo "打印编排文件详细信息"
if [ -e "$pipeline_dir/prod/$Project_Name/deployment.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g"
cat $pipeline_dir/prod/$Project_Name/deployment.yml | sed "s/TAG/${Tag}/g" | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/prod/$Project_Name/service.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/service.yml
cat $pipeline_dir/prod/$Project_Name/service.yml | /usr/bin/kubectl apply -f -
fi
if [ -e "$pipeline_dir/prod/$Project_Name/ingress.yml" ]; then
cat $pipeline_dir/prod/$Project_Name/ingress.yml
cat $pipeline_dir/prod/$Project_Name/ingress.yml | /usr/bin/kubectl apply -f -
fi
'''
} else {
echo "不发布"
}
}
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "📢 Jenkins 应用发布成功",
text: [
"😋 **应用名称**:[${JOB_NAME}](${JOB_URL})",
"😜 **应用环境**:Prod",
"🤪 **任务编号**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"😘 **发布状态**: <font color='green'>成功</font>",
"🤩 **镜像版本**: $Tag",
"😎 **镜像仓库**: harbor.kubesre.com:8443/kubesre/$Project_Name",
"😝 **执 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改记录",
url: "${BUILD_URL}changes"
],
[
title: "控制台",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
}
}
配置Jenkins
依赖的组件(自行安装):
Generic Webhook Trigger
Pipeline(所有以Pipeline开头的组件)
build user vars
Blue Ocean
Lark Notice(通过上传文件的方式安装)https://721806280.github.io/lark-notice-plugin-doc/
配置Lark Notice:
Lark Notice Plugin 是一个用于 Jenkins 的构建通知插件,可以将 Jenkins构建过程以及结果通知推送到 Lark、飞书、钉钉 协作平台。 可配置多个的通知时机,包括 构建启动时、构建中断、构建失败、构建成功时、构建不稳定 等。 支持多种不同类型的消息,包括 文本消息、图片消息, 群名片消息、富文本消息、卡片消息; 同时该插件还提供了自定义模板和变量的功能,使您能够根据自己的需求来定制通知消息的内容和格式。(本次案例是基于飞书进行验证)
准备工作,在飞书群新建一个机器人(https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot):
在飞书群,点击设置:
![42c6dc521ce5735cbfc697baea9397d1.png](https://img-blog.csdnimg.cn/img_convert/42c6dc521ce5735cbfc697baea9397d1.png)
然后点击群机器人:
![0a1c40017c5156cdf1893e061f34c24a.png](https://img-blog.csdnimg.cn/img_convert/0a1c40017c5156cdf1893e061f34c24a.png)
在飞书群,选择添加机器人
![a52c2e18a4bdeff4620defe70ce57e84.png](https://img-blog.csdnimg.cn/img_convert/a52c2e18a4bdeff4620defe70ce57e84.png)
填写相应配置信息并点击保存:
![5bc20d7a453adc77fefb29c7b5043afb.png](https://img-blog.csdnimg.cn/img_convert/5bc20d7a453adc77fefb29c7b5043afb.png)
![ec262fc9771d08731a714304969db5cd.png](https://img-blog.csdnimg.cn/img_convert/ec262fc9771d08731a714304969db5cd.png)
选择系统管理-Lark Notice:
通知时机全部勾选:
![694f06c805362972305d8349ade6e362.png](https://img-blog.csdnimg.cn/img_convert/694f06c805362972305d8349ade6e362.png)
配置机器人信息并保存
![efc725e942d1a80204061c7091de47ae.png](https://img-blog.csdnimg.cn/img_convert/efc725e942d1a80204061c7091de47ae.png)
配置Java Pipeline
新建任务:
![8473a02810d55e7ce127437393c10b68.png](https://img-blog.csdnimg.cn/img_convert/8473a02810d55e7ce127437393c10b68.png)
填写任务名称,并选择流水线,点击确定:
![1b62df41758b8d8d2edcc75400a61210.png](https://img-blog.csdnimg.cn/img_convert/1b62df41758b8d8d2edcc75400a61210.png)
配置Pipeline SCM:
![390d9a5d77fe9c0eda3f150f782e4c44.png](https://img-blog.csdnimg.cn/img_convert/390d9a5d77fe9c0eda3f150f782e4c44.png)
修改脚本路径,点击确定:
![7755f131135d27dda5e2cada78bb5b99.png](https://img-blog.csdnimg.cn/img_convert/7755f131135d27dda5e2cada78bb5b99.png)
点击构建让配置生效:
![1930bf31c8e814594b32f06517054af5.png](https://img-blog.csdnimg.cn/img_convert/1930bf31c8e814594b32f06517054af5.png)
其他Java项目配置都一样!
配置Vue Pipeline
新建任务:
![102525fc31c64a5e4118c9a093b45882.png](https://img-blog.csdnimg.cn/img_convert/102525fc31c64a5e4118c9a093b45882.png)
填写任务名称,选择流水线:
![2eba133f4d42de9267e101d313094940.png](https://img-blog.csdnimg.cn/img_convert/2eba133f4d42de9267e101d313094940.png)
配置Pipeline SCM:
![e1c77e105dec991ca0525f47a93f3c82.png](https://img-blog.csdnimg.cn/img_convert/e1c77e105dec991ca0525f47a93f3c82.png)
修改脚本路径,点击确定:
![e1705e5a2466557a7b0d0097a9db051a.png](https://img-blog.csdnimg.cn/img_convert/e1705e5a2466557a7b0d0097a9db051a.png)
点击构建让配置生效:
![b13e0c0290c0ffce47d3efff1b7e6952.png](https://img-blog.csdnimg.cn/img_convert/b13e0c0290c0ffce47d3efff1b7e6952.png)
其他Vue项目配置都一样!
配置Gitlab Webhook
进入项目,选择webhook:
![1f5f89d7b6d3cd566f57b1c87a07cf92.png](https://img-blog.csdnimg.cn/img_convert/1f5f89d7b6d3cd566f57b1c87a07cf92.png)
选择添加Webhook:
![c08d74d7bc9a768eb12b135c14df1f99.png](https://img-blog.csdnimg.cn/img_convert/c08d74d7bc9a768eb12b135c14df1f99.png)
配置webhook URL,Token以Job Name:
![32ccb2a0fc7dca55ef25f91f4d5d6cd7.png](https://img-blog.csdnimg.cn/img_convert/32ccb2a0fc7dca55ef25f91f4d5d6cd7.png)
勾选标签推送事件,也就是说只有标签推送事件才会触发流水线:
![89acb8c00563e5ee4c59654ed285d8d8.png](https://img-blog.csdnimg.cn/img_convert/89acb8c00563e5ee4c59654ed285d8d8.png)
到此为止,Webook已配置完毕!所有项目配置都一样
触发验证
触发Java Pipeline:
进入标签管理:
![763ed1078b0707756698d135a9796c89.png](https://img-blog.csdnimg.cn/img_convert/763ed1078b0707756698d135a9796c89.png)
新建标签:
![56d29c648655b5a5e6194b0488f3f32a.png](https://img-blog.csdnimg.cn/img_convert/56d29c648655b5a5e6194b0488f3f32a.png)
填写信息并点击创建标签(此标签名称也是容器镜像的Tag):
![43d5663aa7c2901f48ff80d58d79833b.png](https://img-blog.csdnimg.cn/img_convert/43d5663aa7c2901f48ff80d58d79833b.png)
进入Jenkins可以看到Gateway Pipeline已经触发了:
![b1b937bf41b70cebccd03c7cc1eb6261.png](https://img-blog.csdnimg.cn/img_convert/b1b937bf41b70cebccd03c7cc1eb6261.png)
![c322d4a0b71041c7bee9559f69d59f65.png](https://img-blog.csdnimg.cn/img_convert/c322d4a0b71041c7bee9559f69d59f65.png)
选择发布,并点击确定,将新版本发布到Dev环境:
![20e37aabc7240d4f8622d5fb0a891862.png](https://img-blog.csdnimg.cn/img_convert/20e37aabc7240d4f8622d5fb0a891862.png)
选择发布,并点击确定,将新版本发布到Uat环境:
![cdf78d03006629ca22d249d64c200153.png](https://img-blog.csdnimg.cn/img_convert/cdf78d03006629ca22d249d64c200153.png)
选择对应的灰度发布方式或者跳过:
![966219ab7ba5260de9f7bda705b053e3.png](https://img-blog.csdnimg.cn/img_convert/966219ab7ba5260de9f7bda705b053e3.png)
选择发布,并点击确定,将新版本发布到Prod环境:
![9f750d08ba02108af03b4936b97876d2.png](https://img-blog.csdnimg.cn/img_convert/9f750d08ba02108af03b4936b97876d2.png)
也可以回滚,默认是上一个版本也可修改成想要回滚到的版本:
![f36724651cd09f69211721f6733a4a1d.png](https://img-blog.csdnimg.cn/img_convert/f36724651cd09f69211721f6733a4a1d.png)
触发 Vue流水线:
进入标签管理:
![41a633834517da635c686f2485af3ad5.png](https://img-blog.csdnimg.cn/img_convert/41a633834517da635c686f2485af3ad5.png)
创建标签:
![d965f123d784af364dad4dfffb242676.png](https://img-blog.csdnimg.cn/img_convert/d965f123d784af364dad4dfffb242676.png)
填写信息并点击创建标签(此标签名称也是容器镜像的Tag):
![7a74b2cb07d6be881f6cb29e14476035.png](https://img-blog.csdnimg.cn/img_convert/7a74b2cb07d6be881f6cb29e14476035.png)
![7276b95ec52b6511a742ce9a992cd69d.png](https://img-blog.csdnimg.cn/img_convert/7276b95ec52b6511a742ce9a992cd69d.png)
进入Jenkins可以看到ruoyi-ui Pipeline已经触发了:
![95792c324ccaaec3d70ef5bc260c230d.png](https://img-blog.csdnimg.cn/img_convert/95792c324ccaaec3d70ef5bc260c230d.png)
选择发布,并点击确定,将新版本发布到Dev环境:
![ba1cf4ac9f1f8ec53031acd067586435.png](https://img-blog.csdnimg.cn/img_convert/ba1cf4ac9f1f8ec53031acd067586435.png)
选择发布,并点击确定,将新版本发布到Uat环境:
![31965edfcdc6cc86f8e91a366317634b.png](https://img-blog.csdnimg.cn/img_convert/31965edfcdc6cc86f8e91a366317634b.png)
选择发布,并点击确定,将新版本发布到Prod环境:
![a474491f1452cc7f414d53e3b61af601.png](https://img-blog.csdnimg.cn/img_convert/a474491f1452cc7f414d53e3b61af601.png)
构建通知
![ce0c59cd6a26ccf8ad115f821044f74a.png](https://img-blog.csdnimg.cn/img_convert/ce0c59cd6a26ccf8ad115f821044f74a.png)
本文转载自:「云原生运维圈」,原文:https://url.hi-linux.com/FcL4P,版权归原作者所有。欢迎投稿,投稿邮箱: editor@hi-linux.com。
🚀 最近,我们建立了一个技术交流微信群。目前群里已加入了不少行业内的大神,有兴趣的同学可以加入和我们一起交流技术,在 「奇妙的 Linux 世界」 公众号直接回复 「加群」 邀请你入群。
📕 关注『奇妙的 Linux 世界』公众号,带你开启有趣新生活!更多好用好玩的软件资源,可访问 https://666666.dev 免费获取。
你可能还喜欢
点击下方图片即可阅读
Github 星标 5.2K: 一款开源、自托管、高度定制化的仪表面板,助你轻松管理所有订阅源
点击上方图片,『美团|饿了么』外卖红包天天免费领
更多有趣的互联网新鲜事,关注「奇妙的互联网」视频号全了解!