fix(ci): 修复 Jenkinsfile.tenant-web/ops-web 缓存穿透和错误处理

- 添加 --build-arg GIT_COMMIT 使 Docker 每次提交都重新构建源码层
- 添加 NO_CACHE 参数支持强制禁用缓存
- 修复 bat 错误处理(if %errorlevel% neq 0 exit /b 1)
- Deploy 阶段添加 lock('prod-deploy') + retry(2) 防并发失败
- Dockerfile.ops: 将 COPY ops-platform 移到 yarn install 之后,添加 ARG GIT_COMMIT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-11 17:05:43 +08:00
父节点 07d6307b5d
当前提交 0572bbdd38
共有 3 个文件被更改,包括 46 次插入17 次删除

查看文件

@ -4,13 +4,16 @@ WORKDIR /workspace
COPY package.json ./package.json
COPY yarn.lock ./yarn.lock
COPY ops-platform ./ops-platform
ENV YARN_CACHE_FOLDER=/var/cache/yarn
RUN --mount=type=cache,target=/var/cache/yarn,sharing=locked \
yarn install --frozen-lockfile
# GIT_COMMIT invalidates source-code layers on every commit, without re-running yarn install
ARG GIT_COMMIT=unknown
COPY ops-platform ./ops-platform
ARG OPS_APP_BASE=/
ARG OPS_API_BASE_URL=/api

查看文件

@ -4,6 +4,7 @@ pipeline {
parameters {
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
booleanParam(name: 'NO_CACHE', defaultValue: false, description: '禁用 Docker 构建缓存(缓存错误时使用)')
}
environment {
@ -43,12 +44,20 @@ pipeline {
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
script {
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
def cacheArgs = params.NO_CACHE
? '--no-cache'
: "--build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage}"
def gitCommit = env.GIT_COMMIT ?: 'unknown'
bat """
docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS%
if %errorlevel% neq 0 exit /b 1
docker pull --platform=linux/amd64 ${fullImage} || echo Pull failed, will build fresh
docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage} -t ${fullImage} .
docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg GIT_COMMIT=${gitCommit} ${cacheArgs} -t ${fullImage} .
if %errorlevel% neq 0 exit /b 1
docker push ${fullImage}
docker rmi ${fullImage} || exit 0
if %errorlevel% neq 0 exit /b 1
docker rmi ${fullImage}
exit /b 0
"""
}
}
@ -58,17 +67,21 @@ pipeline {
stage('Deploy to Production') {
when { expression { return params.DEPLOY } }
steps {
lock('prod-deploy') {
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
script {
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
retry(2) {
bat """
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} && docker image prune -f"
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker image prune -f 2>/dev/null || true; docker pull ${fullImage} || exit 1; docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} || exit 1; docker image prune -f"
"""
}
}
}
}
}
}
}
post {
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }

查看文件

@ -4,6 +4,7 @@ pipeline {
parameters {
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
booleanParam(name: 'NO_CACHE', defaultValue: false, description: '禁用 Docker 构建缓存(缓存错误时使用)')
}
environment {
@ -43,12 +44,20 @@ pipeline {
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
script {
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
def cacheArgs = params.NO_CACHE
? '--no-cache'
: "--build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage}"
def gitCommit = env.GIT_COMMIT ?: 'unknown'
bat """
docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS%
if %errorlevel% neq 0 exit /b 1
docker pull --platform=linux/amd64 ${fullImage} || echo Pull failed, will build fresh
docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage} -t ${fullImage} .
docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg GIT_COMMIT=${gitCommit} ${cacheArgs} -t ${fullImage} .
if %errorlevel% neq 0 exit /b 1
docker push ${fullImage}
docker rmi ${fullImage} || exit 0
if %errorlevel% neq 0 exit /b 1
docker rmi ${fullImage}
exit /b 0
"""
}
}
@ -58,17 +67,21 @@ pipeline {
stage('Deploy to Production') {
when { expression { return params.DEPLOY } }
steps {
lock('prod-deploy') {
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
script {
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
retry(2) {
bat """
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker rm -f xuqm-${env.DEPLOY_SERVICE} 2>/dev/null || true; docker pull ${fullImage} && docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} && docker image prune -f"
ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker image prune -f 2>/dev/null || true; docker pull ${fullImage} || exit 1; docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} || exit 1; docker image prune -f"
"""
}
}
}
}
}
}
}
post {
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }