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,实现buildkitd与buildctl通信 -
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 位,关联具体代码版本
-
组合 Tag :
20260124_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 部署等模式,实现了高效可靠的软件交付流程。