java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java 滚动更新

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

作者:Jinkxs

本文详细介绍了在Kubernetes中如何对Java应用实施滚动更新,涵盖原理、配置、最佳实践、故障处理以及完整的代码与部署示例,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

在现代云原生应用开发中,持续交付和零停机部署已成为企业级 Java 应用的标配。而 Kubernetes(简称 K8s)作为事实上的容器编排平台,其 RollingUpdate(滚动更新) 策略为实现平滑、安全、无感知的应用升级提供了强大支持。本文将深入探讨如何在 Kubernetes 中对 Java 应用实施滚动更新,涵盖原理、配置、最佳实践、故障处理以及完整的代码与部署示例。

无论你是刚接触 K8s 的 Java 开发者,还是已有一定经验但希望优化部署流程的 DevOps 工程师,本文都将为你提供实用、可落地的指导。让我们从基础开始,逐步构建一个健壮、可维护的滚动更新体系 🚀。

什么是滚动更新(Rolling Update)?

滚动更新是一种渐进式部署新版本应用的策略。它通过逐个替换旧 Pod的方式,确保在整个更新过程中服务始终可用,从而实现零停机(Zero Downtime) 的目标。

在 Kubernetes 中,当你更新一个 Deployment 的镜像版本或配置时,如果策略设置为 RollingUpdate(默认值),K8s 会自动执行以下操作:

  1. 启动一个或多个新版本的 Pod;
  2. 等待新 Pod 就绪(通过就绪探针 Ready Probe 判断);
  3. 将流量从旧 Pod 逐步切换到新 Pod;
  4. 删除一个或多个旧 Pod;
  5. 重复上述过程,直到所有旧 Pod 被替换。

这种“边下边上”的方式,避免了传统“蓝绿部署”或“全量替换”可能带来的服务中断风险。

💡 小知识:Kubernetes 的滚动更新是基于 ReplicaSet 实现的。每次更新 Deployment 时,K8s 会创建一个新的 ReplicaSet,并逐步缩容旧 ReplicaSet、扩容新 ReplicaSet。

为什么 Java 应用特别需要滚动更新?

Java 应用通常具有以下特点,使其对滚动更新有更高需求:

若采用一次性删除所有旧实例再启动新实例的方式,用户将经历明显的请求失败或超时。而滚动更新通过控制并发替换数量,确保始终有足够健康的实例处理请求,极大提升了用户体验和系统稳定性 ✅。

Kubernetes 滚动更新的核心机制

Kubernetes 的滚动更新由两个关键参数控制:

这两个参数共同决定了更新的速度与安全性。

默认值

对于一个 replicas: 3 的 Deployment,其默认滚动更新策略如下:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 25%
    maxSurge: 25%

这意味着:

因此,更新过程可能如下:

  1. 创建 1 个新 Pod(总数变为 4);
  2. 等待新 Pod 就绪;
  3. 删除 1 个旧 Pod(总数回到 3);
  4. 重复直到全部替换。

参数详解

参数类型说明
maxUnavailableint 或百分比更新期间可处于“未就绪”状态的 Pod 数量上限。设为 0 可实现完全无损更新(但需配合 maxSurge > 0)。
maxSurgeint 或百分比允许超过 replicas 设定值的额外 Pod 数量。用于提前启动新实例,加快更新速度。

⚠️ 注意:maxUnavailablemaxSurge 不能同时为 0,否则更新将无法进行。

构建一个支持滚动更新的 Java 应用

为了演示滚动更新,我们先构建一个简单的 Spring Boot 应用,包含健康检查端点。

1. 创建 Spring Boot 项目

使用 Spring Initializr 创建一个 Web 项目,添加 Spring WebSpring 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: always

Actuator 默认提供 /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 是滚动更新平滑性的核心!务必确保其路径能真实反映应用就绪状态。

部署并验证初始状态

应用上述配置:

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.0v2.0.0 混合结果,随着更新完成,最终全部返回 v2.0.0

这正是滚动更新的典型特征:流量逐步切换,用户无感知

可视化滚动更新过程

下面是一个 Mermaid 图表,展示滚动更新中 Pod 状态的变化:

00:00 00:00 00:00 00:00 00:01 00:01 00:01 00:01 00:02 00:02 00:02 Pod-A (v1) Pod-B (v1) Pod-C (v1) Pod-D (v2) Pod-E (v2) Pod-F (v2) 旧 Pod 新 Pod Kubernetes Rolling Update 过程(replicas=3, maxUnavailable=1, maxSurge=1)

💡 图中显示:新 Pod 在旧 Pod 终止前已启动并就绪,确保服务连续性。

滚动更新中的关键保障:探针(Probes)

前面提到,readinessProbe 是滚动更新平滑的关键。让我们深入理解其作用。

Readiness Probe(就绪探针)

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 本身不支持“自动回滚”,但可通过以下方式实现:

🔗 Argo Rollouts 官网 提供了更强大的渐进式交付能力。

优化滚动更新体验的最佳实践

1. 设置合理的探针参数

readinessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 20   # 根据应用启动时间调整
  periodSeconds: 5
  timeoutSeconds: 3
  successThreshold: 1
  failureThreshold: 3

2. 控制更新速度

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 被删除时:

  1. K8s 先从 Service Endpoints 中移除该 Pod(不再接收新请求);
  2. 执行 preStop hook(等待 15 秒,让现有请求完成);
  3. 发送 SIGTERM 信号给 Java 进程;
  4. Spring Boot 优雅关闭;
  5. 若超时未退出,发送 SIGKILL。

🌐 更多关于优雅关闭的内容,可参考 Kubernetes 官方文档 - Termination of Pods

4. 监控滚动更新指标

通过 Prometheus 监控以下指标:

一旦异常,立即告警或自动回滚。

滚动更新 vs 其他部署策略

Kubernetes 还支持其他部署策略,适用于不同场景:

策略特点适用场景
RollingUpdate逐步替换,零停机大多数生产环境
Recreate先删所有旧 Pod,再建新 Pod可接受短暂停机,或有状态应用需完全重置
Blue/GreenK8s 原生不支持,需 Service 切换需要快速回滚、全量验证
CanaryK8s 原生不支持,需 Ingress 或 Service Mesh渐进式发布,小流量验证

💡 对于 Java 应用,RollingUpdate 是最常用且最安全的选择

常见问题与排查

Q1: 更新卡住了,怎么办?

Q2: 用户偶尔收到 502 错误?

Q3: 如何暂停/恢复滚动更新?

# 暂停
kubectl rollout pause deployment/java-rolling-demo
# 恢复
kubectl rollout resume deployment/java-rolling-demo

适用于调试或分阶段更新。

总结

滚动更新是 Kubernetes 为 Java 应用提供的强大部署能力,它通过渐进式替换 Pod,结合就绪探针优雅关闭机制,实现了零停机、高可用的持续交付。

要成功实施滚动更新,你需要:

  1. 构建健康的 Java 应用:提供准确的 /actuator/health 端点;
  2. 合理配置 Deployment:设置 maxUnavailablemaxSurge、探针参数;
  3. 实现优雅关闭:使用 preStop hook 和 Spring Boot 的 graceful shutdown;
  4. 监控与回滚:建立可观测性,准备快速回滚方案。

随着云原生生态的成熟,滚动更新已成为 Java 应用上线的标准姿势。掌握它,你就能在微服务时代游刃有余地交付高质量软件 🎯。

🌟 最后提醒:永远在非生产环境充分测试你的滚动更新流程!自动化、标准化、可重复,是 DevOps 的核心精神。

到此这篇关于Java 部署滚动更新的方法(K8s RollingUpdate 策略)的文章就介绍到这了,更多相关Java 滚动更新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文