详解Kubernetes Pod优雅退出

1、概述

Pod优雅关闭是指在Kubernetes中,当Pod因为某种原因(如版本更新、资源不足、故障等)需要被终止时,Kubernetes不会立即强制关闭Pod,而是首先尝试以一种“优雅”的方式关闭Pod。这个过程允许Pod中的容器有足够的时间来响应终止信号(默认为SIGTERM),并在终止前完成必要的清理工作,如保存数据、关闭连接等。

注意 :在《
Docker容器优雅退出
》这篇博文中,我们详细讲解了Docker优雅退出机制,在本文我们将详细详解Kubernetes Pod优雅退出机制。

1.1 Pod优雅退出流程

具体来说,Pod优雅关闭的流程如下:

(1)PreStop Hook:

    • 在Pod的定义中,可以配置一个PreStop Hook。这是一个在容器接收到SIGTERM信号之前执行的命令或HTTP请求。
    • PreStop Hook允许容器在接收到SIGTERM信号前,有一段缓冲时间来执行清理工作,如关闭数据库连接、保存文件、通知其他系统等。

(2)SIGTERM信号:

    • 在PreStop Hook执行完毕后或未定义PreStop Hook的情况下,kubelet 会遍历 Pod 中 container, 然后调用 cri 接口中 StopContainer 方法对 Pod 中的所有 container 进行优雅关停,向 dockerd 发送 stop -t 指令,用 SIGTERM 信号以通知容器内应用进程开始优雅停止。
    • 等待容器内应用进程完全停止,如果容器在 gracePeriod 执行时间内还未完全停止,就发送 SIGKILL 信号强制杀死应用进程(容器运行时处理)。

(3)SIGKILL信号与资源清理:如果容器在宽限期后仍在运行,容器运行时会发送 SIGKILL 信号强制终止容器,并随后清理 Pod 的资源(容器运行时处理)。

注意:Kubelet 调用 cri 接口中 StopContainer 方法时,向 dockerd 发送 stop -t 指令时会带着优雅关停容器的宽限时间 gracePeriod,gracePeriod 取值分多个情况,默认是 terminationGracePeriodSeconds[30秒] – 容器执行 preStop 时间,具体详情见下文源码分析部分。


1.2 为什么要进行Pod优雅关闭

进行Pod优雅关闭的重要性主要体现在以下几个方面:


  1. 避免服务中断

    :通过优雅关闭,Pod可以在终止前完成当前正在处理的请求,确保服务不会因为Pod的突然终止而中断。


  2. 确保数据一致性

    :优雅关闭允许Pod在终止前通过PreStop Hook完成必要的数据持久化或事务处理,从而确保数据的一致性。


  3. 最小化用户体验影响

    :通过优雅关闭,可以避免将流量路由到已经被删除的Pod,减少用户请求处理失败的可能性。在滚动更新或扩展Pod时,优雅关闭能够确保服务的平滑过渡,对用户来说几乎是无感知的。


  4. 合理利用资源

    :优雅关闭允许Pod在终止前释放占用的资源,避免资源浪费和泄露,提高资源的利用率。

总的来说,Pod优雅关闭是Kubernetes中一个重要的功能,它结合了PreStop Hook和宽限期等机制,确保Pod在终止前能够优雅地完成必要的清理工作,从而保持服务的稳定性和可用性、确保数据一致性、提升用户体验和合理利用资源。在进行Pod管理时,应该充分了解和利用Pod优雅关闭的功能。

2、Kubernetes Pod删除原理

Kubernetes (k8s) 中的 Pod 可能因多种原因被删除。以下是一些常见原因:


  1. 手动删除

    :用户使用 kubectl delete pod 命令手动删除 Pod。


  2. 控制器策略

    :Deployment、ReplicaSet 或 DaemonSet 等控制器根据其策略调整副本数,例如缩减副本数时会删除多余的 Pod;Job 和 CronJob 完成后删除其创建的 Pod。


  3. 节点故障

    :如果节点失效,节点上的 Pod 会被 Kubernetes 控制平面标记为失效并在其他节点上重新调度。


  4. 资源限制

    :当节点资源不足时,Kubernetes 可能会根据优先级和资源限制(如资源配额和调度策略)来删除一些 Pod。


  5. 健康检查失败

    :Pod 的 liveness 或 readiness 探针连续失败,Kubernetes 会认为 Pod 不健康并删除或重启它。


  6. 优先级抢占

    :如果有更高优先级的 Pod 需要资源,Kubernetes 可能会删除较低优先级的 Pod 以释放资源。


  7. 调度器策略

    :Kubernetes 调度器可能会根据调度策略(如 NodeAffinity、PodAffinity 等)重新分配 Pod,从而删除旧的 Pod。


  8. 更新策略

    :Deployment 或 StatefulSet 进行滚动更新时,旧的 Pod 会被删除并替换为新的 Pod。


  9. 节点自动缩放

    :当使用集群自动缩放器时,如果集群缩小(移除节点),部分 Pod 会被删除。

但是不管是何种原因删除Pod(用户手动删除或控制器自动删除),在Pod的删除过程中,


都会同时会存在两条并行的时间线


,如下图所示:



  1. 一条时间线是网络规则的更新过程。



  2. 另一条时间线是 Pod 的删除过程。

由上面流程图可知,在 Pod 删除过程中,存在两条并行的时间线,


这两条时间线谁先执行完毕是不确定的


。如果 Pod 内的容器已经删除,但网络层面的 Endpoint 资源仍包含该 Pod 的 IP,客户端请求可能会被路由到已删除的 Pod,导致请求处理失败;或者请求未处理完时,Pod 内的容器已经被删除,这样也会导致请求处理失败。以下是一个工作负载滚动升级的示例,说明如果不为 Pod 配置合理的优雅退出机制,会出现什么问题。



工作负载滚动升级问题示例


  1. 请求路由错误

    :旧 Pod 删除但仍在 Endpoint 资源中,导致请求被路由到已删除的 Pod,返回以下错误:


    • 502 Bad Gateway

      :负载均衡器或反向代理无法正确路由请求。

  2. 数据丢失或不一致

    :旧 Pod 未将正在处理的请求处理完成的情况下被删除,如果该请求不是幂等性的,则可能导致以下错误:


    • 500 Internal Server Error

      :服务器内部错误,无法完成请求。

    • 404 Not Found

      :如果数据未正确保存或更新,可能找不到预期的资源。

注意 1:本文假设删除Pod都有关联的svc资源,客户端都是通过svc访问Pod。

注意 2:HTTP 404错误通常表示服务器无法找到请求的资源。这可能是因为资源已被删除、移动或从未存在过。在数据丢失或不一致的场景中,404错误可能是一个间接的结果。例如,如果Pod在删除之前正在处理一个应该创建新资源的请求(如数据库记录或文件),但由于Pod的删除,该资源可能没有被正确创建。稍后的请求试图访问该资源时,可能会收到404错误,因为资源不存在。

2.1 原理分析

一切都从 TerminationGracePeriodSeconds 开始说起,我们回顾下 k8s 关闭 Pod 的流程过程。

注意:信号详解可以参加《
Docker容器优雅退出
》这篇博文。

了解信号的解释以后,再通过代码讲解下 Kubelet 关闭 Pod 流程(包含 preStop 和 GracefulStop):


Kubernetes 源码(1.21.5版本):

pkg/kubelet/types/pod_update.go:

pkg/kubelet/kubelet.go:

如果是删除Pod事件(SyncPodKill)将执行删除Pod逻辑(killPod)。注意看Kubelet调用删除Pod逻辑方法传了一个参数PodTerminationGracePeriodSecondsOverride,它是 Kubelet 的一个配置参数,用于覆盖所有 Pod 的终止宽限时间(grace period)。具体来说,这个参数会设置一个全局的宽限时间值,该值会覆盖所有 Pod 自定义的 terminationGracePeriodSeconds 值。

  • 默认情况下,PodTerminationGracePeriodSecondsOverride 是未设置的(即值为 nil 或未定义)。在这种情况下,Kubelet 会使用每个 Pod 自己定义的 terminationGracePeriodSeconds 值,默认值为 30 秒。
  • 如果设置了这个参数,Kubelet 会使用此值作为所有 Pod 的终止宽限时间,而不再使用各个 Pod 自定义的 terminationGracePeriodSeconds。这意味着所有 Pod 都会在这个指定的时间内尝试完成终止操作,在时间结束后,Kubelet 会强制终止 Pod。

pkg/kubelet/kubelet_pods.go:

pkg/kubelet/kuberuntime/kuberuntime_manager.go:

pkg/kubelet/kuberuntime/kuberuntime_container.go:

使用协程清理Pod里面所有的容器。

pkg/kubelet/kuberuntime/kuberuntime_container.go:



Kubelet 进行 Pod 中容器的关停,这个方法比较关键,这里重点讲解下:

(1)计算容器优雅关闭宽限时间

  1. 默认设置容器最小优雅关停宽限时间等于2秒。

  2. 如果 podDeletionGracePeriodSeconds 不是 nil

    ,即 Pod 是被 Apiserver 删除的,那么 gracePeriod 直接取值,优先使用调用 Apiserver 删除Pod时指定的值作为优雅关闭Pod宽限时间,比如kubectl delete pod my-pod –grace-period=60。
  3. 如果 pod Spec.TerminationGracePeriodSeconds 不是 nil,gracePeriod 取值分为以下三种情况:
  • 使用Pod规格配置文件中的定义的terminationGracePeriodSeconds的值,gracePeriod 默认值30秒;
  • 如果删除的原因是执行失败 startupProbe,gracePeriod 取启动探针TerminationGracePeriodSeconds值(启用探针宽限时间特性);
  • 如果删除的原因是执行失败 livenessProbe,gracePeriod 取存活探针TerminationGracePeriodSeconds值(启用探针宽限时间特性);

(2)如果容器配置了 lifecycle preStop ,执行 container 中 lifecycle preStop 设置的动作或命令,并计算容器执行 lifecycle preStop 的时间。

(3)容器宽限时间 gracePeriod = gracePeriod – 容器执行 lifecycle preStop 的时间。

(4)如果容器执行完 lifecycle preStop 后的宽限时间 < minimumGracePeriodInSeconds(2秒)的话,gracePeriod = minimumGracePeriodInSeconds。

(5)如果kubelet全局配置不为空,所有容器退出宽限时间使用kubelet PodTerminationGracePeriodSecondsOverride配置参数值。

(6)调用 CRI 接口,调用容器云运行时 /container/{containerID}/stop 接口用于关停容器,容器优雅停止的 gracePeriod 值,为上面计算的 gracePeriod。

注意:这里只粘贴和Pod优雅退出相关代码,其他代码直接忽视了。


容器运行时Docker源码:

moby/daemon/stop.go:

未经允许不得转载:大白鲨游戏网 » 详解Kubernetes Pod优雅退出