diff --git a/deploy/docker-compose.test-limits.yml b/deploy/docker-compose.test-limits.yml new file mode 100644 index 000000000..353b3a0cd --- /dev/null +++ b/deploy/docker-compose.test-limits.yml @@ -0,0 +1,65 @@ +# 仅由 TEST 部署入口叠加,固定 JVM 堆并为线程、元空间和直接内存保留余量。 +x-test-limits: &test-limits + mem_limit: 1536m + memswap_limit: 1536m + environment: &test-java-environment + JAVA_OPTS: "-XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8" + JAVA_TOOL_OPTIONS: "-Xms128m -Xmx768m" + +services: + gateway: + <<: *test-limits + + auth: + <<: *test-limits + + iam: + <<: *test-limits + + order: + <<: *test-limits + + firstleg: + <<: *test-limits + mem_limit: 2g + memswap_limit: 2g + environment: + <<: *test-java-environment + JAVA_TOOL_OPTIONS: "-Xms256m -Xmx1g" + + finance: + <<: *test-limits + + warehouse: + <<: *test-limits + + warehouse-overseas-service: + <<: *test-limits + + integration-service: + <<: *test-limits + + audit-service: + <<: *test-limits + + file: + <<: *test-limits + mem_limit: 3g + memswap_limit: 3g + environment: + <<: *test-java-environment + JAVA_TOOL_OPTIONS: "-Xms1g -Xmx2g" + + print: + <<: *test-limits + environment: + <<: *test-java-environment + JAVA_TOOL_OPTIONS: "-Xms512m -Xmx1g" + + lastleg-service: + <<: *test-limits + mem_limit: 3g + memswap_limit: 3g + environment: + <<: *test-java-environment + JAVA_TOOL_OPTIONS: "-Xms512m -Xmx1536m" diff --git a/deploy/scripts/deploy-built-service.sh b/deploy/scripts/deploy-built-service.sh index 995da8f58..b6f420f4a 100644 --- a/deploy/scripts/deploy-built-service.sh +++ b/deploy/scripts/deploy-built-service.sh @@ -12,6 +12,7 @@ IMAGE_TAG="$3" SERVICE="$4" ENV_FILE="$5" COMPOSE_FILE="deploy/docker-compose.yml" +TEST_LIMITS_FILE="deploy/docker-compose.test-limits.yml" IMAGE="$IMAGE_REPOSITORY/$SERVICE:$IMAGE_TAG" REGISTRY_HOST="${IMAGE_REPOSITORY%%/*}" PROJECT_NAME="firstleg-$DEPLOY_ENV" @@ -40,6 +41,9 @@ case "${ALLOW_SAME_CONTRACT_ROLLBACK:-false}" in true|false) ;; *) exit 2 ;; esa if [ "$DEPLOY_ENV" = "test" ] && [ "${TARGET_IS_LOCAL:-false}" != "true" ]; then echo "TEST deploy must run on the target host" >&2; exit 2 fi +if [ "$DEPLOY_ENV" = "test" ] && [ ! -f "$TEST_LIMITS_FILE" ]; then + echo "TEST memory limits file missing: $TEST_LIMITS_FILE" >&2; exit 2 +fi [ -f "$ENV_FILE" ] || { echo "Environment file missing" >&2; exit 2; } env_file_value() { sed -n "s/^$1=//p" "$ENV_FILE" | tail -n 1 | tr -d '\r'; } for key in NACOS_SERVER_ADDR NACOS_NAMESPACE NACOS_USERNAME NACOS_PASSWORD REDIS_HOST REDIS_PORT REDIS_DATABASE REDIS_TOKEN_KEY_PREFIX INTERNAL_SERVICE_TOKEN JWT_RSA_JWK_SET JWT_CURRENT_KID ROCKETMQ_NAME_SERVER; do @@ -74,7 +78,11 @@ local_registry_logout() { if [ -n "${deploy_docker_config:-}" ]; then rm -rf -- "$deploy_docker_config"; fi } +# TEST 的查询、部署和回滚共用同一份内存限额,缺失时由入口提前拒绝。 local_compose() { + if [ "$DEPLOY_ENV" = "test" ]; then + set -- -f "$TEST_LIMITS_FILE" "$@" + fi docker compose \ --project-name "$PROJECT_NAME" \ --env-file "$ENV_FILE" \ diff --git a/scripts/deploy-built-service-rollback.test.mjs b/scripts/deploy-built-service-rollback.test.mjs index f4f82baae..94d659e8d 100644 --- a/scripts/deploy-built-service-rollback.test.mjs +++ b/scripts/deploy-built-service-rollback.test.mjs @@ -69,7 +69,8 @@ case "$command" in esac ` -async function runDeployment({ imageTag, failingTag, failingStartTag, restartingTag, allowRollback = true, environment = 'test', service = 'audit-service', missingHealth = false, topicPrefix = `${environment}-`, remote = false }) { +// 通过临时 Docker/SSH 替身验证环境选择、健康门禁及失败后的镜像恢复。 +async function runDeployment({ imageTag, failingTag, failingStartTag, restartingTag, allowRollback = true, environment = 'test', service = 'audit-service', missingHealth = false, missingTestLimits = false, topicPrefix = `${environment}-`, remote = false }) { const temporaryDirectory = await mkdtemp(join(tmpdir(), 'firstleg-rollback-')) const mockBin = join(temporaryDirectory, 'bin') const dockerPath = join(mockBin, 'docker') @@ -115,7 +116,7 @@ cp "$1" "\${2#*:}" service, envFile.replaceAll('\\', '/') ], { - cwd: repositoryRoot, + cwd: missingTestLimits ? temporaryDirectory : repositoryRoot, encoding: 'utf8', env: { ...process.env, @@ -158,6 +159,16 @@ test('successful deployment keeps the new healthy image', async (context) => { assert.match(result.stdout, /部署前运行镜像: registry\.local\/group\/firstleg\/audit-service:oldgood/) assert.match(result.stdout, /新镜像健康检查通过: registry\.local\/group\/firstleg\/audit-service:newgood/) assert.equal(result.currentImage, 'registry.local/group/firstleg/audit-service:newgood') + for (const command of result.dockerLog.split(/\r?\n/).filter(line => line.startsWith('compose '))) { + assert.match(command, /-f deploy\/docker-compose\.yml -f deploy\/docker-compose\.test-limits\.yml /) + } +}) + +test('TEST rejects a missing memory limits file before Docker', async () => { + const result = await runDeployment({ imageTag: 'newgood', missingTestLimits: true }) + assert.equal(result.status, 2) + assert.equal(result.dockerLog, '') + assert.match(result.stderr, /TEST memory limits file missing/) }) test('unhealthy deployment restores the previous healthy image and still fails the job', async (context) => { @@ -232,6 +243,7 @@ test('gray deploy preserves health checks and uses an isolated Compose project', assert.equal(result.status, 0, result.stderr) assert.match(result.dockerLog, /--project-name firstleg-production/) assert.doesNotMatch(result.dockerLog, /--project-name firstleg-test/) + assert.doesNotMatch(result.dockerLog, /docker-compose\.test-limits\.yml/) }) test('gray deployment rejects TEST message scope before any Docker operation', async () => {