Java应用在Kubernetes上的零停机部署
本文旨在提供一份全面的指南,帮助 Java 应用在 Kubernetes 上实现零停机部署。我们将涵盖部署策略、Kubernetes 原语、Java 相关注意事项、会话状态处理、数据库迁移、流量切换技术、CI/CD 管道、GitHub Actions、Jenkins 自动回滚、可观测性(Prometheus、Grafana、Jaeger)、Helm/ArgoCD 示例、测试策略(金丝雀分析、混沌测试、冒烟测试)以及故障排查。
部署策略
Kubernetes 提供了几种无停机部署新版本的策略:
滚动更新(Rolling Update)
逐步用新 Pod 替换旧 Pod,保持可用性。Kubernetes Deployment 对象默认使用滚动更新。你可以通过 maxUnavailable 和 maxSurge 参数来控制滚动过程。
蓝绿部署(Blue-Green Deployment)
运行两个独立的环境:蓝色(当前版本)和绿色(新版本)。每次只有一个环境服务于实时流量。待绿色版本验证通过后,将 Service 或 Ingress 切换指向绿色,然后缩容蓝色。这种模式允许通过将流量切回蓝色实现即时回滚。Argo Rollouts 定义了蓝绿策略,包含 active 和 preview 两个 Service,流量仅流向已晋升(promotion)的版本。
金丝雀部署(Canary Deployment)
逐步将小部分流量切换到新版本。先启动少量 v2 Pod,监控无误后逐步增加流量。可以使用 Istio 或 Argo Rollouts 等工具控制流量权重。例如,通过运行 9 个 v1 Pod 和 1 个 v2 Pod(10% 流量)将 10% 的流量导向 v2。Argo 定义了金丝雀 rollout,包含 setWeight 步骤和用于分析的暂停点。
影子部署/流量镜像(Shadow/Mirroring)
新版本接收实时请求的副本,用于在真实负载下测试,但其响应不会返回给用户。这种方法风险低,但由于用户看不到新行为,对回滚决策帮助不大。
零停机所需的 Kubernetes 原语
Deployment
Deployment 原生支持滚动更新。默认情况下,它会创建一个新的 ReplicaSet 并逐步扩容,同时按 maxUnavailable/maxSurge 控制缩容旧 ReplicaSet,确保始终有 Pod 在提供服务。若使用蓝绿部署,则需要部署两个独立的 Deployment(例如 app-blue、app-green)并切换 Service。
Service 和 Ingress
Service 作为 Pod 前端。对于蓝绿部署,可以将单个 Service 指向蓝色或绿色 Pod。Ingress 也可以在后端服务之间切换。例如,通过调整标签选择器将流量从蓝色版本 Pod 重定向到绿色版本 Pod。
PodDisruptionBudget (PDB)
保证在自愿中断期间(如滚动更新)有最少数量的 Pod 保持运行。例如,设置 minAvailable: 1 确保滚动更新期间至少有一个 Pod 存活,避免完全停机。
水平 Pod 自动缩放器(HPA)
根据 CPU/内存或自定义指标缩放 Pod,自动调整工作负载以满足需求。可以将 HPA 附加到 Deployment,这样在发布过程中如果流量激增,会自动创建新 Pod 来承载负载。示例:
存活探针(Liveness Probe)与就绪探针(Readiness Probe)
这是实现零停机的关键。存活探针检查应用是否存活,失败时 K8s 会重启 Pod。就绪探针告知应用是否已准备好接收流量。在启动或关闭过程中,就绪探针应失败,使 Pod 从 Service 负载均衡器中移除。Spring Boot Actuator 提供 /actuator/health 端点用于此目的。在 K8s YAML 中:
Spring Boot 默认暴露 health/liveness 和 health/readiness 分组。Quarkus 和 Micronaut 也提供类似的健康端点。
Spring Boot 支持优雅关闭:设置 server.shutdown=graceful 并调整 spring.lifecycle.timeout-per-shutdown-phase。这会使嵌入式服务器(Tomcat/Jetty/Undertow)停止接收新流量,并在超时时间内等待正在处理的请求完成。
Quarkus 提供优雅关闭配置。通过设置 quarkus.shutdown.timeout=10s,Quarkus 会等待最多 10 秒让当前请求完成后再退出。可以使用 @Shutdown 注解在 Bean 方法上执行清理代码。
Micronaut 通过 @EventListener 监听 ShutdownEvent:
Kubernetes Hooks(PreStop)
可以在 Deployment 的 spec 中使用 preStop hook,在发送 SIGTERM 之前执行脚本:
默认的终止宽限期(30 秒)应根据应用完成时间进行调整。K8s 文档描述了容器进入 Terminating、执行 preStop、发送 SIGTERM、等待 terminationGracePeriodSeconds、然后发送 SIGKILL 的序列。
JVM 调优
- 设置
-XX:+ExitOnOutOfMemoryError避免 JVM 挂起。 - 调整线程池使其快速排空。
- 监控 GC 停顿时间,考虑使用低延迟 GC 以最小化关闭前的停顿。
会话与状态处理
在 Pod 切换时保持零停机:
- 无状态服务:最佳实践是保持服务无状态。将会话状态或用户数据存储在外部存储中,如 Redis 或数据库。这样任何 Pod 都能处理任何请求,替换 Pod 不会丢失用户会话。
- 粘性会话:如果应用使用内存会话,可以强制使用粘性会话。
- Service 亲和性:在 Service 上设置
sessionAffinity: ClientIP,Kubernetes 会将来自同一客户端 IP 的请求路由到同一个 Pod。 - Ingress 亲和性:使用 Ingress 注解将用户请求绑定到同一个 Pod。但粘性会话存在风险,不适合自动缩放。
- StatefulSet:对于真正的有状态工作负载,使用 StatefulSet 并赋予稳定标识。StatefulSet 将 Pod 与 PersistentVolume 配对,但本身并不保证零停机。
GitHub Actions CI/CD 管道(零停机)
此工作流构建镜像、推送并更新 Deployment。rollout status 命令会等待所有新 Pod 就绪。如果健康检查失败,该命令将中止,而不会产生停机。
结论
在 Kubernetes 上实现零停机部署需要将细致的架构设计与自动化相结合,包括使用滚动更新、渐进式策略、确保 Java 应用的优雅关闭和健康检查、状态外化、数据库变更管理,以及通过 CI/CD 管道进行编排。Kubernetes 原语如 Deployment、Service、探针和 HPA,以及 Istio 或 Argo Rollouts 等工具,构成了实现的基础。
(DZone 贡献者表达的观点均为其个人观点。)