Files
conti-docs/backend/09-build-deploy.md
T

97 lines
4.5 KiB
Markdown
Raw Normal View History

# 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)