[译] SIGKILL:Linux 容器的快速终止 (信号 9)

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

翻译自 《SIGKILL : Fast termination of Linux containers (signal 9)》
原文链接:https://komodor.com/learn/what-is-sigkill-signal-9-fast-termination-of-linux-containers/

什么是 SIGKILL (信号 9)

SIGKILL 是一种通信类型,称为信号,在 Unix 或类 Unix 的操作系统(如 Linux)中用于立即终止进程。 Linux 操作员以及 Kubernetes 等容器编排器在需要关闭基于 Unix 的操作系统上的容器或 Pod 时使用。

信号是发送到正在运行的程序的标准化消息,触发特定操作(例如终止或处理错误),是一种进程间通信(IPC)。当操作系统向目标进程发送信号时,它会等待原子指令完成,然后中断进程的执行,并对信号进行处理。

SIGKILL 指示进程立即终止,且不能被忽略或阻止。该进程被杀死时,其运行的线程也会被杀死。如果 SIGKILL 信号未能终止进程及其线程,则表明操作系统出现故障。

这是杀死进程的最有力的方式,但是也可能会产生意想不到的后果,因为并不确定该进程是否已完成其清理操作。SIGKILL 可能导致数据丢失或损坏,所以只有在没有其他选择的情况下才应该使用。在 Kubernetes 中,SIGKILL 信号发送前总是会先发送 SIGTERM,让容器有机会优雅地退出。

SIGTERM 与 SIGKILL

在 Linux 和其他类 Unix 操作系统中,有几种操作系统信号可用于终止进程。

最常见的类型是:

  • SIGKILL(也称为 Unix 信号 15):突然终止进程,产生致命错误。它在终止进程时总是有效的,但可能会产生意想不到的后果。
  • SIGTERM(也称为 Unix 信号 9):尝试终止进程,但可以通过各种方式阻止或处理。这是杀死进程的更温和的方法。

kill -9 命令

如果您是 Unix/Linux 用户,以下过程展示如何直接终止进程:

  1. 列出当前正在运行的进程。命令 ps -aux 显示属于所有用户和系统守护程序的所有正在运行的进程的详细列表。
  2. 确定您需要终止的进程的进程 ID。
  3. 执行以下操作之一:
    • 使用 kill [ID] 命令尝试使用 SIGTERM 信号杀死进程
    • 使用 kill -9 [ID] 命令立即使用 SIGKILL 信号终止进程

什么时候使用 SIGKILL?

SIGKILL 立即终止正在运行的进程。对于简单的程序,这可能是安全的,但大多数程序都很复杂并且由多个程序组成,即使是看似微不足道的程序,也需要在退出前执行清理操作。

如果程序在收到 SIGKILL 信号时尚未完成清理,则数据可能会丢失或损坏。所以,您应该仅在以下情况下使用 SIGKILL:

  • 进程在其清理过程中存在错误或问题;
  • 您不希望进程自行清理、保留数据以进行故障排除或取证调查;
  • 该进程可疑或已知是恶意的。

Kubernetes 中的 SIGKILL

如果您是 Kubernetes 用户,您可以通过使用 kubectl delete 命令终止 pod 来向容器发送 SIGKILL

Kubernetes 会首先向 pod 中的容器发送 SIGTERM 信号。默认情况下,Kubernetes 给容器 30 秒的宽限期,然后发送 SIGKILL 立即终止它们。

Pod 终止过程和 SIGKILL

Kubernetes 在部署过程中执行 scale-down 事件或更新 pod 时,会分三个阶段终止容器:

  • kubelet 向容器发送 SIGTERM 信号。您可以处理此信号以优雅地终止容器上运行的应用程序,并执行自定义的清理任务。
  • 默认情况下,Kubernetes 为容器提供 30 秒的宽限期来退出。此值是可自定义的。
  • 如果容器没有退出并且宽限期结束,kubelet 会发送一个 SIGKILL 信号,这会导致容器立即关闭。

重要的是要认识到,虽然可以在容器的日志中捕获 SIGTERM,但不能捕获 SIGKILL 命令,因为它会立即终止容器。

与 SIGKILL 相关的错误

任何导致 pod 关闭的 Kubernetes 错误都会导致 SIGTERM 信号发送到 pod 内的容器,然后是 SIGKILL

OOMKilled 错误的情况下,容器或 pod 因为超出主机上分配的内存而被杀死,kubelet 会立即向容器发送 SIGKILL 信号。

要对被 Kubernetes 终止的容器进行故障排除:

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

SIGKILL 如何影响 NGINX Ingress Controllers?

就像 Kubernetes 可以发送 SIGTERMSIGKILL 信号来关闭常规容器一样,也将这些信号发送到 Nginx Ingress Controller pod。然而,NGINX 处理信号却有所不同:

  • 当接收到 SIGTERMSIGINT 时:NGINX 执行快速关闭。主进程指示工作进程退出,仅等待 1 秒,然后向其发送 SIGKILL 信号。
  • 当收到 QUIT:NGINX 执行正常关闭。关闭监听端口以避免接收更多请求,关闭空闲连接,只有在所有工作进程退出后才退出。

因此,在某种意义上,NGINX 将 SIGTERMSIGINT 视为 SIGKILL。如果控制器在收到信号时正在处理请求,它将断开连接,将导致 HTTP 服务器错误。为了防止这种情况,您应该使用 QUIT 命令关闭 NGINX Ingress Controllers。

使用 QUIT 关闭 NGINX Ingress Controllers

在标准的 nginx-ingress-controller 镜像(版本 0.24.1)中,有一个命令可以向 NGINX 发送适当的终止信号。运行此脚本,通过向其发送 QUIT 信号来优雅地关闭 NGINX:

/usr/local/openresty/nginx/sbin/nginx -c /etc/nginx/nginx.conf -s quit
while pgrep -x nginx; do 
  sleep 1
done

SIGKILL 如何工作

SIGKILL 完全由操作系统(内核)处理。当为特定进程发送 SIGKILL 时,内核调度程序立即停止为进程提供 CPU 时间来执行用户空间代码。当调度器做出这个决定时,如果进程有线程在不同的 CPU 或内核上执行代码,那么这些线程也会停止。

进程执行内核代码时被杀死会发生什么?

当传递 SIGKILL 信号时,如果进程或线程正在执行系统调用或 I/O 操作,内核会将进程切换到“dying”状态。内核调度 CPU 时间以允许垂死的进程解决其剩余的问题。

不可中断的操作将一直运行直到它们完成(但在运行更多用户空间代码之前检查“dying”状态)。可中断操作,当它们识别出进程“dying”时,会提前终止。当所有操作完成后,该进程被赋予“dead”状态。

进程被标记为“dead”时发生什么?

当内核操作完成时,内核开始清理进程,就像程序正常退出时一样。给进程一个高于 128 的结果码,表明它被信号杀死了。被 SIGKILL 杀死的进程没有机会处理收到的 SIGKILL 消息。

在这个阶段,进程转换为“僵尸”状态,并使用 SIGCHLD 信号通知父进程。僵尸状态表示进程已被杀死,但是父进程可以使用 wait(2) 系统调用读取死进程的退出码。僵尸进程消耗的唯一资源是进程表中的一个槽,它存储了进程 ID、退出和其他启用故障排除的“关键统计信息”。

如果僵尸进程在几分钟内保持活动状态,这可能表明其父进程的工作流存在问题。