# 09. 构建与多环境部署 ## 决策 Gradle 多模块统一构建,`bootstrap` 产出单一 jar/Docker 镜像;沿用现有 GitLab CI/CD → Azure 部署链路(见 [Architecture-Diagram/gitlab-cicd-azure-deployment-diagram.drawio](../Architecture-Diagram/gitlab-cicd-azure-deployment-diagram.drawio))。 ## 结构约定 ``` settings.gradle # include 所有 platform-* / domains/*(见 01-project-structure.md) build.gradle # 根工程统一 Kotlin/Spring Boot 插件版本、依赖约束 bootstrap/build.gradle # bootJar,构建出可执行 jar Dockerfile # 基于 bootstrap 的 jar 打镜像 .gitlab-ci.yml # build -> test -> docker build/push -> deploy ``` ## Dockerfile 示例(多阶段构建) ```dockerfile # Dockerfile FROM eclipse-temurin:21-jdk AS build WORKDIR /workspace COPY . . RUN ./gradlew :bootstrap:bootJar --no-daemon FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /workspace/bootstrap/build/libs/*.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] ``` 多阶段构建的好处:最终镜像只包含 JRE + 一个 jar,不带 Gradle 缓存、源码、编译工具链,镜像体积和攻击面都更小。 ## GitLab CI 示例(节选) ```yaml # .gitlab-ci.yml stages: - build - test - package - deploy build: stage: build script: - ./gradlew build -x test --no-daemon test: stage: test script: - ./gradlew test --no-daemon artifacts: reports: junit: '**/build/test-results/test/TEST-*.xml' docker-build: stage: package script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA deploy-uat: stage: deploy environment: uat only: - main script: - kubectl set image deployment/conti-backend conti-backend=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n retailapp-uat ``` 具体 stage 划分、部署到 Azure AKS 的凭证配置沿用现有 `gitlab-cicd-azure-deployment-diagram` 里已经跑通的流程,这里只体现"单一镜像、按环境部署"这条主线。 ## 关键规则 - 多环境(dev/uat/prod)通过 K8s namespace + ConfigMap/Secret 区分(见 [07-config-governance.md](./07-config-governance.md)),**镜像本身不区分环境**,同一个镜像跨环境部署,只是挂载的 ConfigMap/Secret 和 `SPRING_PROFILES_ACTIVE` 不同——避免"UAT 验证过的镜像和 Prod 部署的镜像不是同一个产物"这种环境不一致风险。 - CI 流程顺序:Gradle build(含单元测试)→ 打 Docker 镜像 → 推送镜像仓库 → GitLab CI/CD 触发 Azure/K8s 部署。 - 模块化单体阶段只有一个部署产物(一个 Deployment);如果后续拆分微服务,每个 domain 各自补一份 `Dockerfile` 和 CI job,工程结构上已经按模块划好边界(见 [01-project-structure.md](./01-project-structure.md)),拆分成本较低——本质上是把 `bootstrap` 依赖的某个 `domains/xxx` 模块摘出来,单独套一层 `@SpringBootApplication` 入口和自己的 `Dockerfile`。 ## 附录:为什么坚持"一个镜像走所有环境" 一种常见但有风险的做法是:给每个环境单独打包(比如构建时注入 `application-uat.yml` 到镜像里),这样看起来"环境隔离更彻底",但实际引入了一个更严重的问题——**UAT 验证通过的镜像和 Prod 部署的镜像,字节级别就不是同一个东西**,即使代码版本号一样,构建过程中的依赖解析、基础镜像 layer 缓存状态都可能有细微差异,理论上会出现"UAT 测过没问题,Prod 部署后行为不一致"的情况,而且事后很难证明"两次构建到底有没有差异"。 "一个镜像走所有环境"(Build once, deploy many)反过来保证:镜像本身在所有环境完全一致,环境差异只体现在外部注入的配置(ConfigMap/Secret/环境变量)上。这也是 [The Twelve-Factor App](https://12factor.net/zh_cn/build-release-run) 里"严格分离构建和运行"这条原则的直接应用。 ## 待补充 - 具体 CI pipeline 的完整 stage 定义和镜像版本/tag 策略。 - 灰度发布/回滚方案。 - Gradle 构建缓存/并行构建的 CI 加速配置。 ## 参考链接 - [The Twelve-Factor App](https://12factor.net/zh_cn/) - [Gradle 官方 Docker 集成建议](https://docs.gradle.org/current/userguide/multi_project_builds.html) - [Spring Boot 官方 Docker 打包指南](https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html)