Jenkins + CICD流水线构建指南

持续集成(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备注
Jenkins10.88.88.76/k8smgtJenkins节点
BuildNode10.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

原创文章,未经允许禁止转载。

GitLab CE搭建指南 REK2 搭建6节点K8S教程(一):系统准备 AI混合云架构设计
View Comments
There are currently no comments.