第 18 篇的流水线终点是”部署”,这一篇补上部署的地基:容器与 Kubernetes。路线是渐进的——先让应用在 docker-compose 里跑顺,再上 K8s。JHipster 对这两步都有现成产物:src/main/docker/ 的 compose 文件和 jhipster kubernetes 子生成器的完整 manifest。我们团队的经验是:K8s 不是终点,理解每一份生成的 YAML 为什么这么写才是——集群出事时没人有闲情读生成器文档。
先在 docker-compose 里跑顺
JHipster 在 src/main/docker/ 下生成整套本地依赖编排:
src/main/docker/
├── app.yml // 应用本身 + 依赖的数据库(可选 kafka/redis/elasticsearch)
├── postgresql.yml / mysql.yml ...
├── kafka.yml
├── jhipster-control-center.yml / registry.yml // 微服务场景
├── prometheus.yml / grafana.yml / zipkin.yml // 可观测性(第 20 篇)
└── keycloak.yml // OIDC 场景的 IdP
# 一条命令起完整环境(数据库 + 应用)
docker compose -f src/main/docker/app.yml up -d
# 只起中间件,应用在 IDE 里跑(日常开发主流姿势)
docker compose -f src/main/docker/postgresql.yml up -d
本地 compose 环境的价值不只是”能跑”:它验证容器化后的健康检查、环境变量注入、依赖顺序这些部署语义。应用在 compose 里都跑不起来就上 K8s,等于在两个未知变量上叠加调试。
镜像:jib 直接产 OCI
第 18 篇已讲过 jib:无 Dockerfile、分层缓存、jib:build 直推仓库。这里补容器化视角的两个要点:
- 基础镜像:jib 默认 distroless 式极简基础层(可配置),没有 shell、没有包管理器——体积小且攻击面小,代价是排障不能
kubectl exec ... sh,要用kubectl debug附加临时容器; - JVM 参数容器感知:Java 21 对容器内存限制感知成熟,配合
-XX:MaxRAMPercentage=75.0让堆按 limit 自适应,比写死-Xmx更适合 K8s(limit 调整时无需改镜像)。
./mvnw -Pprod verify jib:build \
-Djib.to.image=registry.myorg.com/myapp:v1.4.0
子生成器产物解读
jhipster kubernetes
# 问答:namespace、镜像仓库与 tag 策略、是否 Ingress、是否 Helm、是否自动扩缩容...
产物(k8s/ 目录)与职责:
| 文件 | K8s 对象 | 作用 |
|---|---|---|
deployment.yml | Deployment | 应用实例声明(镜像、探针、资源) |
service.yml | Service | 集群内稳定访问端点 |
ingress.yml | Ingress | 对外路由与 TLS 终结 |
hpa.yml | HorizontalPodAutoscaler | CPU/内存驱动的自动扩缩容 |
application-configmap.yml | ConfigMap | SPRING_APPLICATION_JSON 配置注入 |
| secrets 相关 | Secret | 数据库密码等凭证占位 |
Deployment:生成器最核心的一份
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp-namespace
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp-app
image: registry.myorg.com/myapp:v1.4.0
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
- name: SPRING_DATASOURCE_URL
value: jdbc:postgresql://myapp-postgresql:5432/myapp
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /management/health/readiness
port: http
initialDelaySeconds: 20
periodSeconds: 10
livenessProbe:
httpGet:
path: /management/health/liveness
port: http
initialDelaySeconds: 40
periodSeconds: 20
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "1536Mi"
探针:readiness 与 liveness 语义必须分清
| 探针 | 失败后果 | 回答的问题 | 接错的症状 |
|---|---|---|---|
| readiness | 摘出 Service 端点,不再接流量 | “能服务了吗” | 流量打到没就绪的实例,5xx 飙升 |
| liveness | 重启容器 | “还活着吗” | 误杀慢启动应用 → 重启风暴 |
我们的调参经验:
initialDelaySeconds按 JVM 冷启动实测加余量(Spring Boot 4 + CDS/AOT 之后我们 2Gi 内存内启动约 25 秒,liveness 延迟给到 40);- liveness 不检查依赖:数据库抖一下就重启全部实例只会放大故障(这是 CKAD 考题级别的经典错误,生产里我们真的见过);
- readiness 可以包含数据库,依赖恢复后自动回到负载均衡;
- startup probe 处理慢启动应用比拉大 liveness 延迟更干净,生成器新模板已采纳。
配置注入:ConfigMap 与 Secret
生成的 application-configmap.yml 用 SPRING_APPLICATION_JSON 环境变量整体注入配置:
env:
- name: SPRING_APPLICATION_JSON
valueFrom:
configMapKeyRef:
name: myapp-configmap
key: SPRING_APPLICATION_JSON
Secret 同构,只是 secretKeyRef。要点:
- 配置改 ConfigMap、凭证改 Secret,永远不进镜像(第 18 篇铁律的 K8s 面);
- ConfigMap 更新不自动重启 Pod,配合 rollout(
kubectl rollout restart deployment/myapp)显式生效——不要指望”改了配置怎么没反应”自己想通; - 生产建议 sealed-secrets/SOPS 管理加密后的 Secret 清单,与 ConfigMap 一起进 git,环境配置可审计。
Helm 选项与微服务拓扑
问答里选 Helm 会生成 charts/ 目录:模板化的上述资源 + values.yaml 分环境覆盖。多服务团队值得开——单体项目手写 kustomize overlay 也够用,别为单体上 Helm 全家桶。
微服务全家(第 5 篇的 gateway + registry + 若干业务服务)在 K8s 上的拓扑:
flowchart TB
I["Ingress\nTLS 终结"] --> G["gateway Deployment\n路由/限流/鉴权"]
G --> R["JHipster Registry\n服务发现 + 配置中心"]
G --> S1["order-service x2"]
G --> S2["customer-service x3"]
S1 --> DB1[("order-db")]
S2 --> DB2[("customer-db")]
R -.注册/拉配置.-> S1
R -.注册/拉配置.-> S2
K["Kafka"] -.- S1
K -.- S2
微服务上 K8s 的三个提醒:
- registry 先于业务服务就绪(Deployment 依赖用 initContainer 探活或干脆分批部署);
- 每个服务独立 HPA,扩容阈值按各自负载画像调,不要复制粘贴;
- 跨服务调用走 Service DNS,不要把实例 Pod IP 写进任何配置。
滚动更新与回滚
Deployment 默认滚动策略已经能Cover大多数发布:
strategy:
rollingUpdate:
maxSurge: 1 // 最多多起 1 个新实例
maxUnavailable: 0 // 更新期间不允许少实例
maxUnavailable: 0 + maxSurge: 1 是”零停机”的组合:先增后减,全程容量不减,配合 readiness 探门禁流量。
kubectl -n myapp set image deployment/myapp myapp-app=registry.myorg.com/myapp:v1.4.1
kubectl -n myapp rollout status deployment/myapp # 观察推进
kubectl -n myapp rollout history deployment/myapp # 看版本链
kubectl -n myapp rollout undo deployment/myapp --to-revision=2 # 回滚
回滚的边界第 18 篇已讲:镜像是双向可切的,数据库 schema 只向前走——所以两段式变更纪律是 K8s 回滚能真正救命的前提。HPA 环境下还有一个小坑:回滚的 revision 记录的 replicas 可能是扩缩后的瞬时值,直接 undo 副本数会漂,检查一下再收工。
小结
- 渐进路线:compose 跑顺部署语义,再上 K8s,避免双未知叠加调试;
- jib 无 Dockerfile 出 OCI 镜像,
MaxRAMPercentage让 JVM 随 limit 自适应; - 生成的 Deployment 五要素齐全:探针(readiness/lives 分工 + liveness 不查依赖)、资源 requests/limits、配置 ConfigMap、凭证 Secret、滚动策略;
- 微服务拓扑记住三条:registry 先行、按服务独立 HPA、Service DNS 互访;
maxUnavailable: 0滚动零停机,回滚靠rollout undo但以 schema 前向兼容为前提。
系列导航
- 上一篇:CI/CD——从零到自动发布
- 下一篇:可观测性——日志、指标、链路
- 相关阅读:微服务 vs 单体——怎么选 · 配置与 Profile 管理 · 生产环境检查清单