diff --git a/Jenkinsfile.service b/Jenkinsfile.service index 9a67d98b0..a38b7503b 100644 --- a/Jenkinsfile.service +++ b/Jenkinsfile.service @@ -167,6 +167,8 @@ pipeline { } steps { script { + String deployLock = "firstleg-${params.ENVIRONMENT}-deploy" + lock(resource: deployLock, inversePrecedence: true) { boolean development = params.ENVIRONMENT == 'development' boolean testTargetIsLocal = ['master', 'built-in'].contains(env.NODE_NAME) || ['master', 'built-in'].any { label -> env.NODE_LABELS?.tokenize()?.contains(label) } @@ -194,10 +196,15 @@ pipeline { "TARGET_SSH_KNOWN_HOSTS=${remoteKnownHosts}", "TARGET_DEPLOY_DIR=${remoteDir}" ]) { - withCredentials([file(credentialsId: credentialId, variable: 'DEPLOY_ENV_FILE')]) { + 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\"" } } + } } } } diff --git a/deploy/scripts/deploy-built-service.sh b/deploy/scripts/deploy-built-service.sh index d10d16685..5afa92b0a 100644 --- a/deploy/scripts/deploy-built-service.sh +++ b/deploy/scripts/deploy-built-service.sh @@ -13,6 +13,7 @@ SERVICE="$4" ENV_FILE="$5" COMPOSE_FILE="deploy/docker-compose.yml" IMAGE="$IMAGE_REPOSITORY/$SERVICE:$IMAGE_TAG" +REGISTRY_HOST="${IMAGE_REPOSITORY%%/*}" PROJECT_NAME="firstleg-$DEPLOY_ENV" case "$DEPLOY_ENV" in @@ -157,9 +158,17 @@ if [ "$SERVICE" = "file" ]; then fi fi -docker image inspect "$IMAGE" >/dev/null +: "${REGISTRY_USER:?REGISTRY_USER 未设置}" +: "${REGISTRY_PASSWORD:?REGISTRY_PASSWORD 未设置}" + +local_registry_logout() { + docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true +} deploy_local() { + printf '%s' "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + trap local_registry_logout EXIT HUP INT TERM + docker pull "$IMAGE" export DEPLOY_ENV IMAGE_REPOSITORY IMAGE_TAG docker compose \ --project-name "$PROJECT_NAME" \ @@ -171,6 +180,8 @@ deploy_local() { --env-file "$ENV_FILE" \ -f "$COMPOSE_FILE" \ ps "$SERVICE" + local_registry_logout + trap - EXIT HUP INT TERM } if [ "${TARGET_IS_LOCAL:-false}" = "true" ]; then @@ -232,8 +243,14 @@ mv -f "$env_tmp" "$env_target" mv -f "$compose_tmp" "$compose_target" REMOTE_PREPARE -echo "传输镜像到 $SSH_TARGET: $IMAGE" -docker save "$IMAGE" | gzip -1 | run_ssh 'gzip -dc | docker load' +remote_registry_logout() { + run_ssh docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true +} + +echo "目标服务器从共享仓库拉取增量镜像: $IMAGE" +printf '%s' "$REGISTRY_PASSWORD" | run_ssh docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" +trap remote_registry_logout EXIT HUP INT TERM +run_ssh docker pull "$IMAGE" run_ssh sh -s -- "$REMOTE_DIR" "$DEPLOY_ENV" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE" "$PROJECT_NAME" "$REMOTE_ENV_NAME" "$REMOTE_COMPOSE_NAME" <<'REMOTE_DEPLOY' set -eu @@ -260,3 +277,6 @@ docker compose \ -f "$compose_file" \ ps "$service" REMOTE_DEPLOY + +remote_registry_logout +trap - EXIT HUP INT TERM diff --git a/scripts/test-environment-pipeline-contract.test.mjs b/scripts/test-environment-pipeline-contract.test.mjs index e3463170f..f1369963d 100644 --- a/scripts/test-environment-pipeline-contract.test.mjs +++ b/scripts/test-environment-pipeline-contract.test.mjs @@ -30,12 +30,16 @@ test('production deployments cannot skip tests', () => { assert.match(pipeline, /error\('生产环境禁止跳过测试'\)/) }) -test('Java builds use the shared builder pool and deploy on the target environment node', () => { +test('Java builds use the shared builder pool and serialize incremental target deployments', () => { assert.match(pipeline, /agent \{ label 'java-builder' \}/) assert.match(pipeline, /\['development', 'test'\]\.contains\(params\.ENVIRONMENT\)/) assert.match(pipeline, /\['master', 'built-in'\]\.contains\(env\.NODE_NAME\)/) + assert.match(pipeline, /String deployLock = "firstleg-\$\{params\.ENVIRONMENT\}-deploy"/) + assert.match(pipeline, /lock\(resource: deployLock, inversePrecedence: true\)/) assert.match(pipeline, /deploy-built-service\.sh/) - assert.match(directDeployScript, /docker save "\$IMAGE" \| gzip -1 \| run_ssh/) + assert.match(directDeployScript, /docker pull "\$IMAGE"/) + assert.match(directDeployScript, /run_ssh docker pull "\$IMAGE"/) + assert.doesNotMatch(directDeployScript, /docker save "\$IMAGE"/) assert.match(directDeployScript, /--pull never/) })