在Docker中使用Supervisor

56

我并不是在询问关于如何在 Docker 中使用 Supervisor,而只是想确认一下我的理解是否正确。

我了解到当 Docker 运行时只能运行单个进程。此外,当我们需要在容器内运行多个进程时使用 Supervisor。

我见过一些例子,在不使用 Supervisor 的情况下,从基础镜像启动容器并安装了多个服务,然后提交该容器以形成新镜像。

那么,我的基本疑问是这两种方法之间的区别是什么。

我理解的是,当 Docker 容器停止时,它会向 PID 1 发送一个终止信号,而 PID 1 管理子进程并停止所有子进程,这正好是 Supervisor 所做的事情。虽然我们可以在没有 Supervisor 的情况下安装多个进程,但在执行 docker run 命令时只能运行一个进程,当容器停止时只会发送信号给 PID 1,并且其他正在运行的进程将无法正常停止。

请确认我的使用 supervisord 的理解是否正确。


1
2016年9月更新:请参见下面的我的新答案:在docker 1.12中,docker守护进程可以为您处理这些僵尸进程。 - VonC
3个回答

66
虽然我们可以在没有supervisor的情况下安装多个进程,但是当执行docker run时只能运行一个进程,停止容器时只有PID 1会收到信号,其他正在运行的进程不会优雅地停止。
是的,尽管这取决于您的主要进程如何运行(前台或后台),以及它如何收集子进程。这些详细信息在 "Trapping signals in Docker containers" 中有详细介绍。
使用docker stop发送SIGTERM信号停止正在运行的容器,让主进程处理该信号,在优雅的期间使用SIGKILL终止应用程序。发送给容器的信号由正在运行的主进程(PID 1)处理。
如果应用程序在前台,也就是说,应用程序是容器中的主进程(PID1),则可以直接处理信号。
但是:

要发送信号的进程可能是后台进程,您不能直接发送任何信号。在这种情况下,一种解决方案是设置一个shell脚本作为入口点,并在该脚本中编排所有信号处理。

该问题在 "Docker和PID 1僵尸回收问题" 中有更详细的说明。

Unix被设计成父进程必须显式“等待”子进程终止,以收集其退出状态。使用waitpid()系统调用族来执行此操作,直到父进程调用waitpid()处理子进程,才会消除僵尸进程。

调用waitpid()对子进程进行清理以消除其僵尸状态,称为“回收”。

init进程-- PID 1 -- 有一个特殊任务。它的任务是“领养”孤儿子进程。

https://blog.phusion.nl/wp-content/uploads/2015/01/adoption.png

操作系统希望 init 进程也能够清理掉被收养的子进程。
Docker 的问题在于:
我们看到很多人只在容器中运行一个进程,并认为当他们运行这个单一进程时,就完成了任务。但是,最有可能的情况是,这个进程并没有像正常的 init 进程一样去处理被收养的进程,而是期望另一个 init 进程来完成该工作,这是合理的。
使用像 phusion/baseimage-docker 这样的镜像可以帮助管理一个(或多个)进程,同时保持主进程符合 init 标准。
它使用 runit 而不是 supervisord 来进行多进程管理。
{

Runit并不是用来解决收割问题的,而是为了支持多个进程。通过进程和用户隔离,我们鼓励使用多个进程来提高安全性。
Runit使用的内存比Supervisord少,因为Runit是用C语言编写的,而Supervisord是用Python编写的。
在某些情况下,容器中的进程重启优于整个容器的重启。

}

该图像包括一个my_init脚本,它负责“收割”问题。

{

在baseimage-docker中,我们鼓励在单个容器中运行多个进程。不一定是多个服务。
一个逻辑服务可以由多个操作系统进程组成,并且我们提供了方便的工具来实现这一点。

}

感谢您详细的回答。我正在尝试Phusion镜像,据我所知,每当容器启动时,它都会运行/etc/init.d中的任何内容。但是,我在init.d中有一个服务,在容器启动时无法启动。请问您能否帮忙解决这个问题? - user3275095
当然可以:您能否提出一个新问题,并详细说明您的新设置?这样,我(以及其他人)就可以看一下。 - VonC
哦,我的错误,应该是 /etc/my_init.d。 - user3275095
1
这里的关键是 phusion/baseimage-docker - Matthew Ratzloff

19

2016年9月更新,适用于docker 1.12(2016年第四季度/2017年第一季度)

Arnaud Porterie 刚刚发推

[] 刚合并:使用 docker run --initRick Grimes 将处理所有的僵尸进程。

(提交 eabae09)

请参见PR 26061:“添加处理僵尸进程和信号处理的 init 进程”(以及 PR 26736

这个功能会添加一个小的C二进制文件来对抗僵尸进程。它被挂载在/dev/init下,并在用户指定的参数之前添加。你可以通过一个守护进程标志(dockerd --init)来启用它,因为默认情况下它是禁用的以保持向后兼容性。

你也可以通过docker run --init=true|false命令覆盖守护进程选项或在每个容器中单独指定。

你可以将一个进程作为pid 1在容器中运行,测试它并查看容器中额外出现的僵尸进程。

int main(int argc, char ** argv) {
    pid_t pid = fork();
    if (pid == 0) {
        pid = fork();
        if (pid == 0) {
            exit(0);
        }
        sleep(3);
        exit(0);
    }
    printf("got pid %d and exited\n", pid);
    sleep(20);
}

现在 docker daemon 已经有了这个选项

--init

在容器内运行init以转发信号并回收进程。

2
这篇 Docker 文档中展示了一个运行多个进程并使用 supervisord 的例子。

https://docs.docker.com/config/containers/multi-service_container/

我已经很好地解决了这个问题,但我们可能会将工作进程简单地转移到另一个容器中,每个容器只处理一个进程。这种方法在目前看来似乎更简单。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接