Java 部署滚动更新的方法(K8s RollingUpdate 策略)

在现代云原生应用开发中,持续交付和零停机部署已成为企业级 Java 应用的标配。而 Kubernetes(简称 K8s)作为事实上的容器编排平台,其 RollingUpdate(滚动更新) 策略为实现平滑、安全、无感知的应用升级提供了强大支持。本文将深入探讨如何在 Kubernetes 中对 Java 应用实施滚动更新,涵盖原理、配置、最佳实践、故障处理以及完整的代码与部署示例。
无论你是刚接触 K8s 的 Java 开发者,还是已有一定经验但希望优化部署流程的 DevOps 工程师,本文都将为你提供实用、可落地的指导。让我们从基础开始,逐步构建一个健壮、可维护的滚动更新体系 🚀。
什么是滚动更新(Rolling Update)?
滚动更新是一种渐进式部署新版本应用的策略。它通过逐个替换旧 Pod的方式,确保在整个更新过程中服务始终可用,从而实现零停机(Zero Downtime) 的目标。
在 Kubernetes 中,当你更新一个 Deployment 的镜像版本或配置时,如果策略设置为 RollingUpdate(默认值),K8s 会自动执行以下操作:
- 启动一个或多个新版本的 Pod;
- 等待新 Pod 就绪(通过就绪探针 Ready Probe 判断);
- 将流量从旧 Pod 逐步切换到新 Pod;
- 删除一个或多个旧 Pod;
- 重复上述过程,直到所有旧 Pod 被替换。
这种“边下边上”的方式,避免了传统“蓝绿部署”或“全量替换”可能带来的服务中断风险。
💡 小知识:Kubernetes 的滚动更新是基于 ReplicaSet 实现的。每次更新 Deployment 时,K8s 会创建一个新的 ReplicaSet,并逐步缩容旧 ReplicaSet、扩容新 ReplicaSet。
为什么 Java 应用特别需要滚动更新?
Java 应用通常具有以下特点,使其对滚动更新有更高需求:
- 启动时间较长:JVM 预热、Spring Boot 初始化等过程可能需要数秒甚至数十秒;
- 内存占用高:频繁重启可能导致资源竞争;
- 有状态中间件依赖:如数据库连接池、缓存客户端等,需优雅关闭;
- 高可用要求:企业级系统通常要求 99.9% 以上的可用性。
若采用一次性删除所有旧实例再启动新实例的方式,用户将经历明显的请求失败或超时。而滚动更新通过控制并发替换数量,确保始终有足够健康的实例处理请求,极大提升了用户体验和系统稳定性 ✅。
Kubernetes 滚动更新的核心机制
Kubernetes 的滚动更新由两个关键参数控制:
maxUnavailable:更新过程中允许不可用的 Pod 最大数量(相对于期望副本数);maxSurge:更新过程中允许超出期望副本数的最大 Pod 数量。
这两个参数共同决定了更新的速度与安全性。
默认值
对于一个 replicas: 3 的 Deployment,其默认滚动更新策略如下:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%这意味着:
- 最多允许 1 个 Pod 不可用(3 × 25% ≈ 0.75 → 向上取整为 1);
- 最多允许临时增加 1 个 Pod(3 × 25% ≈ 0.75 → 向上取整为 1)。
因此,更新过程可能如下:
- 创建 1 个新 Pod(总数变为 4);
- 等待新 Pod 就绪;
- 删除 1 个旧 Pod(总数回到 3);
- 重复直到全部替换。
参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
maxUnavailable | int 或百分比 | 更新期间可处于“未就绪”状态的 Pod 数量上限。设为 0 可实现完全无损更新(但需配合 maxSurge > 0)。 |
maxSurge | int 或百分比 | 允许超过 replicas 设定值的额外 Pod 数量。用于提前启动新实例,加快更新速度。 |
⚠️ 注意:
maxUnavailable和maxSurge不能同时为 0,否则更新将无法进行。
构建一个支持滚动更新的 Java 应用
为了演示滚动更新,我们先构建一个简单的 Spring Boot 应用,包含健康检查端点。
1. 创建 Spring Boot 项目
使用 Spring Initializr 创建一个 Web 项目,添加 Spring Web 和 Spring Boot Actuator 依赖。
2. 编写主类
// src/main/java/com/example/rollingupdate/DemoApplication.java
package com.example.rollingupdate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}3. 添加控制器
// src/main/java/com/example/rollingupdate/HelloController.java
package com.example.rollingupdate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello from Java App v1! 🌟";
}
// 模拟版本信息,便于观察更新效果
@GetMapping("/version")
public String version() {
return "v1.0.0";
}
}4. 配置 Actuator 健康端点
在 application.yml 中启用健康检查端点:
# src/main/resources/application.yml
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: alwaysActuator 默认提供 /actuator/health 端点,返回 {"status":"UP"} 表示应用健康。
5. 构建 Docker 镜像
编写 Dockerfile:
# Dockerfile FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
构建并打标签(假设你的 Docker Hub 用户名为 yourname):
./mvnw clean package -DskipTests docker build -t yourname/java-rolling-demo:v1 . docker push yourname/java-rolling-demo:v1
🔗 你可以参考 Docker 官方文档 了解如何构建镜像。
编写 Kubernetes Deployment 配置
接下来,我们将这个 Java 应用部署到 Kubernetes,并配置滚动更新策略。
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-rolling-demo
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: java-rolling-demo
template:
metadata:
labels:
app: java-rolling-demo
spec:
containers:
- name: app
image: yourname/java-rolling-demo:v1
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: java-rolling-demo-svc
spec:
selector:
app: java-rolling-demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP关键配置说明
readinessProbe:决定 Pod 是否准备好接收流量。只有当探针成功,Service 才会将请求转发给该 Pod。这对滚动更新至关重要——新 Pod 必须完全就绪后才能加入服务。livenessProbe:用于判断应用是否存活。若失败,K8s 会重启容器。resources:限制资源使用,避免单个 Pod 占用过多资源影响其他实例。maxUnavailable: 1:最多允许 1 个 Pod 不可用(3 个副本时,至少 2 个在线)。maxSurge: 1:允许临时多启动 1 个 Pod,加快更新速度。
📌 重要:
readinessProbe是滚动更新平滑性的核心!务必确保其路径能真实反映应用就绪状态。
部署并验证初始状态
应用上述配置:
kubectl apply -f deployment.yaml
检查 Pod 状态:
kubectl get pods -l app=java-rolling-demo
输出应类似:
NAME READY STATUS RESTARTS AGE java-rolling-demo-7d5b8c9f45-abc12 1/1 Running 0 2m java-rolling-demo-7d5b8c9f45-def34 1/1 Running 0 2m java-rolling-demo-7d5b8c9f45-ghi56 1/1 Running 0 2m
测试服务:
# 获取 Service IP(或使用 NodePort/Ingress) kubectl get svc java-rolling-demo-svc # 使用 curl 测试(假设已暴露到外部) curl http://<SERVICE-IP>/version # 返回: v1.0.0
执行滚动更新:从 v1 到 v2
现在,我们准备发布新版本 v2。
1. 修改 Java 应用代码
更新 HelloController.java:
@GetMapping("/version")
public String version() {
return "v2.0.0"; // 改为 v2
}并修改欢迎语:
@GetMapping("/hello")
public String hello() {
return "Hello from Java App v2! 🚀";
}2. 构建并推送 v2 镜像
./mvnw clean package -DskipTests docker build -t yourname/java-rolling-demo:v2 . docker push yourname/java-rolling-demo:v2
3. 触发滚动更新
有两种方式:
方式一:直接修改 Deployment YAML
将 image 字段改为 yourname/java-rolling-demo:v2,然后执行:
kubectl apply -f deployment.yaml
方式二:使用kubectl set image(推荐)
kubectl set image deployment/java-rolling-demo app=yourname/java-rolling-demo:v2
✅ 这种方式无需修改文件,适合 CI/CD 自动化。
4. 监控更新过程
查看滚动更新状态:
kubectl rollout status deployment/java-rolling-demo
输出示例:
Waiting for deployment "java-rolling-demo" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "java-rolling-demo" rollout to finish: 2 out of 3 new replicas have been updated... deployment "java-rolling-demo" successfully rolled out
同时,观察 Pod 变化:
kubectl get pods -w -l app=java-rolling-demo
你将看到旧 Pod 逐步被终止,新 Pod 逐步启动:
NAME READY STATUS RESTARTS AGE java-rolling-demo-7d5b8c9f45-abc12 1/1 Running 0 5m java-rolling-demo-7d5b8c9f45-def34 1/1 Running 0 5m java-rolling-demo-7d5b8c9f45-ghi56 1/1 Running 0 5m java-rolling-demo-6b8d9f7c54-jkl78 0/1 ContainerCreating 0 1s java-rolling-demo-6b8d9f7c54-jkl78 1/1 Running 0 10s java-rolling-demo-7d5b8c9f45-abc12 1/1 Terminating 0 5m30s ...
5. 验证新版本
多次调用 /version 接口:
for i in {1..10}; do curl -s http://<SERVICE-IP>/version; echo; done初期可能返回 v1.0.0 和 v2.0.0 混合结果,随着更新完成,最终全部返回 v2.0.0。
这正是滚动更新的典型特征:流量逐步切换,用户无感知。
可视化滚动更新过程
下面是一个 Mermaid 图表,展示滚动更新中 Pod 状态的变化:
💡 图中显示:新 Pod 在旧 Pod 终止前已启动并就绪,确保服务连续性。
滚动更新中的关键保障:探针(Probes)
前面提到,readinessProbe 是滚动更新平滑的关键。让我们深入理解其作用。
Readiness Probe(就绪探针)
- 作用:告诉 K8s “我准备好接收流量了吗?”
- 触发时机:Pod 启动后周期性调用。
- 行为:若失败,Pod 不会被加入 Service 的 Endpoints,即不会收到任何请求。
- Java 应用建议:
- 初始延迟(
initialDelaySeconds)应大于应用启动时间(如 Spring Boot 通常 10-30 秒); - 路径应返回真实业务就绪状态(如数据库连接成功、缓存初始化完成等)。
- 初始延迟(
Liveness Probe(存活探针)
- 作用:告诉 K8s “我还活着吗?”
- 行为:若连续失败,K8s 会重启容器。
- 注意:不要将复杂逻辑放入 liveness probe,否则可能导致误杀。
示例:增强健康检查
你可以自定义健康指示器,确保只有在所有依赖就绪后才返回 UP:
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// 检查数据库连接
if (isDatabaseConnected()) {
return Health.up().withDetail("database", "available").build();
}
return Health.down().withDetail("database", "unavailable").build();
}
}这样,即使 Spring Boot 启动完成,若数据库未连通,/actuator/health 仍返回 DOWN,Pod 不会接收流量,避免错误请求。
🔗 更多关于 Spring Boot 健康检查的信息,可参考 Spring Boot Actuator 文档。
处理滚动更新失败:回滚(Rollback)
尽管我们做了充分准备,但更新仍可能失败(如新版本有 bug、配置错误等)。Kubernetes 提供了便捷的回滚机制。
查看更新历史
kubectl rollout history deployment/java-rolling-demo
输出:
REVISION CHANGE-CAUSE 1 <none> 2 kubectl set image deployment/java-rolling-demo app=yourname/java-rolling-demo:v2
回滚到上一版本
kubectl rollout undo deployment/java-rolling-demo
K8s 会自动将镜像切回 v1,并执行反向滚动更新。
回滚到指定版本
kubectl rollout undo deployment/java-rolling-demo --to-revision=1
自动回滚(高级)
Kubernetes 本身不支持“自动回滚”,但可通过以下方式实现:
- 结合监控告警:如 Prometheus + Alertmanager 检测到错误率飙升,触发 webhook 执行
kubectl rollout undo; - 使用 Argo Rollouts:这是一个 K8s 扩展,支持金丝雀发布、自动回滚等高级功能。
🔗 Argo Rollouts 官网 提供了更强大的渐进式交付能力。
优化滚动更新体验的最佳实践
1. 设置合理的探针参数
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 20 # 根据应用启动时间调整
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 32. 控制更新速度
- 若系统敏感,可设置
maxUnavailable: 0和maxSurge: 1,确保始终有全部副本在线; - 若追求速度,可增大
maxSurge(如 50%),但需注意资源压力。
3. 使用 preStop Hook 实现优雅关闭
在 Java 应用终止前,执行清理操作(如关闭连接池、拒绝新请求):
containers:
- name: app
image: yourname/java-rolling-demo:v2
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]同时,在 Spring Boot 中启用优雅关闭:
# application.yml
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s这样,当 Pod 被删除时:
- K8s 先从 Service Endpoints 中移除该 Pod(不再接收新请求);
- 执行
preStophook(等待 15 秒,让现有请求完成); - 发送 SIGTERM 信号给 Java 进程;
- Spring Boot 优雅关闭;
- 若超时未退出,发送 SIGKILL。
🌐 更多关于优雅关闭的内容,可参考 Kubernetes 官方文档 - Termination of Pods。
4. 监控滚动更新指标
通过 Prometheus 监控以下指标:
kube_deployment_status_replicas_updated:已更新的副本数;kube_pod_status_ready:Pod 就绪状态;- 应用层指标:错误率、延迟、吞吐量。
一旦异常,立即告警或自动回滚。
滚动更新 vs 其他部署策略
Kubernetes 还支持其他部署策略,适用于不同场景:
| 策略 | 特点 | 适用场景 |
|---|---|---|
| RollingUpdate | 逐步替换,零停机 | 大多数生产环境 |
| Recreate | 先删所有旧 Pod,再建新 Pod | 可接受短暂停机,或有状态应用需完全重置 |
| Blue/Green | K8s 原生不支持,需 Service 切换 | 需要快速回滚、全量验证 |
| Canary | K8s 原生不支持,需 Ingress 或 Service Mesh | 渐进式发布,小流量验证 |
💡 对于 Java 应用,RollingUpdate 是最常用且最安全的选择。
常见问题与排查
Q1: 更新卡住了,怎么办?
- 检查新 Pod 是否就绪:
kubectl describe pod <new-pod> - 查看日志:
kubectl logs <new-pod> - 检查 readinessProbe 是否失败;
- 确认镜像是否拉取成功(网络、权限问题)。
Q2: 用户偶尔收到 502 错误?
- 可能是旧 Pod 被终止时仍有请求在处理;
- 解决方案:配置
preStophook + 优雅关闭; - 确保 Service 的
sessionAffinity未开启(除非必要)。
Q3: 如何暂停/恢复滚动更新?
# 暂停 kubectl rollout pause deployment/java-rolling-demo # 恢复 kubectl rollout resume deployment/java-rolling-demo
适用于调试或分阶段更新。
总结
滚动更新是 Kubernetes 为 Java 应用提供的强大部署能力,它通过渐进式替换 Pod,结合就绪探针和优雅关闭机制,实现了零停机、高可用的持续交付。
要成功实施滚动更新,你需要:
- 构建健康的 Java 应用:提供准确的
/actuator/health端点; - 合理配置 Deployment:设置
maxUnavailable、maxSurge、探针参数; - 实现优雅关闭:使用
preStophook 和 Spring Boot 的 graceful shutdown; - 监控与回滚:建立可观测性,准备快速回滚方案。
随着云原生生态的成熟,滚动更新已成为 Java 应用上线的标准姿势。掌握它,你就能在微服务时代游刃有余地交付高质量软件 🎯。
🌟 最后提醒:永远在非生产环境充分测试你的滚动更新流程!自动化、标准化、可重复,是 DevOps 的核心精神。
到此这篇关于Java 部署滚动更新的方法(K8s RollingUpdate 策略)的文章就介绍到这了,更多相关Java 滚动更新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
关于maven打包时的报错: Return code is: 501 , ReasonPhrase:HTTPS Requ
这篇文章主要介绍了关于maven打包时的报错: Return code is: 501 , ReasonPhrase:HTTPS Required,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-09-09
详解SpringBoot中@SessionAttributes的使用
这篇文章主要通过示例为大家详细介绍了SpringBoot中@SessionAttributes的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下2022-07-07
Spring MVC之WebApplicationContext_动力节点Java学院整理
这篇文章主要介绍了Spring MVC之WebApplicationContext的相关资料,需要的朋友可以参考下2017-08-08


最新评论