- Introduced integration layer design with Resilience4j for external vendor calls. - Established API design standards with unified response structures and global exception handling. - Defined configuration and service governance using Kubernetes native solutions. - Implemented observability practices including trace ID propagation and structured logging. - Outlined build and multi-environment deployment strategies using Gradle and GitLab CI/CD. - Specified testing strategies across different layers, utilizing JUnit, MockK, Testcontainers, and WireMock.
97 lines
4.5 KiB
Markdown
97 lines
4.5 KiB
Markdown
# 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)
|