7.4.2 确保可弃置性:优雅关闭

仅有应用程序快速启动不足以满足我们的可扩展性需求。每当一个应用程序实例被关闭时,都必须优雅地进行,而不会让客户端经历停机或错误。优雅关闭意味着应用程序停止接受新请求,完成所有仍在进行中的请求,并关闭所有打开的资源,如数据库连接。

Spring Boot 中所有可用的内嵌服务器都支持优雅关闭模式,但方式略有不同。Tomcat、Jetty 和 Netty 在收到关闭信号时完全停止接受新请求。另一方面,Undertow 继续接受新请求,但立即回复 HTTP 503 响应。

默认情况下,Spring Boot 在收到终止信号(SIGTERM)后立即停止服务器。您可以通过配置 server.shutdown 属性切换到优雅模式。您还可以配置宽限期,即应用程序可以花多长时间处理所有待处理的请求。宽限期到期后,即使仍有待处理的请求,应用程序也会被终止。默认情况下,宽限期为 30 秒。您可以通过 spring.lifecycle.timeout-per-shutdown-phase 属性更改它。

让我们为 Catalog Service 配置优雅关闭。我们可以通过环境变量或将其设置为默认配置来实现。我们将选择第二种方式。打开位于 catalog-service/src/main/resources 文件夹中的 application.yml 文件,按如下方式更新配置:

server:
 port: 9001
 shutdown: graceful # 启用优雅关闭
 tomcat:
 connection-timeout: 2s
 keep-alive-timeout: 15s
 threads:
 max: 50
 min-spare: 5
spring:
 application:
 name: catalog-service
 lifecycle:
 timeout-per-shutdown-phase: 15s # 定义 15 秒宽限期

由于我们修改了应用程序源代码,我们需要构建新的容器镜像并将其加载到 minikube 中。这不是很高效,对吧?在本章后面,我将向您展示一种更好的方法。目前,请按照我之前描述的过程将 Catalog Service 打包为容器镜像(./gradlew bootBuildImage),并将其加载到我们用于 Polar Bookshop 的 Kubernetes 集群中(minikube image load catalog-service --profile polar)。

在为应用程序启用优雅关闭支持后,您需要相应地更新 Deployment 清单。当 Pod 必须被终止时(例如在缩减过程中或作为升级的一部分),Kubernetes 会向其发送 SIGTERM 信号。Spring Boot 将拦截该信号并开始优雅关闭。默认情况下,Kubernetes 等待 30 秒的宽限期。如果 Pod 在该期限后未终止,Kubernetes 发送 SIGKILL 信号以强制 Pod 终止。由于 Spring Boot 的宽限期低于 Kubernetes 的,应用程序控制着何时终止。

当 Kubernetes 向 Pod 发送 SIGTERM 信号时,它还会通知其自身组件停止将请求转发到正在终止的 Pod。由于 Kubernetes 是一个分布式系统,且两个操作并行发生,存在一个短暂的时间窗口,正在终止的 Pod 可能仍然收到请求,即使它已经开始了优雅关闭过程。当这种情况发生时,这些新请求将被拒绝,导致客户端出现错误。我们的目标是使关闭过程对客户端透明,因此该场景是不可接受的。

推荐的解决方案是延迟向 Pod 发送 SIGTERM 信号,以便 Kubernetes 有足够的时间在集群中传播消息。这样,当 Pod 开始优雅关闭过程时,所有 Kubernetes 组件都已经知道不要向其发送新请求。在技术上,可以通过 preStop 钩子来配置延迟。让我们看看如何更新 Catalog Service 的 Deployment 清单以支持透明和优雅的关闭。

打开 catalog-service/k8s 中的 deployment.yml 文件,添加 preStop 钩子以将 SIGTERM 信号延迟 5 秒。

# 代码清单 7.4 在 Kubernetes 中配置关闭开始前的延迟

apiVersion: apps/v1
kind: Deployment
metadata:
 name: catalog-service
 labels:
 app: catalog-service
spec:
 ...
 template:
 metadata:
 labels:
 app: catalog-service
 spec:
 containers:
 - name: catalog-service
 image: catalog-service
 imagePullPolicy: IfNotPresent
 lifecycle:
 preStop: # 使 Kubernetes 在发送 SIGTERM 信号前等待 5 秒
 exec:
 command: ["sh", "-c", "sleep 5"]
 ...

最后,使用 kubectl apply -f k8s/deployment.yml 应用更新后的 Deployment 对象。Kubernetes 将协调新的期望状态,并用一个完全配置了优雅关闭的新 Pod 替换现有的 Pod。

注意:当 Pod 包含多个容器时,SIGTERM 信号会并行发送给所有容器。Kubernetes 最多等待 30 秒。如果 Pod 中的任何容器尚未终止,它将强制关闭它们。

现在我们已经为 Catalog Service 配置了优雅关闭行为,让我们看看如何在 Kubernetes 集群中扩展它。

results matching ""

    No results matching ""