为什么对于某些 docker 镜像,CTRL+C 不会终止“docker run”命令?

gre*_*emo 7 docker

使用此图像(Caddy 网络服务器):

docker run --rm -p 80:80 caddy:latest 
Run Code Online (Sandbox Code Playgroud)

我可以通过在终端内发送 CTRL+C来阻止它。

其他一些图像不会以这种方式工作,例如 MariaDB:

docker run -it --rm -p 3306:3306 -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true mariadb:latest
Run Code Online (Sandbox Code Playgroud)

我无法用 CTRL+C 停止它

我注意到在 Caddy 中Dockerfile没有butENTRYPOINT only CMD,而 MariaDB 有 anENTRYPOINT和 a CMD

为什么?有什么理由不支持终止信号的处理吗SIGTERM如果这是原因的话,如何支持docker 入口点呢?

Von*_*onC 14

但我有点好奇官方图片是如何解决这个问题的......

不要忘记Docker 1.13 附带了tini,最初来自krallin/tini.

任何运行的镜像都docker run --init将包含一个init转发信号和获取进程的容器内部。
正如这里提到的tini工作透明,Dockerfiles 不需要以任何方式修改。

因此,在 Unix 系统中,当您按下CTRL+C终端时,一个SIGINT(信号中断)会被发送到前台进程组,在本例中是 Docker 容器。如果您使用CTRL+\,它会发送SIGQUIT,如果您使用CTRL+Z,它会发送SIGTSTP(信号停止)。

Docker 容器运行单个主进程,该进程以 PID 1 运行。PID
1 在 Linux 上很特殊:它是第一个运行的进程,也是所有其他进程的祖先。
它与 Unix 信号也有特殊的关系:它是唯一可以选择忽略SIGINT和 的进程SIGTERM。其他进程不能忽略这些信号,但它们可以具有在收到这些信号时执行的处理程序。

在 Docker 中,当您使用CMD指定要运行的进程时,Docker 会用一个小init系统(如tini)包装该进程,该系统可以正确处理 Unix 信号并将它们转发到主进程(我最初在这里提到过)。
这就是为什么您的caddy图像没有 且ENTRYPOINT只有CMD,可以处理CTRL+C.

然而,当你使用 时ENTRYPOINT,Docker 不会用这个小init系统包装进程,进程以 PID 1 运行。

如果进程没有内置处理 或SIGINTSIGTERM则不会响应CTRL+C。这就是为什么当您按 时,mariadb带有 的图像ENTRYPOINT不会停止CTRL+C

终止信号的处理是正常关闭的一个重要部分。当进程收到SIGTERM(或SIGINT) 时,它应该停止接受新工作,完成当前工作,然后退出。这允许它清理正在使用的任何资源并确保数据一致性。

如果进程不处理这些信号并且只是被终止(使用SIGKILL),则它没有机会进行清理,并且可能使资源处于不一致的状态。对于像 MariaDB 这样的数据库来说,这可能是有害的,因为它可能有未提交的事务或部分写入的数据。

要支持在 Docker 入口点中处理终止信号,您可以:

  1. 将信号处理添加到入口点脚本本身。这需要修改脚本,如果入口点是二进制文件,则可能不可行。

  2. 使用init可以处理信号并将其转发到主进程的系统。有一些init类似的小型系统tini就是为此目的而设计的。您可以在 Dockerfile 中使用它们,如下所示:

    FROM your-base-image
    RUN apt-get update && apt-get install -y tini
    ENTRYPOINT ["/usr/bin/tini", "--", "your-command"]
    
    Run Code Online (Sandbox Code Playgroud)

    这将以 PID 1 运行tini,它将处理信号并将它们转发给您的命令。

  3. 使用 Docker 的内置 init,这是一个最小的tini实现。--init您可以在运行容器时使用以下选项启用它:

    docker run --init -it --rm -p 3306:3306 -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true mariadb:latest
    
    Run Code Online (Sandbox Code Playgroud)

    这将运行 Docker 的内置 init 作为 PID 1,它将处理信号并将它们转发到 MariaDB。

这些方法确保您的 Docker 入口点可以处理终止信号并在必要时执行正常关闭。


有什么理由不支持终止信号的处理吗?

不确定,除非你希望你的图像是真的:

  • 简单,对于非常简单或短暂的进程,不需要信号处理
  • 快速,以避免在代码的关键部分出现任何关闭信号
  • 当您想要防止意外或未经授权的关闭时,具有弹性。

但一般来说,忽略终止信号可能会导致数据丢失、资源泄漏或僵尸进程等问题,应该避免。


剩下的唯一一件事是:如果不支持关闭信号对于像 MariaDB 这样的服务来说有点危险,为什么 MariaDB 本身不直接在入口点支持它?

这可能是由于遗留原因,继承自 MySQL,而 MariaDB 是 MySQL 的一个分支。MySQL 是在 Docker 出现之前开发的,在传统的服务器环境中,进程接收SIGINT. 更常见的是,SIGTERM当系统关闭时,进程会收到 ,MySQL 和 MariaDB 会处理这种情况。
它的入口点反映了这一点,并且您运行它--init以确保基本SIGINT支持。

但请记住,这不是唯一的问题。我犯了一个错误:在 Azure ACI(容器)而不是 AKS (Kubernetes) 上运行 MariaDB 实例。
当 ACI 关闭时(至少当我在 2021 年使用它时),它会发送……一个 SIGKILL(无法捕获、阻止或忽略的信号)。当内核向进程发送 SIGKILL 时,该进程会立即终止,并且在被终止之前没有机会进行清理或执行任何其他工作。

因此,无论您的映像是否支持正常关闭,都要注意您的执行环境,这可能首先不允许任何正常关闭。