From a4a693837e65aa4eaef9c418c773b8146b442099 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 24 Aug 2026 10:56:33 +0800 Subject: [PATCH] perf(ci): acquire deploy lock before target agent --- Jenkinsfile.service | 42 +++++++++---------- ...est-environment-pipeline-contract.test.mjs | 15 +++++++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Jenkinsfile.service b/Jenkinsfile.service index 3a6077aa8..bc3edba32 100644 --- a/Jenkinsfile.service +++ b/Jenkinsfile.service @@ -165,37 +165,37 @@ pipeline { beforeAgent true expression { params.DEPLOY && ['development', 'test'].contains(params.ENVIRONMENT) } } - agent { - label "${params.ENVIRONMENT == 'development' ? 'java-dev' : 'java-test'}" - } + agent none steps { - deleteDir() - unstash 'deployment-files' script { String deployLock = "firstleg-${params.ENVIRONMENT}-deploy" String credentialId = "firstleg-${params.ENVIRONMENT}-env" + String targetLabel = params.ENVIRONMENT == 'development' ? 'java-dev' : 'java-test' lock(resource: deployLock, inversePrecedence: true) { - echo "在目标节点 ${env.NODE_NAME} 本机部署到 ${params.ENVIRONMENT}" - withEnv([ - 'TARGET_IS_LOCAL=true', - "EVENT_BRIDGE_REGION_OVERRIDE=${params.EVENT_BRIDGE_REGION}" - ]) { - withCredentials([ - file(credentialsId: credentialId, variable: 'DEPLOY_ENV_FILE'), - usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, - usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD') - ]) { - sh "sh deploy/scripts/deploy-built-service.sh '${params.ENVIRONMENT}' '${env.RESOLVED_IMAGE_REPOSITORY}' '${env.RESOLVED_IMAGE_TAG}' '${env.RESOLVED_SERVICE}' \"\$DEPLOY_ENV_FILE\"" + node(targetLabel) { + deleteDir() + try { + unstash 'deployment-files' + echo "在目标节点 ${env.NODE_NAME} 本机部署到 ${params.ENVIRONMENT}" + withEnv([ + 'TARGET_IS_LOCAL=true', + "EVENT_BRIDGE_REGION_OVERRIDE=${params.EVENT_BRIDGE_REGION}" + ]) { + withCredentials([ + file(credentialsId: credentialId, variable: 'DEPLOY_ENV_FILE'), + usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, + usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD') + ]) { + sh "sh deploy/scripts/deploy-built-service.sh '${params.ENVIRONMENT}' '${env.RESOLVED_IMAGE_REPOSITORY}' '${env.RESOLVED_IMAGE_TAG}' '${env.RESOLVED_SERVICE}' \"\$DEPLOY_ENV_FILE\"" + } + } + } finally { + cleanWs(deleteDirs: true, notFailBuild: true) } } } } } - post { - always { - cleanWs(deleteDirs: true, notFailBuild: true) - } - } } stage('部署单个服务') { diff --git a/scripts/test-environment-pipeline-contract.test.mjs b/scripts/test-environment-pipeline-contract.test.mjs index 242b12638..3ccf85117 100644 --- a/scripts/test-environment-pipeline-contract.test.mjs +++ b/scripts/test-environment-pipeline-contract.test.mjs @@ -49,6 +49,21 @@ test('Java builds use the shared builder pool and serialize incremental target d assert.match(directDeployScript, /--pull never/) }) +test('development and test deployment locks are acquired before target executors', () => { + const directDeployStage = pipeline.slice( + pipeline.indexOf("stage('部署开发或测试环境')"), + pipeline.indexOf("stage('部署单个服务')") + ) + const lockIndex = directDeployStage.indexOf('lock(resource: deployLock') + const nodeIndex = directDeployStage.indexOf('node(targetLabel)') + + assert.match(directDeployStage, /agent none/) + assert.match(directDeployStage, /String targetLabel = params\.ENVIRONMENT == 'development' \? 'java-dev' : 'java-test'/) + assert.ok(lockIndex >= 0, 'deployment lock must exist') + assert.ok(nodeIndex >= 0, 'target node allocation must exist') + assert.ok(lockIndex < nodeIndex, 'deployment lock must be acquired before target node allocation') +}) + test('parallel remote deployments keep env and compose assets isolated by project', () => { assert.match(directDeployScript, /REMOTE_ENV_NAME="\.env\.\$PROJECT_NAME"/) assert.match(directDeployScript, /REMOTE_COMPOSE_NAME="docker-compose\.\$PROJECT_NAME\.yml"/)