引言
在云原生时代,保证服务的稳定性和高可用性至关重要。本文将深入探讨如何结合 Kubernetes (K8s) 与 Spring Boot,实现零宕机发布。我们将覆盖以下核心主题:
- 健康检查:确保流量只被路由到健康的实例。
- 滚动更新:平滑地升级应用,不中断服务。
- 优雅停机:安全地关闭应用,避免数据丢失或请求失败。
- 弹性伸缩:根据负载自动调整实例数量。
- Prometheus 监控:收集关键指标,洞察应用性能。
- 配置分离:实现镜像复用,提高交付效率。
参考资料:
1. 健康检查
健康检查是实现零宕机发布的基础。K8s 通过就绪探针 (Readiness Probe) 和存活探针 (Liveness Probe) 来判断应用实例是否准备好接收流量或是否需要重启。
- 就绪探针 (Readiness): 告诉 K8s 应用是否准备好处理请求。如果失败,K8s 会将该 Pod 从 Service 的端点列表中移除。
- 存活探针 (Liveness): 判断应用是否仍在运行。如果失败,K8s 会重启该 Pod。
探针类型主要有三种:exec(执行脚本)、tcpSocket(检查端口)和 httpGet(发起 HTTP 请求)。
1.1. 应用层配置 (Spring Boot)
首先,在 pom.xml 中添加 spring-boot-starter-actuator 依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
然后,在 application.yaml 中配置 Actuator,暴露健康检查端点:
1 2 3 4 5 6 7 8 9 10 11
| management: server: port: 50000 endpoint: health: probes: enabled: true endpoints: web: exposure: include: health
|
配置完成后,Actuator 会暴露以下端点:
/actuator/health/readiness: 就绪状态检查/actuator/health/liveness: 存活状态检查
1.2. 平台层配置 (Kubernetes)
在 deployment.yaml 中配置探针,指向 Actuator 提供的端点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: {APP_NAME} ports: - containerPort: {APP_PORT} - name: management-port containerPort: 50000 readinessProbe: httpGet: path: /actuator/health/readiness port: management-port initialDelaySeconds: 90 periodSeconds: 30 timeoutSeconds: 10 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health/liveness port: management-port initialDelaySeconds: 90 periodSeconds: 30 timeoutSeconds: 10 successThreshold: 1 failureThreshold: 3
|
2. 滚动更新
滚动更新 (Rolling Update) 是 K8s 的默认部署策略,它通过逐个替换旧 Pod 来实现平滑升级。结合健康检查,可以确保在整个更新过程中服务不中断。
1 2 3 4 5 6 7 8 9
| apiVersion: apps/v1 kind: Deployment spec: replicas: {REPLICAS} strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0
|
maxSurge: 保证了在更新时有额外的 Pod 来处理流量。maxUnavailable: 设为 0 确保了在任何时候都有足够数量的 Pod 在运行。
3. 优雅停机
优雅停机的核心目标是:不再接收新流量、完成在途任务、有序释放资源。
3.1. 核心原则
- 停止流量: 首先让 Pod 的就绪探针失败,K8s 会自动将其从流量入口移除。
- 处理在途: 应用内部等待正在处理的任务(如 HTTP 请求、消息消费)完成。
- 释放资源: 按顺序关闭线程池、数据库连接等资源。
- 设置超时: 所有等待过程都应有超时机制,防止无限期阻塞。
3.2. 应用层配置 (Spring Boot)
HTTP 请求
Spring Boot 2.3+ 默认支持优雅停机。在 application.yaml 中配置:
1 2 3 4 5 6
| server: shutdown: graceful
spring: lifecycle: timeout-per-shutdown-phase: 30s
|
@Async 和 @Scheduled 任务
对于异步和定时任务,可以配置等待其完成:
1 2 3 4 5 6 7 8 9 10
| spring: task: execution: shutdown: await-termination: true await-termination-period: 30s scheduling: shutdown: await-termination: true await-termination-period: 30s
|
消息队列 (Kafka)
对于消息消费,关键是手动确认 (ack),确保消息被完全处理后再提交位移。
1 2 3 4 5 6 7 8
| spring: cloud: stream: kafka: bindings: testConsumer-in-0: consumer: ack-mode: manual
|
在消费逻辑中,处理完成后再调用 ack.acknowledge()。
3.3. 平台层配置 (Kubernetes)
K8s 在删除 Pod 时,会先发送 SIGTERM 信号,并等待 terminationGracePeriodSeconds 定义的时间。我们可以利用 preStop 钩子来主动触发应用的优雅停机逻辑。
1 2 3 4 5 6 7 8
| spec: terminationGracePeriodSeconds: 45 containers: - name: {APP_NAME} lifecycle: preStop: exec: command: ["curl", "-XPOST", "http://localhost:50000/actuator/shutdown"]
|
推荐的停机顺序:
- K8s 发送
SIGTERM 信号。 preStop 钩子执行,调用 /actuator/shutdown。- Spring Boot 开始优雅停机,新请求被拒绝。
- 等待在途的 HTTP 请求、异步任务和消息处理完成。
- 应用进程退出。
4. 弹性伸缩
通过水平 Pod 自动伸缩器 (HPA),K8s 可以根据 CPU 或内存使用率自动调整 Pod 数量。
首先,为容器设置资源请求和限制:
1 2 3 4 5 6 7 8 9 10 11 12
| spec: template: spec: containers: - name: {APP_NAME} resources: requests: cpu: "200m" memory: "512Mi" limits: cpu: "1000m" memory: "2Gi"
|
然后,创建 HPA 资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {APP_NAME} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: {APP_NAME} minReplicas: {REPLICAS} maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 80
|
5. Prometheus 集成
为了监控应用性能,我们可以集成 Prometheus。
5.1. 应用层配置
添加 micrometer-registry-prometheus 依赖:
1 2 3 4
| <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>
|
暴露 prometheus 端点:
1 2 3 4 5
| management: endpoints: web: exposure: include: health, prometheus
|
5.2. 平台层配置
在 Deployment 中添加注解,让 Prometheus 能够自动发现并抓取指标:
1 2 3 4 5 6 7
| spec: template: metadata: annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "50000"
|
6. 配置分离
为了实现镜像复用和配置的灵活管理,我们使用 ConfigMap 来外挂配置文件。
创建 ConfigMap:
1
| kubectl create configmap {APP_NAME}-config --from-file=application-prod.yaml
|
挂载 ConfigMap 到 Pod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| spec: template: spec: containers: - name: {APP_NAME} env: - name: SPRING_PROFILES_ACTIVE value: "prod" volumeMounts: - name: config-volume mountPath: /app/config volumes: - name: config-volume configMap: name: {APP_NAME}-config
|
7. 汇总配置示例
pom.xml
1 2 3 4 5 6 7 8 9 10
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> </dependencies>
|
application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| spring: application: name: my-awesome-app lifecycle: timeout-per-shutdown-phase: 30s task:
server: port: 8080 shutdown: graceful
management: server: port: 50000 endpoints: web: exposure: include: health, prometheus, shutdown endpoint: health: probes: enabled: true
|
Dockerfile
1 2 3 4 5 6 7
| FROM openjdk:11-jre-slim ARG JAR_FILE=target/*.jar WORKDIR /app COPY ${JAR_FILE} app.jar RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* EXPOSE 8080 50000 ENTRYPOINT ["java", "-jar", "app.jar"]
|
deployment.yaml (部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| apiVersion: apps/v1 kind: Deployment metadata: name: {APP_NAME} spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "50000" spec: terminationGracePeriodSeconds: 45 containers: - name: {APP_NAME} image: {IMAGE_URL} ports: - containerPort: 8080 - name: management containerPort: 50000 lifecycle: preStop: exec: command: ["curl", "-XPOST", "http://localhost:50000/actuator/shutdown"]
|
常见问题
问题: 应用中有 while(true) 循环(例如在 CommandLineRunner 中),导致就绪探针永远返回 503 Service Unavailable。
原因: CommandLineRunner 在 Spring Boot 应用启动的主线程中执行。如果它进入一个无限循环,启动过程将永远不会完成,导致 Actuator 端点无法响应。
解决方案: 将耗时的或无限循环的任务放在一个单独的子线程中执行,避免阻塞主线程。
1 2 3 4 5 6 7 8 9 10 11
| @Component public class MyTaskRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { new Thread(() -> { while (true) { } }).start(); } }
|
通过遵循这些最佳实践,您可以构建一个健壮、高可用的 Spring Boot 应用,并充满信心地在 Kubernetes 上进行部署和运维。