持续集成(Continuous Integration, CI) 与 持续交付/持续部署(Continuous Delivery/Continuous Deployment, CD) 构成了现代软件开发生命周期的核心实践,旨在实现从代码提交到产品上线的自动化、快速且可靠的流水线。而 Jenkins,作为一款久经考验、功能强大、MIT开源协议且高度可扩展的开源自动化服务器,无疑是支撑这套 CI/CD 体系最广泛采用的中坚力量。
1. 理解 CI/CD
要真正理解 Jenkins 的核心价值,首先需要明晰其服务的对象——CI/CD。持续集成(CI)倡导开发者频繁地(通常每天多次)将代码变更合并至共享主干分支。每次集成都伴随自动化构建和测试,以便快速发现集成错误,从而显著降低修复成本并提升代码质量,其关键在于“快速反馈”。
持续交付(CD)是在 CI 的基础上进一步自动化构建、测试、部署至预生产(Staging)环境,确保软件始终处于可随时发布的状态。而持续部署则更进一步,将所有通过验证的更改自动部署至生产环境,完全免除人工干预。
CI/CD 编织成一张高效、可靠的自动化交付网,加速软件发布节奏,提高稳定性与质量,降低发布风险。

图 1:CI/CD 自动化工作流示意图。从代码提交开始,经过构建、多阶段测试、打包,最终实现自动化部署,形成高效闭环。
2.Jenkins安装
2.1 资源准备
除 Jenkins 控制节点外,建议单独准备一台构建节点(BuildNode),用于执行构建、测试、镜像打包等任务。构建过程资源开销大,应避免与其他业务服务混用,确保性能稳定。
节点 | IP/hostname | 备注 |
Jenkins | 10.88.88.76/k8smgt | Jenkins节点 |
BuildNode | 10.88.88.82/buildnode1 | 构建节点 |
2.2 Docker安装
-p 和–httpsPort 改成你要的端口,例如443,httpsKeyStore+httpsKeyStorePassword是PKCS12格式的SSL证书,请向SSL服务商索要。修改/data/jenkins持久化路径。
docker run -d \
--name=jenkins-server \
--restart=always \
-p 443:443 \ #改成你要的端口
-p 50000:50000 \
-v /data/jenkins:/var/jenkins_home \
-v /data/jenkins/certs:/certs \
-e JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Dhudson.model.WorkspaceCleanupThread.disabled=true" \
-e JENKINS_OPTS="--httpPort=-1 --httpsPort=443 --httpsKeyStore=/certs/_.procoding.cn.p12 --httpsKeyStorePassword=123456" \
jenkins/jenkins:2.516.1-lts
部署成功后,首次访问 Jenkins 需要使用初始化密码:
#/var/jenkins_home/secrets/initialAdminPassword已经映射到主机的/data/jenkins目录下,所以直接查看密钥命令为:
cat /data/jenkins/secrets/initialAdminPassword
3.Jenkins配置
3.1 加入Agent
在 Jenkins 后台 Manage Jenkins -> Nodes 添加新节点:


在 Jenkins 中配置 Agent 节点时,需要关注以下几个核心参数:
Name(名称):为你的 Agent 设置一个清晰、易识别的名称。
Number of executors(执行器数量):此值决定了该节点上可以同时执行的最大构建任务数量(即并发构建数)。每个执行器实质上是 Jenkins 在该节点上管理的一个独立执行单元(通常表现为一个线程)。默认值为 1,你可以根据节点的硬件资源(如 CPU 核心数)进行调整,允许在单个节点上运行多个执行器以提高并发处理能力。
Remote root directory(远程工作目录):指定 Agent 节点机器上用于 Jenkins 执行构建任务的工作空间目录。必须使用绝对路径(例如 /var/jenkins/agent 或 C:\jenkins\agent)。
Labels(标签):标签是管理多 Agent 环境的关键机制。通过为 Agent 分配一个或多个标签(如 jdk8, nodejs,python, linux, windows, docker),可以精确地标识其具备的能力或运行环境。当构建任务指定了特定标签要求时,Jenkins 会自动将任务调度到拥有匹配标签的 Agent 上执行。
Usage(使用策略):控制 Jenkins 如何利用此 Agent:
– Use this node as much as possible(尽可能多地使用此节点):Jenkins 会优先使用此 Agent 来运行任何匹配其标签(或没有标签限制)的任务。这是最常用的策略,旨在充分利用 Agent 资源。
– Only build jobs with label expressions matching this node(仅允许运行绑定到该节点的任务):只有那些明确指定了与该 Agent 标签完全匹配的构建任务,才会被调度到此 Agent 上运行。此策略用于严格限制 Agent 的使用范围。
Launch method(启动方式):定义 Jenkins Controller(主节点)如何启动和连接此 Agent:
– Launch agent by connecting it to the controller :JNLP 通过 Java Web 启动代理,Agent 节点需要预先安装配置好 JRE/JDK 环境。Agent 主动连接到 Controller(通常下载一个 agent.jar 文件运行)。跨平台支持,是最常用的方式。
– Launch agent via SSH:通过 SSH 启动代理 ,Controller 主动通过 SSH 协议连接到 Agent 节点并启动代理进程。配置相对简单(需提供 SSH 凭证)。局限性在于 SSH 本身对跨平台的支持不如 JNLP 通用,使用相对较少。
Availability 控制 Jenkins 何时自动启动/连接(上线)和停止/断开(下线)此 Agent。
– Keep online as much as possible:Agent 始终保持在线,Jenkins 会立即连接并在断开后自动重连。场景: 需要 Agent 随时待命(如物理服务器、长期运行的稳定 VM)。特点: 响应最快,但持续占用资源。
– Bring online according to a schedule: 严格按预设时间表自动上线/下线。场景: 需严格控制运行时间(遵守许可、避开高峰、配合开关机)。特点: 节省非计划时段资源。
– Bring online when in demand, take offline when idle: 按需上线(匹配任务排队时启动),空闲下线(任务完成后,超时无新任务则停止)。场景: 云环境(AWS/Azure/K8s等)或需极致节省资源。特点: 资源利用率最高,任务启动需等待初始化。
Node Properties(节点属性)中的四个勾选框说明:
Disable deferred wipeout on this node:禁用该节点的延迟清理工作区优化功能(默认启用,用于在清理工作区时减少磁盘占用并提升效率,禁用后将采取直接清理方式)。
Disk Space Monitoring Thresholds:设置节点磁盘空间监控的高/低阈值(当磁盘使用量超过阈值时触发警告或阻止任务分配,确保节点资源充足)。
Environment variables:定义节点特定的环境变量(例如设置 JAVA_HOME 或覆盖节点全局运行时参数,供构建任务调用配置的变量)。
Tool Locations:配置节点上工具(如 JDK 、Maven 、Node.js)的绝对路径(可用于覆盖 Jenkins 全局工具路径,指定特定版本或自定义路径满足构建需求)。

本示例采用静态部署方式,Agent 常驻特定构建服务器,资源可控、性能稳定,适合中大型持续交付系统。相比之下,动态部署(如 K8s 弹性 Pod)虽具扩展性,但可能在密集构建的高负载下影响集群稳定性,k8s出问题时容易引发系统性雪崩。

3.2 Agent 安装配置
在构建节点上进行以下操作:
#预创建文件夹
mkdir -p /usr/local/bin/jenkins-agent/
mkdir -p /data/jenkins/
#移动到目标文件夹
cd /data/jenkins
#安装jdk21
apt install openjdk-21-jre-headless -y
#打印是否安装成功
java --version
#下载agent.jar
curl -sO https://jenkins.procoding.cn/jnlpJars/agent.jar
创建启动脚本 /usr/local/bin/jenkins-agent/service:
cat > /usr/local/bin/jenkins-agent/service <<EOF
#!/bin/bash
start()
{
java -jar /data/jenkins/agent.jar -url https://jenkins.procoding.cn/ -secret 7cad*******************39826f -name BuildNode1 -webSocket -workDir "/data/jenkins/"
}
case "\$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "usage: \$0 start|stop|restart"
exit 0;
esac
exit
EOF
创建 systemd 服务:
#生成 systemd 服务文件
cat > /etc/systemd/system/jenkins-agent.service <<EOF
[Unit]
Description=The Jenkins Agent Server
[Service]
User=root
ExecStart=/usr/local/bin/jenkins-agent/service start
ExecReload=/usr/local/bin/jenkins-agent/service restart
ExecStop=/usr/local/bin/jenkins-agent/service stop
SuccessExitStatus=143
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
给与权限与启动服务:
#给予权限
chmod 755 /usr/local/bin/jenkins-agent/service
chmod 755 /data/jenkins/agent.jar
#加载服务
systemctl daemon-reload
#启动服务
systemctl start jenkins-agent
systemctl enable jenkins-agent
#确认服务状态
systemctl status jenkins-agent
回到Jenkins后台查看Agent状态

新建流水线测试:

测试脚本如下:
// 声明式流水线(需插件支持)
pipeline {
agent {
label 'BuildNode1' // 构建机器
}
stages {
stage('test BuildNode1') {
steps {
echo '使用BuildNode1执行流水线'
sh 'sleep 1m'
}
}
}
}

4. CI/CD 流水线构建指南:
4.1 工作准备:
鉴权信息:
– GitLab 创建 DevOps 专用账号
– Harbor 创建 DevOps 专用账号
– 准备K8s的master中的.kube/config文件,用于部署
插件要求:
– Jenkins中安装 安装 Config File Provider Plugin,用于注入 K8s kubeconfig 文件
代码结构部分:
– deploy/ 下存放 K8s YAML 部署模板
– 根目录包含 Dockerfile

YAML 变量示例(deploy/deploy.yaml):
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: $APP_NAME-deployment
namespace: $PROJECT_NS
spec:
replicas: 3
selector:
matchLabels:
app: $APP_NAME
template:
metadata:
labels:
app: $APP_NAME
spec:
containers:
- name: $APP_NAME-containers
image: ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${BRANCH_NAME}-latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: $APP_NAME-service
namespace: $PROJECT_NS
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: $APP_NAME

Dockerfile 示例(以 SpringBoot 为例):
FROM maven:3.9.10 AS build
WORKDIR /app
COPY . .
COPY docker-maven-settings.xml /root/.m2/settings.xml
RUN mvn -Dmaven.wagon.http.ssl.insecure=true \
-Dmaven.wagon.http.ssl.allowall=true \
-Dmaven.wagon.http.ssl.ignore.validity.dates=true \
clean package -am -DskipTests
FROM openjdk:21-jdk-slim
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=build /app/target/*.jar /app/app.jar
#COPY config/config-version.yml /config/config-version.yml
ENTRYPOINT ["java","-jar","/app/app.jar","--spring.profiles.active=prod"]
4.2 构建节点工具安装
# 安装 gettext (envsubst)
apt-get install gettext -y
# 安装 kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl
# 检查 kubectl 是否安装成功
kubectl version
4.3 Jenkins 凭据配置
在 Manage Jenkins -> Managed Files 添加 .kube/config,类型选择 Custom file。



记录凭据 ID 用于流水线中调用。

在 Manage Jenkins -> Credentials 中分别添加 GitLab 和 Harbor 的账号密码,记录凭据 ID 用于流水线中调用。





4.4 构建流水线 Job
点击New Item 新建 Pipeline 类型的任务。


勾选 This project is parameterized,添加构建参数BRANCH_NAME:用于dev/staging/production分支选择。

添加构建参数BUILD_VERSION:用于版本号输入。

示例流水线脚本:

脚本分三阶段:代码克隆 → 镜像构建推送 → K8s 部署。结构清晰,支持按分支区分处理逻辑。详见脚本,需注意:
- 修改 Git 凭据 ID(credentialsId)、Harbor 凭据 ID(credentialsId)、KubeConfig 文件 ID(fileId)
- 每个阶段的变量替换使用 envsubst 处理(gettext)
- 构建后自动清理镜像,节省磁盘空间
pipeline {
agent {
label "BuildNode1" // 指定在哪个节点上运行该流水线
}
parameters {
// 参数1:选择构建的分支
choice(
name: 'BRANCH_NAME',
choices: ['staging', 'dev', 'production'],
description: '选择要构建的分支'
)
// 参数2:构建版本号
string(
name: 'BUILD_VERSION',
defaultValue: '1.0.0',
description: '请输入构建版本号'
)
}
environment {
// 应用名称定义为环境变量
APP_NAME = "qinghao-demo"
}
stages {
stage("1. clone codes") {
steps {
// 克隆 Git 仓库代码,根据选择的分支
git branch: "${params.BRANCH_NAME}", credentialsId: 'ea1a72f5-7b3d-4239-8873-58bf288a8c98', url: 'https://git.procoding.cn/QingHao/qinghao.git'
}
}
stage("2. build image && push image") {
steps {
script {
// 分支为 production 时,构建并推送镜像
if (params.BRANCH_NAME == "production") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
// 构建两个标签的 Docker 镜像:版本号和 latest
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
// 登录 Harbor 并推送镜像
withCredentials([usernamePassword(credentialsId: '35c1872f-77da-4017-8b83-76a13bc096ec', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
// 分支为 dev 时的镜像构建与推送
} else if (params.BRANCH_NAME == "dev") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
withCredentials([usernamePassword(credentialsId: '35c1872f-77da-4017-8b83-76a13bc096ec', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
// staging 分支的镜像构建与推送
} else if (params.BRANCH_NAME == "staging") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
withCredentials([usernamePassword(credentialsId: '35c1872f-77da-4017-8b83-76a13bc096ec', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
}
}
}
post {
always {
script {
// 构建后清理本地镜像,避免占用磁盘空间
if (params.BRANCH_NAME == "production") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
} else if (params.BRANCH_NAME == 'dev') {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
} else if (params.BRANCH_NAME == 'staging') {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
}
}
}
}
stage('Deploy') {
steps {
script {
// 使用 kubeconfig 进行部署,三个分支共用配置
if (params.BRANCH_NAME == 'production') {
configFileProvider([configFile(fileId: 'c984b959-abc1-4115-ba6f-c3504050d0d8', targetLocation: 'kube-config')]) {
env.PROJECT_NS = "default"
env.REGISTRY_URL = "harbor.procoding.cn"
env.REGISTRY_REPO = "library"
env.BRANCH_NAME = "${params.BRANCH_NAME}"
// 使用 envsubst 替换 deploy.yaml 变量并应用
sh """
envsubst < deploy/deploy.yaml | kubectl apply -f - --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
} else if (params.BRANCH_NAME == 'dev') {
configFileProvider([configFile(fileId: 'c984b959-abc1-4115-ba6f-c3504050d0d8', targetLocation: 'kube-config')]) {
env.PROJECT_NS = "default"
env.REGISTRY_URL = "harbor.procoding.cn"
env.REGISTRY_REPO = "library"
env.BRANCH_NAME = "${params.BRANCH_NAME}"
sh """
envsubst < deploy/deploy.yaml | kubectl apply -f - --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
} else if (params.BRANCH_NAME == 'staging') {
configFileProvider([configFile(fileId: 'c984b959-abc1-4115-ba6f-c3504050d0d8', targetLocation: 'kube-config')]) {
env.PROJECT_NS = "default"
env.REGISTRY_URL = "harbor.procoding.cn"
env.REGISTRY_REPO = "library"
env.BRANCH_NAME = "${params.BRANCH_NAME}"
sh """
envsubst < deploy/deploy.yaml | kubectl apply -f - --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
}
}
}
}
}
}
另外一种实现,直接将Dockerfile写入流水线:
pipeline {
agent {
label "BuildNode1"
}
parameters {
choice(
name: 'BRANCH_NAME',
choices: ['staging', 'dev', 'production'],
description: '选择要构建的分支'
)
string(
name: 'BUILD_VERSION',
defaultValue: '1.0.0',
description: '请输入构建版本号'
)
}
environment {
APP_NAME = "spring-demo"
}
stages {
stage("1. clone codes") {
steps {
git branch: "${params.BRANCH_NAME}", credentialsId: '4867215b-cc0e-474c-8e6d-b623d55598c9', url: 'https://git.procoding.cn/QingHao/qinghao.git'
}
}
stage("2. build image && push image") {
steps {
script {
if (params.BRANCH_NAME == "production") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
withCredentials([usernamePassword(credentialsId: '4f0f0d02-7dba-4f36-a896-5d2ab0466cf8', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
} else if (params.BRANCH_NAME == "dev") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
withCredentials([usernamePassword(credentialsId: '4f0f0d02-7dba-4f36-a896-5d2ab0466cf8', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
} else if (params.BRANCH_NAME == "staging") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker build -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION} -t ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest .
"""
withCredentials([usernamePassword(credentialsId: '4f0f0d02-7dba-4f36-a896-5d2ab0466cf8', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USERNAME')]) {
sh """
echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin ${REGISTRY_URL}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker push ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
}
}
}
post {
always {
script {
if (params.BRANCH_NAME == "production") {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
} else if (params.BRANCH_NAME == 'dev') {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
} else if (params.BRANCH_NAME == 'staging') {
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-${params.BUILD_VERSION}
docker rmi ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
"""
}
}
}
}
}
stage('Deploy') {
steps {
script {
if (params.BRANCH_NAME == 'production') {
configFileProvider([configFile(fileId: '1d1426bd-1be3-4730-808e-d73ff35c1e3c', targetLocation: 'kube-config')]) {
def PROJECT_NS = "default"
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
cat > deploy.yaml <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: $APP_NAME-deployment
namespace: $PROJECT_NS
spec:
replicas: 3
selector:
matchLabels:
app: $APP_NAME
template:
metadata:
labels:
app: $APP_NAME
spec:
containers:
- name: $APP_NAME-containers
image: ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
imagePullPolicy: Always
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: $APP_NAME-service
namespace: $PROJECT_NS
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: $APP_NAME
EOF
kubectl apply -f deploy.yaml --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
} else if (params.BRANCH_NAME == 'dev') {
configFileProvider([configFile(fileId: '1d1426bd-1be3-4730-808e-d73ff35c1e3c', targetLocation: 'kube-config')]) {
def PROJECT_NS = "default"
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
cat > deploy.yaml <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: $APP_NAME-deployment
namespace: $PROJECT_NS
spec:
replicas: 3
selector:
matchLabels:
app: $APP_NAME
template:
metadata:
labels:
app: $APP_NAME
spec:
containers:
- name: $APP_NAME-containers
image: ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
imagePullPolicy: Always
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: $APP_NAME-service
namespace: $PROJECT_NS
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: $APP_NAME
EOF
kubectl apply -f deploy.yaml --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
} else if (params.BRANCH_NAME == 'staging') {
configFileProvider([configFile(fileId: '1d1426bd-1be3-4730-808e-d73ff35c1e3c', targetLocation: 'kube-config')]) {
def PROJECT_NS = "default"
def REGISTRY_URL = "harbor.procoding.cn"
def REGISTRY_REPO = "library"
sh """
cat > deploy.yaml <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: $APP_NAME-deployment
namespace: $PROJECT_NS
spec:
replicas: 3
selector:
matchLabels:
app: $APP_NAME
template:
metadata:
labels:
app: $APP_NAME
spec:
containers:
- name: $APP_NAME-containers
image: ${REGISTRY_URL}/${REGISTRY_REPO}/${APP_NAME}:${params.BRANCH_NAME}-latest
imagePullPolicy: Always
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: $APP_NAME-service
namespace: $PROJECT_NS
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: $APP_NAME
EOF
kubectl apply -f deploy.yaml --kubeconfig=kube-config
kubectl rollout restart deployment -n $PROJECT_NS $APP_NAME-deployment --kubeconfig=kube-config
"""
}
}
}
}
}
}
}
构建成功后就会推送到k8s上了。如果需要不同的分支推送到不同的k8s,只要修改不同环境的Harbor、K8s地址和凭据ID即可。

参考文档:
https://blog.csdn.net/weixin_40340444/article/details/142172287
https://tanqidi.com/archives/jenkins-slave
https://blog.csdn.net/yprufeng/article/details/136402798
原创文章,未经允许禁止转载。