Skip to content

深入理解 Kubernetes 优雅停止机制


🏷 后端   🕒 4/4/2025

在现代云原生应用部署中,Kubernetes(K8S)已成为容器编排的事实标准。随着微服务架构的广泛采用,如何保证服务平滑更新、无损下线变得尤为重要。本文将详细探讨 K8S 中的优雅停止(Graceful Shutdown)机制,这是确保应用可靠性和用户体验的关键环节。

优雅停止的意义

优雅停止是指在容器终止前,给予应用程序一定的时间来完成当前处理中的请求、释放资源并正常退出的机制。这看似简单的过程实际上涉及多层面的协同工作,是保障服务质量的重要环节。

在没有优雅停止机制的情况下,当 Pod 被删除时,容器会被突然终止,导致正在处理的请求失败、资源未释放,甚至数据损坏。这些问题在生产环境中可能引发级联失败,影响整个系统的稳定性。

优雅停止的核心思想是给予应用足够的"缓冲时间"来完成手头工作,就像一位专业的乐手不会在演奏中途突然停止,而是会找到一个合适的节点来结束表演。

工作原理

Kubernetes 的优雅停止机制主要依赖于信号处理系统和提前通知机制。当 Pod 被标记为删除时,K8S 会遵循以下流程:

首先,Kubernetes 会将 Pod 状态更新为"Terminating",并从 Service 的 Endpoints 中移除该 Pod,确保新的流量不再路由到它。这一步骤非常关键,它确保了在 Pod 停止处理请求前,负载均衡器已停止向其转发新请求。

接着,K8S 会向容器发送 SIGTERM 信号,通知应用程序即将终止。此时,应用程序应当捕获该信号并开始清理工作:完成当前请求处理、关闭数据库连接、保存状态等。这个阶段被称为"宽限期"(terminationGracePeriodSeconds),默认为 30 秒。

如果在宽限期结束后应用仍未退出,Kubernetes 将发送 SIGKILL 信号强制终止容器,这是不可捕获、不可忽略的"最后通牒"。

K8S 中优雅停止的详细过程

让我们更深入地探究优雅停止的具体流程:

  1. 触发终止:当用户执行 kubectl delete pod 或更新 Deployment 触发滚动更新时,优雅停止过程开始。

  2. 服务摘流:Pod 被标记为 "Terminating" 并从 Service 的 Endpoints 列表中移除。然而,需要注意的是,这个过程并非即时的,存在一定的延迟。

  3. PreStop 钩子:如果定义了 PreStop 生命周期钩子,Kubernetes 会在发送 SIGTERM 前执行此钩子。这为应用提供了额外的准备时间,例如,可以通过 HTTP 调用通知应用准备停止。

  4. SIGTERM 信号处理:给 Pod 发送 SIGTERM 信号,应用接收 SIGTERM 信号后,应开始有序关闭。这包括完成当前请求、关闭监听端口、释放资源等。

  5. 宽限期倒计时:Kubernetes 会等待 terminationGracePeriodSeconds 指定的时间(默认 30 秒)。

  6. 强制终止:如果超出宽限期应用仍未退出,Kubernetes 将发送 SIGKILL 信号强制终止容器。

HTTP 短连接与 gRPC 长连接的优雅停止差异

在讨论优雅停止时,不同通信协议的特性会显著影响实现策略。HTTP 短连接和 gRPC 长连接在处理优雅停止时有本质区别:

HTTP 短连接的优雅停止相对简单。当服务接收到 SIGTERM 信号时,它可以停止接受新请求,同时完成已经接收的请求。由于每个请求都是独立的短连接,处理完当前批次请求后服务就可以安全退出。实现方式通常是关闭监听端口,然后等待活跃请求完成。

gRPC 长连接的情况更为复杂。gRPC 基于 HTTP/2,通常维持长时间的连接,多个请求可能共享同一连接。当服务需要优雅停止时,既要停止接受新连接,也要妥善处理现有连接上的请求,同时通知客户端连接即将关闭。gRPC 提供了专门的机制如 GracefulStop() 方法,在停止服务器时会完成所有未完成的 RPC 调用,并拒绝新的调用。

长连接服务的优雅停止通常需要额外的策略,如连接耗尽(drain connections)或主动通知客户端重连到其他实例(通过发送 GoAway 帧)。这也是为什么 gRPC 服务通常需要更长的优雅停止时间。

NodePort 与 Ingress 在流量管理中的角色

在 Kubernetes 中,优雅停止与流量管理紧密相关。NodePort 和 Ingress 是两种不同的流量入口机制,它们在优雅停止过程中表现不同:

NodePort 是一种相对简单的服务暴露方式,它在集群所有节点上开放特定端口,将流量直接转发到后端 Pod。当 Pod 进入终止状态时,Kubernetes 会将其从 Service 的端点列表中移除,这样 NodePort 就不会再向其转发流量。但这个过程存在延迟,特别是在大型集群中,端点更新可能需要几秒钟才能完全生效。

Ingress 则提供了更高级的流量路由能力,通常由 Ingress 控制器(如 Nginx、Traefik)实现。Ingress 控制器本身就是运行在 Kubernetes 中的 Pod,可以实现更智能的负载均衡,包括基于 HTTP 头的路由、TLS 终止等。在优雅停止场景下,Ingress 控制器可以更快地感知到 Pod 状态变化,许多控制器还提供了自定义的健康检查和流量管理策略,比如根据 Pod 的就绪状态动态调整流量分配。

两者的主要区别在于:NodePort 是基于 TCP/IP 层的简单转发,而 Ingress 可以理解应用层协议(如 HTTP),提供更精细的流量控制。在优雅停止过程中,Ingress 通常能提供更快的流量切换和更灵活的配置选项。

常见的优雅停止问题及解决方案

尽管 Kubernetes 提供了优雅停止的框架,实际应用中仍存在一些常见问题:

1. 摘流延迟

服务从 Endpoints 列表移除到实际不再接收流量之间存在延迟,导致 Pod 在开始停止过程后仍可能收到新请求。

解决方案:实现 PreStop 钩子,添加额外的等待时间,给 Kubernetes 和负载均衡器足够时间更新配置。例如:

yaml
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 10"]

2. 应用未正确处理 SIGTERM

许多应用未正确实现 SIGTERM 信号处理,导致无法优雅停止。一般的 Server 框架都会提供诸如 GracefulStop 的方法来处理当前未完成的请求,然后再关闭程序。如果直接关闭可能导致请求无法响应(比如常见的 HTTP 的 499 状态)。

3. 长连接处理不当

对于长连接服务,除了 EndPoints 摘流,已经建立的链接也应该主动通知客户端连接即将关闭。比如 gRPC 的 GracefulStop 会给客户端发送 GoAway 帧通知,客户端在收到 GoAway 时停止继续往当前连接发送新情求,并及时建立新连接。

因此长连接的优雅停止一般需要 Server 和 Client 互相配合实现。

Nginx 在 1.21.1 之前有 Bug 无法正确处理 gRPC GoAway,会导致一直给即将停止的连接发送新消息,导致后端关闭时 499/502.

参考文档