CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / JHIPSTER / P-234 · JHipster 项目开发

JHipster 开发 19:容器化与 Kubernetes

第 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 直推仓库。这里补容器化视角的两个要点:

  1. 基础镜像:jib 默认 distroless 式极简基础层(可配置),没有 shell、没有包管理器——体积小且攻击面小,代价是排障不能 kubectl exec ... sh,要用 kubectl debug 附加临时容器;
  2. 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.ymlDeployment应用实例声明(镜像、探针、资源)
service.ymlService集群内稳定访问端点
ingress.ymlIngress对外路由与 TLS 终结
hpa.ymlHorizontalPodAutoscalerCPU/内存驱动的自动扩缩容
application-configmap.ymlConfigMapSPRING_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.ymlSPRING_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 的三个提醒:

  1. registry 先于业务服务就绪(Deployment 依赖用 initContainer 探活或干脆分批部署);
  2. 每个服务独立 HPA,扩容阈值按各自负载画像调,不要复制粘贴;
  3. 跨服务调用走 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 前向兼容为前提。

系列导航

← 软件工程 019:Google Java 编程风格指南 目录 开发工具 019:Linux:ab压力测试 →
← 返回文章列表