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>
这个提交包含在:
父节点
07d6307b5d
当前提交
0572bbdd38
@ -4,13 +4,16 @@ WORKDIR /workspace
|
|||||||
|
|
||||||
COPY package.json ./package.json
|
COPY package.json ./package.json
|
||||||
COPY yarn.lock ./yarn.lock
|
COPY yarn.lock ./yarn.lock
|
||||||
COPY ops-platform ./ops-platform
|
|
||||||
|
|
||||||
ENV YARN_CACHE_FOLDER=/var/cache/yarn
|
ENV YARN_CACHE_FOLDER=/var/cache/yarn
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/var/cache/yarn,sharing=locked \
|
RUN --mount=type=cache,target=/var/cache/yarn,sharing=locked \
|
||||||
yarn install --frozen-lockfile
|
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_APP_BASE=/
|
||||||
ARG OPS_API_BASE_URL=/api
|
ARG OPS_API_BASE_URL=/api
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ pipeline {
|
|||||||
parameters {
|
parameters {
|
||||||
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
||||||
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
||||||
|
booleanParam(name: 'NO_CACHE', defaultValue: false, description: '禁用 Docker 构建缓存(缓存错误时使用)')
|
||||||
}
|
}
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
@ -43,12 +44,20 @@ pipeline {
|
|||||||
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
||||||
script {
|
script {
|
||||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
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 """
|
bat """
|
||||||
docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS%
|
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 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 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') {
|
stage('Deploy to Production') {
|
||||||
when { expression { return params.DEPLOY } }
|
when { expression { return params.DEPLOY } }
|
||||||
steps {
|
steps {
|
||||||
|
lock('prod-deploy') {
|
||||||
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
||||||
script {
|
script {
|
||||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||||
|
retry(2) {
|
||||||
bat """
|
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 {
|
post {
|
||||||
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }
|
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }
|
||||||
|
|||||||
@ -4,6 +4,7 @@ pipeline {
|
|||||||
parameters {
|
parameters {
|
||||||
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag')
|
||||||
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署')
|
||||||
|
booleanParam(name: 'NO_CACHE', defaultValue: false, description: '禁用 Docker 构建缓存(缓存错误时使用)')
|
||||||
}
|
}
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
@ -43,12 +44,20 @@ pipeline {
|
|||||||
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
||||||
script {
|
script {
|
||||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
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 """
|
bat """
|
||||||
docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS%
|
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 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 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') {
|
stage('Deploy to Production') {
|
||||||
when { expression { return params.DEPLOY } }
|
when { expression { return params.DEPLOY } }
|
||||||
steps {
|
steps {
|
||||||
|
lock('prod-deploy') {
|
||||||
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) {
|
||||||
script {
|
script {
|
||||||
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
|
||||||
|
retry(2) {
|
||||||
bat """
|
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 {
|
post {
|
||||||
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }
|
success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" }
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户