Kubernets pod 过快退出引起的无法平滑滚动重启的问题
平时在使用 kubernets 的时候我们会使用滚动更新策略,滚动更新确保了我们新的 pod 实例逐步替换旧的 pod 实例从而确保重启期间服务不会中断。
举个例子:
有一个 gateway 程序一直在接受客户端请求,这时我们需要升级 gateway 的程序。在更新代码之后我们需要对 gateway 程序进行重启。这个重启不能 block 对三方对我们的持续请求。 k8s 支持对 pod 进行滚动更新,在杀掉一个老版本 pod 之前会启动新的 pod,当新的 pod 能正常执行之后才会去杀掉老的 pod。
new pod start ⇒ kill old pod 这样保证永远有 pod 对业务进行持续服务。
那么现在我们遇到的问题是,当我们在配置了滚动更新策略,我们依然观察到了短时间的服务延迟或者消息丢失。
于是怀疑在滚动重启的时候,可能存在
- 新的节点还没起来老的节点就被杀掉。
- 新的节点起来,老的节点在被杀掉之前并没有先停止向老的节点发送请求。导致老的节点本已经停止服务的情况下依然收到了请求于是被丢弃。
调查了 k8s 的相关策略。
- od 的优雅终止: 当 Kubernetes 试图停止一个 Pod 时,它首先发送一个
TERM
信号到容器中的进程。这是给进程的提示,让它知道应该开始清理和结束它的工作。进程收到此信号后应该停止接受任何新请求,并尽可能完成已经开始的请求。 - 优雅终止的时限: 容器有一定的时间来响应
TERM
信号并优雅地关闭。这个时间被称为“优雅终止的时限”(terminationGracePeriodSeconds
),默认是 30 秒,但可以根据需要在 Pod 的配置中进行调整。 - 强制删除: 如果一个容器在优雅终止时限过后仍然运行,Kubernetes 会发送一个
KILL
信号强制结束它。 - 从 Service 中移除: 当一个 Pod 开始终止,它会立即从所有关联的 Service 的 Endpoints 列表中移除。这意味着该 Pod 不再接受任何新的流量。
- 开始新的 Pod: 在结束老的 Pod 之前或之后,Kubernetes 会开始新的 Pod(这取决于
maxSurge
和maxUnavailable
参数的配置)。一旦新的 Pod 的 readiness 探针成功返回,它就会被添加到 Service 的 Endpoints 列表中,开始接收流量。 - 等待就绪: Kubernetes 不会立即认为新启动的 Pod 是“就绪”的。Pod 的容器必须首先启动,并且任何配置的 readiness 探针也必须成功返回。只有满足这些条件,Pod 才会被认为是“就绪”的,能够从 Service 中接收流量。
这种逐渐摘掉旧 Pod 并引入新 Pod 的机制,结合 readiness 和 liveness 探针的检查,确保了在更新过程中,至少有一定数量的 Pod 保持运行并为用户提供服务。这样,即使在部署新版本时,也能够提供无缝的用户体验。
然后 Kubernetes 优雅终止的流程是这样的:
- 当 Kubernetes 决定终止一个 Pod 时,该 Pod 首先会从所有关联的 Service 的 Endpoints 列表中移除,这样它就不会再接收到新的流量。
- 几乎同时,Kubernetes 向 Pod 发送
TERM
信号,开始优雅终止过程。
这两个步骤(从 Service 中移除和发送 TERM
信号)确实是几乎同时发生的。然而,如果容器进程响应 TERM
信号的速度非常快,并立即退出,而在此之前还有请求在路上(尤其是在高延迟的网络环境中),那么这些请求可能会失败。
这就是问题可能发生的地方,我们可以通过设置 prestop 再加一点等待的时间。 如果我们 follow 下面的流程,应该就可以避免这种极端情况了。
- 从 Service 中移除: Pod 会首先从其关联的 Service 的 Endpoints 列表中移除。这确保新的请求不会被转发到即将终止的 Pod。
- 执行
preStop
钩子: 如果定义了preStop
钩子,它会被执行。这是一个机会让你在容器接收到TERM
信号之前执行任何需要的清理工作或其他任务。 - 发送
TERM
信号: 在preStop
钩子完成执行之后,Kubernetes 会向 Pod 中的每个容器发送TERM
信号,提示它们开始优雅地终止。 - 等待优雅终止: Kubernetes 会等待一段时间(由
terminationGracePeriodSeconds
定义)以便容器进行优雅的终止。如果容器在这段时间结束,流程到此结束。否则,进入下一步。 - 发送
KILL
信号: 如果容器在terminationGracePeriodSeconds
指定的时间内没有终止,Kubernetes 会发送KILL
信号来强制容器立即结束。
总结一下:preStop
钩子可以帮助你在容器收到 TERM
信号之前执行一些操作,但它并不会影响 Pod 从 Service 的 Endpoints 列表中被移除的时机。这确保了在 Pod 开始终止过程的时候,新的请求不会被路由到这个 Pod。
Reference:
https://i4t.com/4424.html
https://learnk8s.io/graceful-shutdown