Java 部署:滚动更新(K8s RollingUpdate 策略)

· 运维技术教程

现在,Kubernetes(大家常叫它 K8s)已经成了部署和管理容器化程序最常用的方式。

对于用 Java 写的服务来说,怎么在用户还能正常访问的情况下完成版本升级,是开发和运维都很在意的事情,而 Kubernetes 里的 滚动更新(RollingUpdate) 功能正好能帮上忙。

滚动更新的基本概念

滚动更新就是系统一边启动新版本的 Pod,一边慢慢关掉旧的,而不是一下子全换掉;这样做可以在整个升级过程中始终保持一部分实例在线处理请求,避免服务中断。这种方式特别适合那些不能停机的 Java 后端服务,比如基于 Spring Boot 开发的应用——用户几乎感觉不到后台正在更新。

更新流程是怎么工作的?

当你把一个 Deployment 的镜像从旧版本(比如 my-java-app:v1)改成新版本(比如 my-java-app:v2),只要它的更新方式是默认的 RollingUpdate,K8s 就会自动开始替换:它先根据新配置创建一个或几个新 Pod,等这些新 Pod 通过就绪检查、真正能处理请求之后,再删掉相同数量的旧 Pod,然后重复这个过程,直到所有旧实例都被替换成新的。

这个过程快还是慢,主要看两个设置:maxUnavailablemaxSurge

这两个值一起决定了更新的速度、资源消耗和服务稳定性之间的平衡。

针对 Java 应用的优化建议

Java 程序启动通常比较花时间,尤其是大型的 Spring Boot 项目,可能要十几秒甚至更久才能完全准备好;如果探针或者资源配置没调好,很容易导致更新失败,或者让服务短暂变卡。

1. 正确设置就绪探针(readinessProbe)

一定要配好 readinessProbe,这样 Kubernetes 才知道什么时候新 Pod 真正能用了。例如:

readinessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 20
  periodSeconds: 10

这里的 initialDelaySeconds 要留足时间,让它能覆盖 JVM 启动加上 Spring 上下文初始化所需的时间,不然还没准备好就被加进流量里,容易出错。

2. 别让旧实例被太快干掉

Kubernetes 在删除 Pod 前会先发一个关闭信号,默认等 30 秒(也就是 terminationGracePeriodSeconds 的默认值)再强制结束进程。对于 Java 应用,最好开启优雅停机功能(比如在 Spring Boot 里加上 server.shutdown=graceful),同时在 Deployment 里把等待时间调长一点,比如设成 60 秒:

terminationGracePeriodSeconds: 60

还可以加上 preStop 钩子,再多留点缓冲时间:

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 15"]

这样可以让正在处理的请求跑完,再安全退出,避免连接突然断开。

3. 控制一次更新多少个实例

如果你的应用对数据库连接、外部接口或者缓存依赖比较敏感,可以把 maxSurge 设成 0,maxUnavailable 设成 1,这样每次只更新一个 Pod,对下游系统的压力最小,也更容易排查问题。

实际配置例子

假设你有一个叫 java-web-app 的服务,当前运行着 4 个副本,下面是一个兼顾稳定性和效率的完整配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-web-app
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    spec:
      containers:
      - name: app
        image: my-registry/java-web-app:v2
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 25
          periodSeconds: 10
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 15"]
      terminationGracePeriodSeconds: 45

这个配置的意思是:更新时最多同时关掉 1 个旧实例,最多额外启动 1 个新实例;新 Pod 启动后要等 25 秒才开始接收流量;在真正终止前还会 sleep 15 秒做清理,整体留给它退出的时间是 45 秒。

回滚也很简单

滚动更新还有一个好处:万一新版本有问题,可以快速退回到上一个正常版本。只需要运行这一条命令就行:

kubectl rollout undo deployment/java-web-app

K8s 会用同样的逐步替换方式把应用恢复回去,整个过程不会影响用户访问。

总结

滚动更新是 Kubernetes 支持不停机发布的核心能力。对写 Java 的人来说,搞清楚它是怎么运行的,并根据自己程序的特点调整探针、终止时间和更新节奏,就能实现平滑、可靠的上线。记住:部署不只是让代码跑起来,更重要的是让系统在变化中一直稳稳地工作。

希望这篇文章能帮你更轻松地在 Kubernetes 上管理 Java 应用。