feat: Enhance documentation on layering, object naming conventions, and API design
- Added object naming conventions (PO/DAO/BO/DTO/VO) in 02-layering.md to clarify terminology and usage within the team. - Updated 06-api-design.md to include MapStruct for DTO and entity conversion, providing examples and configuration details. - Expanded 07-config-governance.md with local development instructions and strategies for running without K8s, including two recommended approaches. - Included K8s probe configuration details in 08-observability.md for liveness and readiness checks. - Clarified CI/CD processes in 09-build-deploy.md, detailing environment distinctions and deployment strategies for local, Dev, UAT, and Prod. - Introduced ArchUnit for architectural testing in 10-testing.md, ensuring adherence to defined layering rules and coverage verification with Jacoco.
This commit is contained in:
@@ -101,6 +101,62 @@ class WorkbenchProperties {
|
||||
}
|
||||
```
|
||||
|
||||
## 本地开发怎么跑(不需要真的连 K8s)
|
||||
|
||||
`spring-cloud-starter-kubernetes-client-config` 启动时默认会尝试读 `~/.kube/config` 或 in-cluster 凭证去调 K8s API 拿 ConfigMap,本地电脑上没有这些东西的话,启动会报错或者卡住去连一个不存在的 API Server。本地开发不需要也不应该依赖真实 K8s,两种可选方案:
|
||||
|
||||
**方案一(推荐,日常开发默认用这个):本地 profile 直接关掉 Spring Cloud Kubernetes**
|
||||
|
||||
```yaml
|
||||
# application-local.yml
|
||||
spring:
|
||||
cloud:
|
||||
kubernetes:
|
||||
config:
|
||||
enabled: false # 本地不连 K8s API,配置全部走本地文件
|
||||
reload:
|
||||
enabled: false
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/conti_backend
|
||||
username: conti
|
||||
password: conti_local_password # 仅本地开发用,不是真实密钥
|
||||
security:
|
||||
jwt:
|
||||
secret: local-dev-only-secret-not-for-real-use
|
||||
```
|
||||
|
||||
```bash
|
||||
# 本地起依赖(DB 等),配合 docker-compose 用
|
||||
docker compose up -d postgres
|
||||
|
||||
# 用 local profile 启动
|
||||
SPRING_PROFILES_ACTIVE=local ./gradlew :bootstrap:bootRun
|
||||
```
|
||||
|
||||
`application-local.yml` 不提交敏感真实值(本来也没有,本地密码本身就是假的),可以放进代码库方便新人直接跑起来;`local` profile 和 dev/uat/prod 的关键区别就是 `spring.cloud.kubernetes.config.enabled=false`,其余代码逻辑完全一致——这也是为什么 [06-api-design.md](./06-api-design.md) 强调的"配置外置"很重要:业务代码不知道、也不需要知道配置到底来自 K8s 还是本地文件。
|
||||
|
||||
这是**个人本机调试**用的,跟团队共享的 **Dev 环境**不是一回事——Dev 环境跑在公司内网一台 Ubuntu 服务器的 k3s 集群上,走真实的 K8s ConfigMap/Secret(跟下面"方案二"是同一套思路,只是长期跑着给团队用,而不是临时验证),团队通过公司 VPN 访问,具体见 [09-build-deploy.md](./09-build-deploy.md#环境层级local个人本机vs-dev内网-ubuntu-k3s-集群vs-uatprodazure-aks)。
|
||||
|
||||
**方案二(需要验证 ConfigMap 热更新等 K8s 特有行为时才用):本地起一个真实的小集群**
|
||||
|
||||
用 Docker Desktop 自带的 Kubernetes、[Kind](https://kind.sigs.k8s.io/)(Kubernetes in Docker)或 [Minikube](https://minikube.sigs.k8s.io/) 在本机跑一个单节点集群,把上面"ConfigMap / Secret 示例"里的 yaml 应用到本地集群,验证 `@RefreshScope` 热更新、`ServiceAccount` 权限这些真正依赖 K8s API 的行为:
|
||||
|
||||
```bash
|
||||
kind create cluster --name conti-local
|
||||
kubectl config use-context kind-conti-local
|
||||
|
||||
kubectl create namespace retailapp-local
|
||||
kubectl apply -f k8s/configmap-local.yaml -n retailapp-local
|
||||
kubectl apply -f k8s/secret-local.yaml -n retailapp-local
|
||||
|
||||
# 应用本身也可以跑成本地集群里的 Pod(用本地构建的镜像),
|
||||
# 或者在宿主机直接跑 jar、用 KUBECONFIG 指向 kind 集群验证配置读取
|
||||
export KUBECONFIG=~/.kube/config
|
||||
SPRING_PROFILES_ACTIVE=dev ./gradlew :bootstrap:bootRun
|
||||
```
|
||||
|
||||
日常业务开发用方案一就够了,只有专门验证"配置中心相关能力本身"(比如调试 `spring.cloud.kubernetes.reload` 轮询逻辑)才需要方案二。
|
||||
|
||||
## 关键规则
|
||||
|
||||
- 配置变更优先走 ConfigMap 热更新(`@RefreshScope` + `spring.cloud.kubernetes.reload`),不重新构建镜像;涉及 Secret 轮换的走正常发布流程(Secret 变化通常需要重启 Pod 才能生效,不像 ConfigMap 可以做到无重启热更)。
|
||||
@@ -116,11 +172,70 @@ class WorkbenchProperties {
|
||||
|
||||
所以规则很简单:**这个值如果出现在日志里、被同事在 `kubectl get configmap -o yaml` 时看到会不会造成安全问题**——会,就放 Secret;不会,就放 ConfigMap。DB 密码、JWT 签名密钥、第三方 API key 毫无疑问要放 Secret;而"首页降级提示文案"这种放哪都无所谓的东西放 ConfigMap 就行,还能享受到热更新不用走发布流程的好处。
|
||||
|
||||
## Azure 上 Secret 的真正来源:Key Vault(不是手写 K8s Secret)
|
||||
|
||||
前面 `k8s/secret-uat.yaml` 示例里 `stringData` 写的是占位符(`__injected_by_pipeline__`),这一节说清楚这个占位符具体是怎么被替换成真实值的。
|
||||
|
||||
根据现有部署架构(见 [Architecture-Diagram/deployment-architecture-diagram.drawio](../Architecture-Diagram/deployment-architecture-diagram.drawio)),我们的 AKS 是 **Private AKS Cluster**,Key Vault 也是通过 **Private Endpoint**(`privatelink.vaultcore.azure.net`)访问的——也就是说真实密钥长期存在 Azure Key Vault 里,代码库、镜像、Git 历史里都不出现明文。落地到 K8s Secret 有两种方式,我们现在用的是方式一(跟 CI/GitLab 侧的配置习惯一致,不需要额外在 AKS 上装东西)。
|
||||
|
||||
**方式一(现用):CI/CD 流水线在部署前从 Key Vault 读值,渲染成 K8s Secret**
|
||||
|
||||
```bash
|
||||
# GitLab CI job 里(Runner 需要有权限访问 Key Vault,见 09-build-deploy.md)
|
||||
JWT_SECRET=$(az keyvault secret show --vault-name conti-backend-kv --name security-jwt-secret --query value -o tsv)
|
||||
kubectl create secret generic conti-backend-secret \
|
||||
--namespace retailapp-uat \
|
||||
--from-literal=SECURITY_JWT_SECRET="$JWT_SECRET" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
- 密钥值只在 CI job 的执行过程中短暂出现(不会写进 CI 日志、不会落盘到镜像里),`kubectl apply` 之后就是一个普通的 K8s Secret,`Deployment` 照常用 `envFrom.secretRef` 引用(见前面 `k8s/deployment-uat.yaml` 示例)。
|
||||
- 好处是**不需要在 AKS 上额外装插件、不需要给节点/Pod 配置 Managed Identity 绑定**,全部配置集中在 GitLab(CI 变量里存 Runner 访问 Key Vault 所需的 Service Principal/Managed Identity,业务代码和 K8s yaml 完全不感知 Key Vault 的存在),跟本地开发用 `application-local.yml` 手工填值、只是"谁来填值"变成了流水线,心智负担更小。
|
||||
- 权衡:Key Vault 里密钥更新后,**不会自动同步**到已经跑着的 Secret,需要重新跑一次部署(或专门加一个"仅同步 Secret,不发版本"的 job)才能生效——这点上不如方式二自动。日常密钥轮换频率不高的情况下这个权衡是划算的。
|
||||
|
||||
**方式二(可选的未来增强):CSI Secret Store Driver,运行时直接挂载,K8s Secret 不落地明文**
|
||||
|
||||
<details>
|
||||
<summary>展开查看(AKS 需装 `azure-keyvault-secrets-provider` 插件 + 配置 Managed Identity,运维成本更高,暂不采用)</summary>
|
||||
|
||||
```yaml
|
||||
# k8s/secretproviderclass-uat.yaml
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: conti-backend-kv-uat
|
||||
namespace: retailapp-uat
|
||||
spec:
|
||||
provider: azure
|
||||
parameters:
|
||||
usePodIdentity: "false"
|
||||
useVMManagedIdentity: "true" # 用 AKS 节点/Pod 的 Managed Identity 免密访问 Key Vault
|
||||
userAssignedIdentityID: "<managed-identity-client-id>"
|
||||
keyvaultName: "conti-backend-kv"
|
||||
tenantId: "<azure-tenant-id>"
|
||||
objects: |
|
||||
array:
|
||||
- |
|
||||
objectName: security-jwt-secret
|
||||
objectType: secret
|
||||
secretObjects: # 顺便同步成一个 K8s Secret,供 envFrom 引用
|
||||
- secretName: conti-backend-secret
|
||||
type: Opaque
|
||||
data:
|
||||
- objectName: security-jwt-secret
|
||||
key: SECURITY_JWT_SECRET
|
||||
```
|
||||
|
||||
优点是密钥更新后 CSI driver 会定期轮询自动同步、Pod 用 Managed Identity 直连 Key Vault 不经过 CI;代价是要在 AKS 上启用插件、每个环境配置对应的 `SecretProviderClass` 和身份绑定,运维配置面更大。如果以后密钥轮换频率变高、或者审计要求"密钥不能经过 CI 执行上下文",再切换到这条路径,现阶段先用方式一。
|
||||
|
||||
</details>
|
||||
|
||||
两种方式**不需要同时维护**——选一个用,文档里保留方式二只是留个参考路径,不是说两者要并存。
|
||||
|
||||
## 待补充
|
||||
|
||||
- 具体 ConfigMap/Secret 命名规范和 namespace 划分细节。
|
||||
- 多环境 profile 的详细参数列表。
|
||||
- 是否需要接入 Azure Key Vault 做 Secret 的进一步加固。
|
||||
|
||||
## 参考链接
|
||||
|
||||
|
||||
Reference in New Issue
Block a user