[译] SIGTERM:Linux 容器的优雅终止(退出代码 143)

2022/07/19 14:58 下午 posted in  操作系统

翻译自 《SIGTERM: Graceful termination of Linux containers (exit code 143)》
原文链接:https://komodor.com/learn/sigterm-signal-15-exit-code-143-linux-graceful-termination/

什么是 SIGTERM(信号 15)

SIGTERM(信号 15)在基于 Unix 的操作系统(如 Linux)中用于终止进程。SIGTERM 信号提供了一种优雅的方式来终止程序,使其有机会准备关闭并执行清理任务,或者在某些情况下拒绝关闭。Unix/Linux 进程可以以多种方式处理 SIGTERM,包括阻塞和忽略。

SIGTERM 是 Unix/Linux kill 命令的默认行为,当用户执行 kill 时,操作系统会在后台向进程发送 SIGTERM。如果过程不在 Docker 容器中,通过 SIGTERM 信号终止的容器在其日志中显示退出码 143。如果您是 Kubernetes 用户,本文将帮助您了解 Kubernetes 终止容器时幕后发生的情况,以及如何在 Kubernetes 中使用 SIGTERM 信号。

SIGTERM 与 SIGKILL

SIGTERM(Unix 信号 15)是一个“礼貌”的 Unix 信号,默认情况下会终止进程,但可以被进程处理或忽略。这使进程有机会在关闭之前完成基本操作或执行清理。目的是不管它是否成功结束,都要杀死进程,但是给它一个机会先清理进程。

SIGKILL(Unix 信号 9)是一个“残酷”的 Unix 信号,它会立即终止进程。无法处理或忽略 SIGKILL,因此进程没有机会进行清理。SIGKILL 应该被 Unix/Linux 用户用作最后的手段,因为它可能导致错误和数据损坏。

在某些情况下,即使发送了 SIGKILL,内核也可能无法终止进程。如果一个进程正在等待网络或磁盘 I/O,而内核无法阻止它,它就会成为僵尸进程。需要重新启动才能从系统中清除僵尸进程。

退出码 143 和 137 与 Docker 容器中的 SIGTERMSIGKILL 一一对应:

  • Docker 退出码 143 – 表示容器收到底层操作系统的 SIGTERM
  • Docker 退出码 137 - 表示容器收到底层操作系统的 SIGKILL

在 Linux 中发送 SIGTERM

在 Unix/Linux 中结束进程最常用的方法是使用 kill 命令,如下所示:kill [ID]。默认情况下,kill 命令会向进程发送 SIGTERM 信号。

如需找到 [ID](进程 ID),请使用命令 ps -aux,它会列出所有正在运行的进程。

如何发送 SIGKILL

在极端情况下,您可能需要立即使用 SIGKILL 终止进程。使用此命令发送 SIGKILLkill -9 [ID]

处理僵尸进程

当您列出正在运行的进程时,您可能会发现在 CMD 列中显示 defunct 的进程。这些是没有正确终止的僵尸进程。僵尸进程的特征是:

  • 不再执行
  • 没有分配系统空间
  • 但是保留一个进程ID

僵尸进程会一直出现在进程表中,直到其父进程关闭或操作系统重新启动。在许多情况下,僵尸进程会在进程表中累积,因为多个子进程被父进程 fork 出来,但没有被成功杀死。为避免这种情况,请确保您的应用程序的 sigaction 事务忽略 SIGCHLD 信号。

Kubernetes 中的 SIGTERM

如果您是 Kubernetes 用户,您可以通过终止 pod 向容器发送 SIGTERM。每当 pod 终止时,默认情况下,Kubernetes 都会向 pod 中的容器发送 SIGTERM 信号。

由于扩容或部署操作,Pod 通常会自动终止。要手动终止 pod,您可以发送 kubectl delete 命令或 API 调用来终止 pod。

请注意,在默认为 30 秒的宽限期之后,Kubernetes 会发送 SIGKILL 以立即终止容器。

优雅终止和 SIGTERM

Kubernetes 管理容器集群,会在您的应用程序上执行许多自动化操作。例如,它可以对应用程序扩容或缩容、更新以及删除。因此,在很多情况下 Kubernetes 需要关闭一个 pod(带有一个或多个容器),即使它们运行正常。

在某些情况下,Kubernetes 会因为 Pod 出现故障,或者因为主机上的资源不足(称为驱逐)而关闭 Pod。每当 Kubernetes 出于任何原因需要终止 pod 时,它都会向 pod 中运行的容器发送 SIGTERM

Kubernetes 终止 pod 的完整过程如下:

  1. Pod 设置为 Terminating 状态:然后 Kubernetes 将其从所有服务中删除,并停止接收新流量。此时,在 pod 上运行的容器并不会感知到这一变化。
  2. preStop hook:这是一个特殊的命令,在 pod 开始终止之前发送到 pod 中的容器。您可以在容器中使用此 hook 来启动正常关闭。虽然最好直接处理 SIGTERM 信号(在下一步中发送),但如果由于任何原因无法执行,则可以使用 preStop hook,且无需更改应用程序的代码。
  3. SIGTERM 信号发送到 pod:Kubernetes 将 SIGTERM 发送到 pod 中的所有容器。理想情况下,您的应用程序应该处理 SIGTERM 信号并启动干净的关闭过程。请注意,即使处理了 preStop hook,您仍然需要测试并了解您的应用程序如何处理 SIGTERM。对 preStop 和 SIGTERM 的冲突或重复反应可能导致生产问题。
  4. 宽限期:发送 SIGTERM 后,Kubernetes 会等待 TerminationGracePeriod,默认为 30 秒,以允许容器关闭。您可以在每个 pod 的 YAML 模板中自定义宽限期。注意:Kubernetes 不会等待 preStop hook 完成,它从发送 SIGTERM 信号的那一刻开始计算宽限期。如果容器在宽限期结束之前自行退出,Kubernetes 将停止等待并进入下一步。
  5. 向 pod 发送 SIGKILL 信号:所有正在运行的容器进程在主机上立即终止,并且 kubelet 将清理所有相关的 Kubernetes 对象。

处理 SIGTERM 和 preStop

为确保 pod 终止不会中断您的应用程序并影响最终用户,您应该处理 pod 的终止。

实际上,这意味着需要确保您的应用程序处理 SIGTERM 信号并在收到信号时执行有序的关闭过程。这应该包括完成事务、保存临时数据、关闭网络连接和清理不需要的数据。

请注意,与常规 Linux 系统不同,在 Kubernetes 中,在宽限期后,SIGTERM 后面跟着 SIGKILL。所以你必须准备关闭容器,不能简单地忽略它。

处理优雅终止的另一个选项是 preStop hook,允许您在不更改应用程序代码的情况下执行关闭过程。如果您使用 preStop hook,请确保其执行的操作不会与应用程序在收到 SIGTERM 信号时执行的操作重复或冲突。通常最好处理 SIGTERM 或 preStop 其中之一,以避免冲突。

与 SIGTERM 相关的错误

任何导致 pod 关闭的 Kubernetes 错误都会触发 SIGTERM 信号发送到 pod 内的容器:

  • 在 Kubernetes 级别,您将通过运行 kubectl describe pod 看到 Kubernetes 错误。
  • 在容器级别,您可以看到退出代码:如果容器使用 SIGTERM 正常终止,则为 143,如果在宽限期后强制终止,则为 137。
  • 在主机级别,您可以看到发送到容器进程的 SIGTERMSIGKILL 信号。

一个例外是 OOMKilled 错误。这是由于容器或 pod 超出主机上分配给它们的内存而发生的 Kubernetes 错误。当容器或 Pod 因 OOMKilled 而终止时,Kubernetes 会立即发送 SIGKILL 信号,而不使用 SIGTERM 和宽限期。

SIGTERM 如何影响 NGINX Ingress Controllers?

在 Kubernetes 上运行应用程序时,您必须确保 ingress controllers 不会出现停机。否则,每当 controller 重新启动或重新部署时,用户都会遇到速度变慢或服务中断的情况。如果一个 ingress pod 被终止,可能会导致连接断开,在生产中必须避免这种情况。

问题:NGINX 没有在 SIGTERM 上执行优雅终止

如果你使用的是官方的 NGINX Ingress Controller,当 controller Pod 被终止时,Kubernetes 会像往常一样发送一个 SIGTERM 信号。

然而,NGINX controller 并没有按照 Kubernetes 期望的方式处理 SIGTERM

  • 当 NGINX 收到 SIGTERM 时,它会立即关闭。基本上,NGINX 将 SIGTERM 视为 SIGKILL
  • 当 NGINX 收到 SIGQUIT 信号时,它会执行正常关闭。

解决方案:使用 preStop 挂钩

正如我们在上面的处理 SIGTERM 和 preStop 部分中所讨论的,Kubernetes 提供了第二个处理优雅终止的方案 - preStop hook。您可以在发送 SIGTERM 之前使用 preStop 挂钩向 NGINX 发送 SIGQUIT 信号。这避免了 NGINX 突然关闭,并使其有机会优雅地终止。