diff --git a/.gitattributes b/.gitattributes index d54690bad..bf67ef190 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,8 @@ *.yaml text eol=lf *.md text eol=lf *.sql text eol=lf +*.sh text eol=lf +Jenkinsfile text eol=lf *.ps1 text eol=crlf *.cmd text eol=crlf *.bat text eol=crlf @@ -13,4 +15,3 @@ *.jpg binary *.jpeg binary *.pdf binary - diff --git a/Jenkinsfile b/Jenkinsfile index 18b8cda80..f30870d01 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -67,7 +67,7 @@ pipeline { choice(name: 'ENVIRONMENT', choices: ['test', 'staging', 'production'], description: '部署环境') booleanParam(name: 'DEPLOY', defaultValue: false, description: '构建成功后是否部署') booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '是否跳过测试;生产构建不建议勾选') - string(name: 'IMAGE_REPOSITORY', defaultValue: '8.163.30.128:10082/java-project/baiyue-firstleg', description: 'Gitea 容器镜像仓库地址') + string(name: 'IMAGE_REPOSITORY', defaultValue: '8.163.30.128:10082/java-project/baiyue-firstleg', description: '预发布/生产镜像仓库地址;test 环境自动使用本机镜像,无需修改') string(name: 'DOCKER_BASE_IMAGE', defaultValue: 'docker.m.daocloud.io/library/eclipse-temurin:17-jre-alpine', description: 'Java 17 基础镜像;默认使用国内镜像代理') } @@ -81,13 +81,18 @@ pipeline { stage('确定构建范围') { steps { script { - String requestedRepository = params.IMAGE_REPOSITORY?.trim() - if (!requestedRepository || requestedRepository == 'registry.example.com/your-group/baiyue-firstleg') { - requestedRepository = '8.163.30.128:10082/java-project/baiyue-firstleg' - echo "检测到旧的镜像仓库占位地址,已自动切换为测试环境 Gitea:${requestedRepository}" - } - if (requestedRepository.contains('://') || !requestedRepository.contains('/')) { - error('IMAGE_REPOSITORY 格式错误:请填写 host[:port]/owner/image,不要包含 http:// 或 https://') + String requestedRepository + if (params.ENVIRONMENT == 'test') { + requestedRepository = 'baiyue-firstleg-test' + echo '测试环境使用 Jenkins 节点本机镜像,不登录、不推送远程 Registry' + } else { + requestedRepository = params.IMAGE_REPOSITORY?.trim() + if (!requestedRepository || requestedRepository == 'registry.example.com/your-group/baiyue-firstleg') { + error('预发布/生产环境必须填写真实的 HTTPS 镜像仓库地址') + } + if (requestedRepository.contains('://') || !requestedRepository.contains('/')) { + error('IMAGE_REPOSITORY 格式错误:请填写 host[:port]/owner/image,不要包含 http:// 或 https://') + } } env.RESOLVED_IMAGE_REPOSITORY = requestedRepository @@ -150,19 +155,27 @@ pipeline { } } - stage('构建并推送镜像') { + stage('构建镜像并按环境发布') { when { expression { env.NO_SERVICE_CHANGE != 'true' } } steps { script { - String registryHost = env.RESOLVED_IMAGE_REPOSITORY.tokenize('/')[0] - withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { - sh 'printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "' + registryHost + '"' - env.SELECTED_SERVICES.split(',').each { service -> - String image = "${env.RESOLVED_IMAGE_REPOSITORY}/${service}:${env.IMAGE_TAG}" - sh "docker build -f deploy/docker/Dockerfile --build-arg BASE_IMAGE=${params.DOCKER_BASE_IMAGE} --build-arg JAR_FILE=${serviceJars[service]} -t ${image} ." - sh "docker push ${image}" + env.SELECTED_SERVICES.split(',').each { service -> + String image = "${env.RESOLVED_IMAGE_REPOSITORY}/${service}:${env.IMAGE_TAG}" + sh "docker build -f deploy/docker/Dockerfile --build-arg BASE_IMAGE=${params.DOCKER_BASE_IMAGE} --build-arg JAR_FILE=${serviceJars[service]} -t ${image} ." + } + + if (params.ENVIRONMENT == 'test') { + echo "测试镜像已保存在当前 Docker 节点:${env.RESOLVED_IMAGE_REPOSITORY}/*:${env.IMAGE_TAG}" + } else { + String registryHost = env.RESOLVED_IMAGE_REPOSITORY.tokenize('/')[0] + withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { + sh 'printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "' + registryHost + '"' + env.SELECTED_SERVICES.split(',').each { service -> + String image = "${env.RESOLVED_IMAGE_REPOSITORY}/${service}:${env.IMAGE_TAG}" + sh "docker push ${image}" + } + sh "docker logout ${registryHost} || true" } - sh "docker logout ${registryHost} || true" } } } diff --git a/deploy/env/.env.test.example b/deploy/env/.env.test.example index 794a5141f..f524b7e8c 100644 --- a/deploy/env/.env.test.example +++ b/deploy/env/.env.test.example @@ -1,6 +1,7 @@ # 复制本文件仅用于本地验证。Jenkins 中应创建“Secret file”凭据,禁止提交真实 .env。 DEPLOY_ENV=test -IMAGE_REPOSITORY=8.163.30.128:10082/java-project/baiyue-firstleg +# Jenkins 部署时会覆盖为 baiyue-firstleg-test;本地手工部署可使用此默认值。 +IMAGE_REPOSITORY=baiyue-firstleg-test IMAGE_TAG=latest INFRA_NETWORK=firstleg-test-infra-network diff --git a/deploy/scripts/deploy-services.sh b/deploy/scripts/deploy-services.sh index 37a41301e..4271e1434 100644 --- a/deploy/scripts/deploy-services.sh +++ b/deploy/scripts/deploy-services.sh @@ -20,17 +20,23 @@ fi SERVICES=$(printf '%s' "$SERVICE_CSV" | tr ',' ' ') export DEPLOY_ENV IMAGE_REPOSITORY IMAGE_TAG -docker compose \ - --project-name "firstleg-${DEPLOY_ENV}" \ - --env-file "$ENV_FILE" \ - -f deploy/docker-compose.yml \ - pull $SERVICES + +PULL_POLICY_ARGS="" +if [ "$DEPLOY_ENV" = "test" ]; then + PULL_POLICY_ARGS="--pull never" +else + docker compose \ + --project-name "firstleg-${DEPLOY_ENV}" \ + --env-file "$ENV_FILE" \ + -f deploy/docker-compose.yml \ + pull $SERVICES +fi docker compose \ --project-name "firstleg-${DEPLOY_ENV}" \ --env-file "$ENV_FILE" \ -f deploy/docker-compose.yml \ - up -d --remove-orphans $SERVICES + up -d --remove-orphans $PULL_POLICY_ARGS $SERVICES docker compose \ --project-name "firstleg-${DEPLOY_ENV}" \ diff --git a/docs/jenkins-multi-service-deployment.md b/docs/jenkins-multi-service-deployment.md index b574a6e52..c0bf1970c 100644 --- a/docs/jenkins-multi-service-deployment.md +++ b/docs/jenkins-multi-service-deployment.md @@ -18,6 +18,8 @@ MySQL、Redis、Nacos 是外部基础设施,不包含在业务 Compose 文件 | `deploy/env/.env.test.example` | 测试环境变量模板,不含真实密码 | | `deploy/scripts/deploy-services.sh` | Jenkins 节点上的增量部署脚本 | +测试环境服务器准备步骤详见 `docs/test-environment-deployment.md`。 + ## 三、Jenkins 前置条件 Jenkins 新建“多分支流水线”或“流水线”,脚本路径填写 `Jenkinsfile`。执行节点标签为 `docker-java17`,节点需要安装: