Jenkinsfile流水线设计解析

Jenkinsfile示例

复制代码
pipeline {
    agent {
        kubernetes {
            inheritFrom "jenkins-slave"
            yaml '''
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: jenkins-admin
  securityContext:
    fsGroup: 0
    runAsUser: 0
  containers:
  - name: jnlp
    image: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/inbound-agent:latest-jdk17"
    imagePullPolicy: IfNotPresent
    env:
    - name: GIT_SSL_NO_VERIFY
      value: "true"
  - name: maven
    image: "harbor.apotos.com/maven/maven:3.9.9"
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep infinity"]
  - name: buildkitd
    image: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/moby/buildkit:v0.23.2"
    imagePullPolicy: IfNotPresent
    command: ["sh","-c"]
    args:
    - |
      set -e
      mkdir -p /etc/buildkit
      CERT="/etc/certs/harbor.apotos.com/ca.crt"

      if command -v update-ca-certificates >/dev/null 2>&1; then
        mkdir -p /usr/local/share/ca-certificates
        cp -f "$CERT" /usr/local/share/ca-certificates/harbor.apotos.com.crt
        update-ca-certificates >/dev/null 2>&1 || true
      elif [ -f /etc/ssl/certs/ca-certificates.crt ]; then
        cat "$CERT" >> /etc/ssl/certs/ca-certificates.crt || true
      fi

      # 处理 SWR 自签证书:抓取证书链并导入系统信任库
      SWR_HOST="swr.cn-north-4.myhuaweicloud.com"
      SWR_CERT="/etc/certs/${SWR_HOST}.crt"
      mkdir -p /etc/certs
      if ! command -v openssl >/dev/null 2>&1; then
        if command -v apk >/dev/null 2>&1; then
          apk add --no-cache openssl ca-certificates >/dev/null 2>&1 || true
        elif command -v apt-get >/dev/null 2>&1; then
          apt-get update -y >/dev/null 2>&1 || true
          apt-get install -y --no-install-recommends openssl ca-certificates >/dev/null 2>&1 || true
        fi
      fi
      if command -v openssl >/dev/null 2>&1; then
        openssl s_client -showcerts -connect "${SWR_HOST}:443" -servername "${SWR_HOST}" </dev/null 2>/dev/null \
          | sed -ne '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > "${SWR_CERT}" || true
        if [ -s "${SWR_CERT}" ]; then
          if command -v update-ca-certificates >/dev/null 2>&1; then
            mkdir -p /usr/local/share/ca-certificates
            cp -f "${SWR_CERT}" "/usr/local/share/ca-certificates/${SWR_HOST}.crt"
            update-ca-certificates >/dev/null 2>&1 || true
          elif [ -f /etc/ssl/certs/ca-certificates.crt ]; then
            cat "${SWR_CERT}" >> /etc/ssl/certs/ca-certificates.crt || true
          fi
        fi
      fi

      echo '[registry."harbor.apotos.com"]' > /etc/buildkit/buildkitd.toml
      echo '  ca=["'"$CERT"'"]' >> /etc/buildkit/buildkitd.toml

      echo '[registry."swr.cn-north-4.myhuaweicloud.com"]' >> /etc/buildkit/buildkitd.toml
      echo '  ca=["'"$SWR_CERT"'"]' >> /etc/buildkit/buildkitd.toml

      exec buildkitd --debug --addr unix:///run/buildkit/buildkitd.sock --config /etc/buildkit/buildkitd.toml
    securityContext:
      privileged: true
    volumeMounts:
    - name: buildkit-sock
      mountPath: /run/buildkit
    - name: harbor-registry-ca
      mountPath: /etc/certs/harbor.apotos.com
      readOnly: true
  - name: buildctl
    image: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/moby/buildkit:v0.23.2"
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep infinity"]
    volumeMounts:
    - name: buildkit-sock
      mountPath: /run/buildkit
    - name: harbor-registry-ca
      mountPath: /etc/certs/harbor.apotos.com
      readOnly: true
  - name: git
    image: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/alpine/git:v2.49.0"
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep infinity"]
  volumes:
  - name: buildkit-sock
    emptyDir: {}
  - name: harbor-registry-ca
    secret:
      secretName: harbor-registry-ca
      optional: false
'''
        }
    }

    options {
        skipDefaultCheckout(true)
        timeout(time: 45, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '30'))
    }

    environment {
        // Harbor
        HARBOR_REGISTRY = "harbor.apotos.com"
        HARBOR_PROJECT  = "spring-petclinic"
        IMAGE_NAME      = "spring-petclinic"
        IMAGE_REPO      = "${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}"
        CACHE_REPO      = "${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}-buildcache:cache"

        // Nexus
        NEXUS_URL          = "https://nexus.apotos.com"
        NEXUS_RELEASE_REPO = "maven-releases"
        NEXUS_SNAPSHOT_REPO = "maven-snapshots"

        // GitOps/ArgoCD
        ARGOCD_REPO     = "https://gitlab.apotos.com/myteam/argocd_deploy.git"
        ARGOCD_BRANCH   = "develop"
        ARGOCD_DIR      = "argocd_deploy"
        ARGOCD_MANIFEST = "apps/dev/spring-petclinic/deploy.yaml"

        // SonarQube
        SONARQUBE_SERVER = "SonarScanner"
        SONAR_PROJECT_KEY = "spring-petclinic"
        SONAR_TOKEN_CRED = "sonar-token"

        // 兼容自签名环境(仅对 git)
        GIT_SSL_NO_VERIFY = "true"
    }

    stages {
        stage('checkout') {
            steps {
                retry(3) {
                    checkout scm
                }
            }
        }

        stage('prepare-metadata') {
            steps {
                script {
                    env.BUILD_TIMESTAMP = sh(script: 'TZ=Asia/Shanghai date +%Y%m%d_%H%M%S', returnStdout: true).trim()
                    env.GIT_COMMIT_SHORT = (env.GIT_COMMIT ? env.GIT_COMMIT.take(8) : sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim())
                    env.IMAGE_TAG  = "${env.BUILD_TIMESTAMP}-${env.GIT_COMMIT_SHORT}"
                    env.IMAGE_FULL = "${env.IMAGE_REPO}:${env.IMAGE_TAG}"
                }
            }
        }

        stage('build (maven package)') {
            steps {
                container('maven') {
                    sh '''
                        set -euo pipefail
                        if [ -f .mvn/settings.xml ]; then
                          MVN_SETTINGS="-s .mvn/settings.xml"
                        else
                          MVN_SETTINGS=""
                        fi
                        mvn -B ${MVN_SETTINGS} clean package -DskipTests -Dcheckstyle.skip=true
                        ls -lh target/*.jar | head -n 5
                    '''
                }
            }
        }

        stage('sonarqube-scan') {
            steps {
                script {
                    if (!fileExists('pom.xml') && !fileExists('build.gradle')) {
                        checkout scm
                    }
                }
                container('maven') {
                    withCredentials([string(credentialsId: env.SONAR_TOKEN_CRED, variable: 'SONAR_TOKEN')]) {
                        withSonarQubeEnv(env.SONARQUBE_SERVER) {
                            sh '''
                                set -euo pipefail
                                if [ -f .mvn/settings.xml ]; then
                                  MVN_SETTINGS="-s .mvn/settings.xml"
                                else
                                  MVN_SETTINGS=""
                                fi
                                mvn -B ${MVN_SETTINGS} sonar:sonar \
                                  -DskipTests -Dcheckstyle.skip=true \
                                  -Dsonar.host.url=http://sonarqube.sonarqube.svc.cluster.local:9000 \
                                  -Dsonar.projectKey="${SONAR_PROJECT_KEY}" \
                                  -Dsonar.login="${SONAR_TOKEN}"
                            '''
                        }
                    }
                }
            }
        }

        stage('sonarqube-quality-gate') {
            steps {
                timeout(time: 30, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }

        stage('deploy (nexus)') {
            when {
                expression { return fileExists('pom.xml') }
            }
            steps {
                container('maven') {
                    withCredentials([usernamePassword(credentialsId: 'nexus-passwd', usernameVariable: 'NEXUS_USER', passwordVariable: 'NEXUS_PASSWORD')]) {
                        sh '''
                            set -euo pipefail

                            REPO_ID="${NEXUS_SNAPSHOT_REPO}"
                            REPO_NAME="maven-snapshots"
                            if [ "${BRANCH_NAME:-}" = "main" ] || [ "${BRANCH_NAME:-}" = "master" ]; then
                              REPO_ID="${NEXUS_RELEASE_REPO}"
                              REPO_NAME="maven-releases"
                            fi

                            mkdir -p "$HOME/.m2"
                            cat > "$HOME/.m2/settings.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="https://maven.apache.org/SETTINGS/1.2.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="https://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
  <mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>*</mirrorOf>
      <name>阿里云公共仓库</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
    <mirror>
      <id>aliyun-spring</id>
      <mirrorOf>spring-milestones,spring-snapshots</mirrorOf>
      <name>阿里云Spring仓库</name>
      <url>https://maven.aliyun.com/repository/spring</url>
    </mirror>
  </mirrors>
  <activeProfiles>
    <activeProfile>aliyun</activeProfile>
  </activeProfiles>
  <profiles>
    <profile>
      <id>aliyun</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>https://maven.aliyun.com/repository/public</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>https://maven.aliyun.com/repository/public</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <servers>
    <server>
      <id>maven-releases</id>
      <username>${NEXUS_USER}</username>
      <password>${NEXUS_PASSWORD}</password>
    </server>
    <server>
      <id>maven-snapshots</id>
      <username>${NEXUS_USER}</username>
      <password>${NEXUS_PASSWORD}</password>
    </server>
  </servers>
</settings>
EOF

                            # 处理 Nexus 自签证书:导入到 JVM truststore,避免 PKIX path building failed
                            NEXUS_HOST="$(echo "${NEXUS_URL}" | sed -E 's#^https?://##; s#/.*$##')"
                            JAVA_BIN="$(command -v java || true)"
                            if [ -n "${JAVA_BIN}" ]; then
                              JAVA_HOME_DETECTED="$(cd "$(dirname "$(readlink -f "${JAVA_BIN}")")/.." && pwd)"
                              CACERTS="${JAVA_HOME_DETECTED}/lib/security/cacerts"
                              if [ -f "${CACERTS}" ] && command -v keytool >/dev/null 2>&1; then
                                keytool -printcert -rfc -sslserver "${NEXUS_HOST}:443" > /tmp/nexus.crt 2>/dev/null || true
                                keytool -importcert -noprompt -storepass changeit -alias "nexus-${NEXUS_HOST}" -file /tmp/nexus.crt -keystore "${CACERTS}" >/dev/null 2>&1 || true
                              fi
                            fi

                            mvn -B -s "$HOME/.m2/settings.xml" deploy \
                              -DskipTests -Dcheckstyle.skip=true \
                              -Dmaven.wagon.http.ssl.insecure=true \
                              -Dmaven.wagon.http.ssl.allowall=true \
                              -DaltDeploymentRepository="${REPO_NAME}::${NEXUS_URL}/repository/${REPO_ID}"
                        '''
                    }
                }
            }
        }

        stage('build-and-push (buildkit)') {
            steps {
                container('buildctl') {
                    withCredentials([usernamePassword(credentialsId: 'harbor-passwd', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
                        sh '''
                            set -euo pipefail

                            until buildctl --addr unix:///run/buildkit/buildkitd.sock debug workers >/dev/null 2>&1; do
                              echo "waiting for buildkitd..."
                              sleep 1
                            done

                            export SSL_CERT_FILE=/etc/certs/harbor.apotos.com/ca.crt
                            export SSL_CERT_DIR=/etc/certs

                            export HOME=/root
                            mkdir -p /root/.docker
                            AUTH="$(printf "%s:%s" "$USERNAME" "$PASSWORD" | base64 | tr -d '\\n')"
                            cat > /root/.docker/config.json <<EOF
{"auths":{"${HARBOR_REGISTRY}":{"auth":"${AUTH}"}}}
EOF

                            buildctl --addr unix:///run/buildkit/buildkitd.sock build \
                              --frontend dockerfile.v0 \
                              --local context=. \
                              --local dockerfile=. \
                              --opt filename=Dockerfile \
                              --import-cache type=registry,ref=${CACHE_REPO} \
                              --export-cache type=registry,ref=${CACHE_REPO},mode=max \
                              --output type=image,name=${IMAGE_FULL},push=true
                        '''
                    }
                }
            }
        }

        stage('update-argocd-deploy') {
            steps {
                container('git') {
                    withCredentials([usernamePassword(credentialsId: 'gitlab-argocd-token', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
                        retry(3) {
                            sh '''
                                set -euo pipefail

                                AUTH_B64="$(printf "%s:%s" "$GIT_USERNAME" "$GIT_PASSWORD" | base64 | tr -d '\\n')"
                                EXTRAHEADER="Authorization: Basic ${AUTH_B64}"

                                if [ -d "${ARGOCD_DIR}/.git" ]; then
                                  cd "${ARGOCD_DIR}"
                                  git -c http.sslVerify=false -c http.extraHeader="${EXTRAHEADER}" fetch origin
                                  git checkout "${ARGOCD_BRANCH}"
                                  git -c http.sslVerify=false -c http.extraHeader="${EXTRAHEADER}" pull origin "${ARGOCD_BRANCH}"
                                else
                                  git -c http.sslVerify=false -c http.extraHeader="${EXTRAHEADER}" clone -b "${ARGOCD_BRANCH}" "${ARGOCD_REPO}" "${ARGOCD_DIR}"
                                  cd "${ARGOCD_DIR}"
                                fi

                                git config user.name "Apotos"
                                git config user.email "275717764@qq.com"

                                if [ ! -f "${ARGOCD_MANIFEST}" ]; then
                                  echo "manifest not found: ${ARGOCD_DIR}/${ARGOCD_MANIFEST}"
                                  exit 1
                                fi

                                IMG_KEY_RE='^[[:space:]]*[-]?[[:space:]]*image:[[:space:]]*'
                                echo "=== 更新image标签 ==="
                                if ! grep -nE "${IMG_KEY_RE}" "${ARGOCD_MANIFEST}" | head -n 5; then
                                  echo "no image line found"
                                  exit 1
                                fi

                                # 兼容 `image:` 与 `- image:` 两种写法;并避免 sed 反向引用产生控制字符
                                sed -i -E 's#^([[:space:]]*[-]?[[:space:]]*image:[[:space:]]*).*spring-petclinic/spring-petclinic([:@][^[:space:]]+)?#\\1'"${IMAGE_FULL}"'#' "${ARGOCD_MANIFEST}"

                                echo "=== 更新后 image 标签 ==="
                                grep -nE "${IMG_KEY_RE}" "${ARGOCD_MANIFEST}" | head -n 5 || true

                                if ! grep -q "${IMAGE_FULL}" "${ARGOCD_MANIFEST}"; then
                                  echo "replace image failed"
                                  grep -nE "${IMG_KEY_RE}" "${ARGOCD_MANIFEST}" | head -n 10 || true
                                  exit 1
                                fi

                                git add "${ARGOCD_MANIFEST}"
                                git commit -m "chore: update spring-petclinic image to ${IMAGE_TAG} [skip ci]" || echo "no changes"
                                git -c http.sslVerify=false -c http.extraHeader="${EXTRAHEADER}" push origin "${ARGOCD_BRANCH}"
                            '''
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            script {
                try { deleteDir() } catch (Exception e) { }
            }
        }
    }
}

Jenkins Pipeline 设计思路分析(结合代码片段)

一、Kubernetes 动态 Agent 设计

1.1 Pod 模板结构

复制代码
agent {
    kubernetes {
        inheritFrom "jenkins-slave"
        yaml '''
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: jenkins-admin
  securityContext:
    fsGroup: 0
    runAsUser: 0
  containers:
  - name: jnlp
  - name: maven
  - name: buildkitd
  - name: buildctl
  - name: git

设计思路

设计要素 代码体现 设计意图
继承基座 inheritFrom "jenkins-slave" 复用基础配置,减少重复定义
多容器协作 5 个独立容器定义 工具链解耦,各司其职
统一权限 runAsUser: 0 确保所有容器有足够权限执行构建任务
服务账号 jenkins-admin 赋予 Pod 操作 K8s 资源的权限

1.2 容器职责分离

复制代码
// Maven 容器 - 专注 Java 构建
- name: maven
  image: "harbor.apotos.com/maven/maven:3.9.9"
  command: ["sh","-c","sleep infinity"]
​
// BuildKit 服务端 - 专注镜像构建引擎
- name: buildkitd
  image: "moby/buildkit:v0.23.2"
  securityContext:
    privileged: true
​
// BuildKit 客户端 - 专注构建命令执行
- name: buildctl
  image: "moby/buildkit:v0.23.2"
​
// Git 容器 - 专注代码操作
- name: git
  image: "alpine/git:v2.49.0"

设计思路:每个容器只安装必要的工具,避免镜像臃肿,同时便于独立升级和维护。

1.3 共享存储设计

复制代码
volumes:
- name: buildkit-sock
  emptyDir: {}
- name: harbor-registry-ca
  secret:
    secretName: harbor-registry-ca
    optional: false
复制代码
// buildkitd 容器挂载
volumeMounts:
- name: buildkit-sock
  mountPath: /run/buildkit
- name: harbor-registry-ca
  mountPath: /etc/certs/harbor.apotos.com
​
// buildctl 容器挂载相同卷
volumeMounts:
- name: buildkit-sock
  mountPath: /run/buildkit

设计思路

  • emptyDir:在 Pod 生命周期内共享 Unix Socket,实现 buildkitdbuildctl 通信

  • secret:将证书安全地挂载到多个容器,避免证书重复管理


二、Options 全局控制设计

复制代码
options {
    skipDefaultCheckout(true)      // 跳过默认 checkout,自定义控制
    timeout(time: 45, unit: 'MINUTES')  // 全局超时保护
    disableConcurrentBuilds()      // 串行执行,避免冲突
    buildDiscarder(logRotator(numToKeepStr: '30'))  // 保留 30 次构建记录
}

设计思路

配置项 设计意图
skipDefaultCheckout 在自定义 stage 中控制 checkout 时机,支持重试逻辑
timeout 防止异常构建无限占用集群资源
disableConcurrentBuilds 确保部署顺序性,避免版本覆盖
buildDiscarder 控制历史构建数量,节省存储

三、Environment 环境变量设计

复制代码
environment {
    // Harbor 镜像仓库配置
    HARBOR_REGISTRY = "harbor.apotos.com"
    HARBOR_PROJECT  = "spring-petclinic"
    IMAGE_NAME      = "spring-petclinic"
    IMAGE_REPO      = "${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}"
    CACHE_REPO      = "${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}-buildcache:cache"
​
    // Nexus 制品仓库配置
    NEXUS_URL          = "https://nexus.apotos.com"
    NEXUS_RELEASE_REPO = "maven-releases"
    NEXUS_SNAPSHOT_REPO = "maven-snapshots"
​
    // GitOps/ArgoCD 配置
    ARGOCD_REPO     = "https://gitlab.apotos.com/myteam/argocd_deploy.git"
    ARGOCD_BRANCH   = "develop"
    ARGOCD_DIR      = "argocd_deploy"
    ARGOCD_MANIFEST = "apps/dev/spring-petclinic/deploy.yaml"
​
    // SonarQube 配置
    SONARQUBE_SERVER = "SonarScanner"
    SONAR_PROJECT_KEY = "spring-petclinic"
    SONAR_TOKEN_CRED = "sonar-token"
​
    // 网络兼容配置
    GIT_SSL_NO_VERIFY = "true"
}

设计思路

  • 集中配置:所有外部系统地址、项目名称集中管理,便于迁移和维护

  • 变量组合:通过变量拼接生成完整镜像地址,避免硬编码

  • 环境隔离 :通过 ARGOCD_BRANCH 区分不同环境的部署配置


四、Stage 阶段设计

4.1 Checkout 阶段 - 重试机制

复制代码
stage('checkout') {
    steps {
        retry(3) {
            checkout scm
        }
    }
}

设计思路 :网络波动是常见问题,通过 retry(3) 自动重试 3 次,提高流水线稳定性。

4.2 元数据准备 - 版本追踪

复制代码
stage('prepare-metadata') {
    steps {
        script {
            env.BUILD_TIMESTAMP = sh(script: 'TZ=Asia/Shanghai date +%Y%m%d_%H%M%S', returnStdout: true).trim()
            env.GIT_COMMIT_SHORT = (env.GIT_COMMIT ? env.GIT_COMMIT.take(8) : sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim())
            env.IMAGE_TAG  = "${env.BUILD_TIMESTAMP}-${env.GIT_COMMIT_SHORT}"
            env.IMAGE_FULL = "${env.IMAGE_REPO}:${env.IMAGE_TAG}"
        }
    }
}

设计思路

  • 时间戳20260124_143025 格式,便于按时间排序和追溯

  • Git Commit:取前 8 位,关联具体代码版本

  • 组合 Tag20260124_143025-a1b2c3d4 确保全局唯一性

4.3 Maven 构建 - 条件化配置

复制代码
stage('build (maven package)') {
    steps {
        container('maven') {
            sh '''
                if [ -f .mvn/settings.xml ]; then
                  MVN_SETTINGS="-s .mvn/settings.xml"
                else
                  MVN_SETTINGS=""
                fi
                mvn -B ${MVN_SETTINGS} clean package -DskipTests -Dcheckstyle.skip=true
            '''
        }
    }
}

设计思路

  • 容器指定container('maven') 明确在 Maven 容器中执行

  • 配置优先 :优先使用项目自带的 settings.xml,否则使用默认配置

  • 快速构建:跳过测试和检查,CI 阶段专注编译验证

4.4 SonarQube 扫描 - 质量门禁

复制代码
stage('sonarqube-scan') {
    steps {
        container('maven') {
            withCredentials([string(credentialsId: env.SONAR_TOKEN_CRED, variable: 'SONAR_TOKEN')]) {
                withSonarQubeEnv(env.SONARQUBE_SERVER) {
                    sh '''
                        mvn -B ${MVN_SETTINGS} sonar:sonar \
                          -Dsonar.host.url=http://sonarqube.sonarqube.svc.cluster.local:9000 \
                          -Dsonar.projectKey="${SONAR_PROJECT_KEY}" \
                          -Dsonar.login="${SONAR_TOKEN}"
                    '''
                }
            }
        }
    }
}

stage('sonarqube-quality-gate') {
    steps {
        timeout(time: 30, unit: 'MINUTES') {
            waitForQualityGate abortPipeline: true
        }
    }
}

设计思路

  • 凭证注入 :通过 withCredentials 安全传递 Sonar Token

  • 环境绑定withSonarQubeEnv 自动配置 Sonar 连接信息

  • 质量卡点waitForQualityGate 阻塞流水线,abortPipeline: true 确保质量不达标时终止

4.5 Nexus 部署 - 分支策略

复制代码
stage('deploy (nexus)') {
    when {
        expression { return fileExists('pom.xml') }
    }
    steps {
        container('maven') {
            withCredentials([usernamePassword(credentialsId: 'nexus-passwd', ...)]) {
                sh '''
                    REPO_ID="${NEXUS_SNAPSHOT_REPO}"
                    REPO_NAME="maven-snapshots"
                    if [ "${BRANCH_NAME:-}" = "main" ] || [ "${BRANCH_NAME:-}" = "master" ]; then
                      REPO_ID="${NEXUS_RELEASE_REPO}"
                      REPO_NAME="maven-releases"
                    fi
                    
                    mvn -B -s "$HOME/.m2/settings.xml" deploy \
                      -DaltDeploymentRepository="${REPO_NAME}::${NEXUS_URL}/repository/${REPO_ID}"
                '''
            }
        }
    }
}

设计思路

设计点 代码体现 意图
条件执行 when { fileExists('pom.xml') } 仅 Java 项目执行此阶段
分支判断 if [ "${BRANCH_NAME}" = "main" ] main 分支发布 Release,其他发布 Snapshot
动态配置 运行时生成 settings.xml 凭证不落地,构建后自动清理

4.6 BuildKit 镜像构建 - 缓存优化

复制代码
stage('build-and-push (buildkit)') {
    steps {
        container('buildctl') {
            withCredentials([usernamePassword(credentialsId: 'harbor-passwd', ...)]) {
                sh '''
                    # 等待 buildkitd 就绪
                    until buildctl --addr unix:///run/buildkit/buildkitd.sock debug workers >/dev/null 2>&1; do
                      echo "waiting for buildkitd..."
                      sleep 1
                    done

                    # 配置证书和认证
                    export SSL_CERT_FILE=/etc/certs/harbor.apotos.com/ca.crt
                    AUTH="$(printf "%s:%s" "$USERNAME" "$PASSWORD" | base64 | tr -d '\\n')"
                    cat > /root/.docker/config.json <<EOF
{"auths":{"${HARBOR_REGISTRY}":{"auth":"${AUTH}"}}}
EOF

                    # 构建并推送,启用缓存
                    buildctl --addr unix:///run/buildkit/buildkitd.sock build \
                      --frontend dockerfile.v0 \
                      --local context=. \
                      --local dockerfile=. \
                      --import-cache type=registry,ref=${CACHE_REPO} \
                      --export-cache type=registry,ref=${CACHE_REPO},mode=max \
                      --output type=image,name=${IMAGE_FULL},push=true
                '''
            }
        }
    }
}

设计思路

  • 就绪检查until 循环等待 buildkitd 启动完成,避免连接失败

  • 认证配置:动态生成 Docker config.json,避免明文密码

  • 缓存策略--import-cache--export-cache 利用远程 Registry 缓存,加速构建

4.7 ArgoCD 部署更新 - GitOps 流程

复制代码
stage('update-argocd-deploy') {
    steps {
        container('git') {
            withCredentials([usernamePassword(credentialsId: 'gitlab-argocd-token', ...)]) {
                retry(3) {
                    sh '''
                        AUTH_B64="$(printf "%s:%s" "$GIT_USERNAME" "$GIT_PASSWORD" | base64 | tr -d '\\n')"
                        EXTRAHEADER="Authorization: Basic ${AUTH_B64}"

                        # 克隆或更新 GitOps 仓库
                        if [ -d "${ARGOCD_DIR}/.git" ]; then
                          cd "${ARGOCD_DIR}"
                          git fetch origin
                          git checkout "${ARGOCD_BRANCH}"
                          git pull origin "${ARGOCD_BRANCH}"
                        else
                          git clone -b "${ARGOCD_BRANCH}" "${ARGOCD_REPO}" "${ARGOCD_DIR}"
                        fi

                        # 更新镜像标签
                        sed -i -E 's#^([[:space:]]*[-]?[[:space:]]*image:[[:space:]]*).*spring-petclinic/spring-petclinic([:@][^[:space:]]+)?#\\1'"${IMAGE_FULL}"'#' "${ARGOCD_MANIFEST}"

                        # 提交并推送
                        git add "${ARGOCD_MANIFEST}"
                        git commit -m "chore: update spring-petclinic image to ${IMAGE_TAG} [skip ci]"
                        git push origin "${ARGOCD_BRANCH}"
                    '''
                }
            }
        }
    }
}

设计思路

  • GitOps 模式:不直接操作 K8s 集群,而是更新 Git 仓库中的 Manifest

  • 智能克隆:检查目录是否存在,决定 clone 还是 fetch,节省时间

  • 重试机制retry(3) 应对 Git 网络波动

  • 跳过 CI[skip ci] 标记避免触发部署仓库的 CI 流水线


五、Post 动作设计

复制代码
post {
    always {
        script {
            try { deleteDir() } catch (Exception e) { }
        }
    }
}

设计思路

  • always:无论成功失败都执行清理

  • 异常捕获try-catch 确保清理失败不影响构建状态

  • 资源回收:删除工作空间,节省磁盘存储


六、整体设计架构图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      Kubernetes Pod                             │
│  ┌─────────┐  ┌─────────┐  ┌───────────┐  ┌─────────┐  ┌─────┐  │
│  │  jnlp   │  │  maven  │  │ buildkitd │  │buildctl │  │ git │  │
│  │ (代理)  │   │ (构建)  │  │ (服务端)  │  │ (客户端) │  │(操作)│ │
│  └────┬────┘  └────┬────┘  └─────┬─────┘  └────┬────┘  └──┬──┘  │
│       │            │             │             │          │     │
│       └───────────┴─────────────┴─────────────┴──────────┘      │
│                          共享 Volumes                           │
│            ┌─────────────────┬─────────────────┐                │
│            │  buildkit-sock  │  harbor-ca-cert │                │
│            │   (emptyDir)    │    (secret)     │                │
│            └─────────────────┴─────────────────┘                │
└─────────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
   ┌─────────┐          ┌─────────┐          ┌─────────┐
   │  Nexus  │          │ Harbor  │          │ ArgoCD  │
   │ (制品)  │          │ (镜像)   │          │ (部署)  │
   └─────────┘          └─────────┘          └─────────┘

七、设计思路总结

设计维度 核心思路 代码体现
环境隔离 每次构建独立 Pod agent { kubernetes { ... } }
工具解耦 多容器 Sidecar 模式 5 个独立容器定义
版本追踪 时间戳 + Git Commit IMAGE_TAG = TIMESTAMP-COMMIT
质量门禁 Sonar 扫描 + 等待 waitForQualityGate abortPipeline: true
分支策略 main→Release,其他→Snapshot if [ "${BRANCH_NAME}" = "main" ]
构建加速 BuildKit 远程缓存 --import-cache/--export-cache
部署模式 GitOps 间接部署 更新 Git 仓库 Manifest
安全设计 凭证注入 + Secret 挂载 withCredentials + volumes.secret
稳定性 重试 + 超时 + 清理 retry(3) + timeout + deleteDir()

该流水线设计体现了云原生、自动化、可追溯、安全的现代 CI/CD 理念,通过 Kubernetes 动态资源、多容器协作、GitOps 部署等模式,实现了高效可靠的软件交付流程。

相关推荐
leblancAndSherry2 小时前
阿里云轻量/ECS 实战:K3s + Helm + cert-manager + 云效 Codeup 全链路 CI/CD 落地(记录自用)
linux·运维·阿里云·ci/cd·kubernetes·云计算
认真的薛薛2 小时前
5.k8s的deploy-ds-nfs-loadbalancer
云原生·容器·kubernetes
@hdd12 小时前
工作节点组件详解:kubelet、kube-proxy 与容器运行时
容器·kubernetes
@hdd12 小时前
Kubernetes 网络模型:Pod 通信、Service 网络与 CNI
网络·云原生·容器·kubernetes
only_Klein17 小时前
kubernetes-ReplicaSet控制器
容器·kubernetes
only_Klein18 小时前
Kubernetes-DaemonSet控制器
容器·kubernetes
阿寻寻1 天前
【云原生技术】探针**就是:Kubelet(K8s 节点上的组件)会**进入容器里执行一条命令**,根据命令的退出码判断健康
云原生·kubernetes·kubelet
迎仔1 天前
11-裸金属算力中心:K8s的实际价值与“管一切“的体现
云原生·容器·kubernetes
岁岁种桃花儿1 天前
kubeadm构建单master多Node的k8s集群。
云原生·容器·kubernetes