基于Jenkins Pipeline構(gòu)建企業(yè)級CI/CD
案例介紹
本案例通過若依項目作為案例,通過Jenkins構(gòu)建企業(yè)級CI/CD平臺。
若依服務(wù)列表:
- ruoyi-auth
- ruoyi-system
- ruoyi-gateway
- ruoyi-ui
若依環(huán)境列表:
- DEV
- UAT
- PROD
環(huán)境準(zhǔn)備工作:
- nacos安裝并配置完成
- MySQL部署完成并初始化
- Redis部署完成
- Harbor鏡像倉庫
- Gitlab部署完成
- Kubernetes部署完成
- Ingress部署完成
設(shè)計思路
觸發(fā)構(gòu)建設(shè)計:
本設(shè)計通過Jenkins Generic Webhook Trigger 插件實(shí)現(xiàn)了基于Webhook自動觸發(fā)流水線構(gòu)建。
圖片
流程說明:
- 研發(fā)項目負(fù)責(zé)人代碼開發(fā)完成后進(jìn)行合并代碼并生成Tag
- Gitlab通過Webhook自動觸發(fā)Jenkins Pipeline構(gòu)建
流水線設(shè)計:
圖片
Jenkins流水線完整圖:
圖片
自定義基礎(chǔ)鏡像
在實(shí)際企業(yè)環(huán)境中,基礎(chǔ)鏡像都會根據(jù)具體得需求定義適合自己得基礎(chǔ)鏡像。
定義Maven鏡像:
用于代碼構(gòu)建編譯打包,會把Ruoyi相關(guān)依賴包打到基礎(chǔ)鏡像內(nèi),避免分層構(gòu)建失敗。
# 拉取源代碼并切換分支
$ 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
# 構(gòu)建鏡像
$ docker build uhub.service.ucloud.cn/kubesre/maven:jdk8 .
$ docker push uhub.service.ucloud.cn/kubesre/maven:jdk8
定義Java基礎(chǔ)鏡像:
根據(jù)需求定義適合自己的基礎(chǔ)鏡像。通過變量傳遞讓配置變得更靈活!
# 創(chuàng)建個目錄
$ mkdir base && cd base
# 創(chuàng)建啟動腳本
$ 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
# 創(chuàng)建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"]
# 構(gòu)建鏡像
$ docker build uhub.service.ucloud.cn/kubesre/java-base:v8 .
$ docker push uhub.service.ucloud.cn/kubesre/java-base:v8
變量說明:
- JVM_XMS:最小JVM堆棧內(nèi)存
- JVM_XMX:最大JVM堆棧內(nèi)存
- SERVICE_PORT:應(yīng)用服務(wù)端口
- NACOS_ADDRESS:Nacos地址
- NACOS_USERNAME:Nacos用戶名
- NACOS_PASSWORD:Nacos密碼
- NACOS_NAMESPACE_ID:Nacos命名空間ID
- PROFILES_ACTIVE:環(huán)境名稱
Dockerfile編寫
分層構(gòu)建好處:
- 不依賴本地環(huán)境
- 減小容器鏡像大小
Java Dockerfile(分層構(gòu)建):
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
# 復(fù)制jar文件到路徑
COPY --from=build /opt/target/*.jar /data/app.jar
Vue Dockerfile(分層構(gòu)建):
FROM node:16 AS builder
# 設(shè)置工作目錄
WORKDIR /usr/src/app
# 將項目文件復(fù)制到 Docker 鏡像中
COPY . .
# 安裝項目依賴
RUN npm install --registry=https://registry.npmmirror.com
# 構(gòu)建靜態(tài)文件
RUN npm run build:prod
# 使用 Nginx 鏡像作為基礎(chǔ)鏡像,用于托管靜態(tài)文件
FROM nginx:stable-alpine
WORKDIR /home/ruoyi/projects/ruoyi-ui
# 將 VuePress 構(gòu)建的靜態(tài)文件復(fù)制到 Nginx 的網(wǎng)站目錄
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文件,需要自行修改內(nèi)容:
- credentialsId
- robot
- 鏡像倉庫地址
如何查找credentialsId:
圖片
如何查找robot:
圖片
Java Pipeline:
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'ref', value: '$.ref'], //獲取分支
[key: 'user_username', value: '$.user_username'], //獲取自動構(gòu)建用戶名
[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=''
// 生產(chǎn)名稱空間
Namespace_Prod=''
// 灰度模式
GrayHeaderMode=''
// 灰度Depolyment名稱
GrayDeploymentName=''
// 灰度Service名稱
GrayServiceName=''
// 灰度Ingress名稱
GrayIngressName=''
// 是否灰度
GrayEnable='yes'
}
options {
// 表示保留10次構(gòu)建歷史
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}"
}
}
}
// // 代碼構(gòu)建
// stage('Build Code') {
// steps {
// sh '/application/maven/bin/mvn -f pom.xml -s settings.xml clean package -DskipTests'
// }
// }
// 鏡像構(gòu)建
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 鏡像構(gòu)建成功",
text: [
"?? **任務(wù)名稱**:[${JOB_NAME}](${JOB_URL})",
"?? **任務(wù)編號**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"?? **構(gòu)建狀態(tài)**: <font color='green'>成功</font>",
"?? **鏡像版本**: $Tag",
"?? **鏡像倉庫**: uhub.service.ucloud.cn/kubesre/$Project_Name",
"?? **構(gòu)建用時**: ${currentBuild.duration} ms",
"?? **執(zhí) 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改記錄",
url: "${BUILD_URL}changes"
],
[
title: "控制臺",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
stage('DeployDev'){
steps {
echo "部署開發(fā)環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到DEV環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "部署Dev環(huán)境開始"
sh '''
echo $pipeline_dir
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
}
stage('DeployUat'){
steps {
echo "部署測試環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到UAT環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "發(fā)布"
sh '''
echo $pipeline_dir
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
}
stage('DeployGray'){
steps {
echo "部署灰度環(huán)境"
script {
def GraysMode = input (
message: '確定要灰度驗證嗎?',
parameters:[
choice(name: 'operation', choices: ['基于權(quán)重灰度','基于請求頭灰度','跳過'])
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (GraysMode['operation'] == '基于權(quán)重灰度'){
def WeightMode = input (
message: '請輸入權(quán)重比例!',
parameters:[
string(name: 'workload_weight',defaultValue: '',description: ''),
string(name: 'grayload_weight',defaultValue: '',description: '')
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
sh """
echo $pipeline_dir
echo "打印編排文件詳細(xì)信息"
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 "配置權(quán)重"
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 "打印編排文件詳細(xì)信息"
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
"""
}
// 默認(rèn)模式為yes,如果跳過為no
if (GraysMode['operation'] == '跳過'){
GrayEnable='no'
}
}
}
}
stage('DeployProd'){
steps {
echo "部署生產(chǎn)環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到生產(chǎn)環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'test',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "發(fā)布"
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=jsnotallow='{.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 "開始部署生產(chǎn)環(huán)境"
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "?? Jenkins 應(yīng)用發(fā)布成功",
text: [
"?? **應(yīng)用名稱**:[${JOB_NAME}](${JOB_URL})",
"?? **應(yīng)用環(huán)境**:Prod",
"?? **任務(wù)編號**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"?? **發(fā)布狀態(tài)**: <font color='green'>成功</font>",
"?? **鏡像版本**: $Tag",
"?? **鏡像倉庫**: harbor.kubesre.com:8443/kubesre/$Project_Name",
"?? **執(zhí) 行 者**: ${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: '確定要執(zhí)行回滾操作嗎?',
parameters:[
choice(name: '是否回滾', choices: ['是', '否']),
string(name: '回滾版本', defaultValue: Revsion_Prod,description: '默認(rèn)上一個版本')
],
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'], //獲取自動構(gòu)建用戶名
[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=''
// 生產(chǎn)名稱空間
Namespace_Prod=''
// 灰度模式
GrayHeaderMode=''
// 灰度Depolyment名稱
GrayDeploymentName=''
// 灰度Service名稱
GrayServiceName=''
// 灰度Ingress名稱
GrayIngressName=''
// 是否灰度
GrayEnable='yes'
}
options {
// 表示保留10次構(gòu)建歷史
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}"
}
}
}
// // 代碼構(gòu)建
// stage('Build Code') {
// steps {
// sh '/application/maven/bin/mvn -f pom.xml -s settings.xml clean package -DskipTests'
// }
// }
// 鏡像構(gòu)建
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 鏡像構(gòu)建成功",
text: [
"?? **任務(wù)名稱**:[${JOB_NAME}](${JOB_URL})",
"?? **任務(wù)編號**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"?? **構(gòu)建狀態(tài)**: <font color='green'>成功</font>",
"?? **鏡像版本**: $Tag",
"?? **鏡像倉庫**: uhub.service.ucloud.cn/kubesre/$Project_Name",
"?? **構(gòu)建用時**: ${currentBuild.duration} ms",
"?? **執(zhí) 行 者**: ${env.BUILD_USER}",
"<at id=all></at>"
],
buttons: [
[
title: "更改記錄",
url: "${BUILD_URL}changes"
],
[
title: "控制臺",
type: "danger",
url: "${BUILD_URL}console"
]
]
)}
}
}
}
stage('DeployDev'){
steps {
echo "部署開發(fā)環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到DEV環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "部署Dev環(huán)境開始"
sh '''
echo $pipeline_dir
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
}
stage('DeployUat'){
steps {
echo "部署測試環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到UAT環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'admin',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "發(fā)布"
sh '''
echo $pipeline_dir
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
}
stage('DeployProd'){
steps {
echo "部署生產(chǎn)環(huán)境"
script {
def userInput = input (
message: '確定要發(fā)布到生產(chǎn)環(huán)境嗎?',
parameters:[
choice(name: '操作', choices: ['發(fā)布', '跳過'])
],
ok: '確定',
submitter: 'test',
submitterParameter: 'APPROVER'
)
if (userInput['操作'] == '發(fā)布'){
echo "發(fā)布"
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=jsnotallow='{.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 "開始部署生產(chǎn)環(huán)境"
echo "打印編排文件詳細(xì)信息"
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 "不發(fā)布"
}
}
}
post {
success {
wrap([$class: 'BuildUser']) {
lark (
robot: "9f7c94cd-491e-4309-83b4-9290d01fc285",
type: "CARD",
title: "?? Jenkins 應(yīng)用發(fā)布成功",
text: [
"?? **應(yīng)用名稱**:[${JOB_NAME}](${JOB_URL})",
"?? **應(yīng)用環(huán)境**:Prod",
"?? **任務(wù)編號**:[${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"?? **發(fā)布狀態(tài)**: <font color='green'>成功</font>",
"?? **鏡像版本**: $Tag",
"?? **鏡像倉庫**: harbor.kubesre.com:8443/kubesre/$Project_Name",
"?? **執(zhí) 行 者**: ${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 的構(gòu)建通知插件,可以將 Jenkins構(gòu)建過程以及結(jié)果通知推送到 Lark、飛書、釘釘 協(xié)作平臺。 可配置多個的通知時機(jī),包括 構(gòu)建啟動時、構(gòu)建中斷、構(gòu)建失敗、構(gòu)建成功時、構(gòu)建不穩(wěn)定 等。 支持多種不同類型的消息,包括 文本消息、圖片消息, 群名片消息、富文本消息、卡片消息; 同時該插件還提供了自定義模板和變量的功能,使您能夠根據(jù)自己的需求來定制通知消息的內(nèi)容和格式。(本次案例是基于飛書進(jìn)行驗證)
準(zhǔn)備工作,在飛書群新建一個機(jī)器人(https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot):
在飛書群,點(diǎn)擊設(shè)置:
圖片
然后點(diǎn)擊群機(jī)器人:
圖片
在飛書群,選擇添加機(jī)器人
圖片
填寫相應(yīng)配置信息并點(diǎn)擊保存:
圖片
圖片
選擇系統(tǒng)管理-Lark Notice:
圖片
通知時機(jī)全部勾選:
圖片
配置機(jī)器人信息并保存
圖片
配置Java Pipeline
新建任務(wù):
圖片
填寫任務(wù)名稱,并選擇流水線,點(diǎn)擊確定:
圖片
配置Pipeline SCM:
圖片
修改腳本路徑,點(diǎn)擊確定:
圖片
點(diǎn)擊構(gòu)建讓配置生效:
圖片
其他Java項目配置都一樣!
配置Vue Pipeline
新建任務(wù):
圖片
填寫任務(wù)名稱,選擇流水線:
圖片
配置Pipeline SCM:
圖片
修改腳本路徑,點(diǎn)擊確定:
圖片
點(diǎn)擊構(gòu)建讓配置生效:
圖片
其他Vue項目配置都一樣!
配置Gitlab Webhook
進(jìn)入項目,選擇webhook:
圖片
選擇添加Webhook:
圖片
配置webhook URL,Token以Job Name:
圖片
勾選標(biāo)簽推送事件,也就是說只有標(biāo)簽推送事件才會觸發(fā)流水線:
圖片
到此為止,Webook已配置完畢!所有項目配置都一樣。
觸發(fā)驗證
觸發(fā)Java Pipeline:
進(jìn)入標(biāo)簽管理:
圖片
新建標(biāo)簽:
圖片
填寫信息并點(diǎn)擊創(chuàng)建標(biāo)簽(此標(biāo)簽名稱也是容器鏡像的Tag):
圖片
進(jìn)入Jenkins可以看到Gateway Pipeline已經(jīng)觸發(fā)了:
圖片
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Dev環(huán)境:
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Uat環(huán)境:
圖片
選擇對應(yīng)的灰度發(fā)布方式或者跳過:
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Prod環(huán)境:
圖片
也可以回滾,默認(rèn)是上一個版本也可修改成想要回滾到的版本:
圖片
觸發(fā) Vue流水線:
進(jìn)入標(biāo)簽管理:
圖片
創(chuàng)建標(biāo)簽:
圖片
填寫信息并點(diǎn)擊創(chuàng)建標(biāo)簽(此標(biāo)簽名稱也是容器鏡像的Tag):
圖片
圖片
進(jìn)入Jenkins可以看到ruoyi-ui Pipeline已經(jīng)觸發(fā)了:
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Dev環(huán)境:
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Uat環(huán)境:
圖片
選擇發(fā)布,并點(diǎn)擊確定,將新版本發(fā)布到Prod環(huán)境:
圖片
構(gòu)建通知
圖片