本文翻译自 https://docs.docker.com/config/containers/multi-service_container/

我们都知道,所谓的容器就是一个被隔离到不同环境的进程而已,那我们如果在一个容器内运行多个进程呢?

一个容器的主进程是 ENTRYPOINT 或者在 Dockerfile 最后的 CMD

容器的主进程负责管理它启动的全部进程。

在某些条件下,主进程由于出错而退出,容器退出时,主进程并不能优雅地处理停止的子进程。

如果你的进程会出现上述情况,你启动容器时可以使用 --init 标志,在容器内插入一个简易的初始化进程作为主进程,容器退出时INIT
处理这些停止的子进程 —- 子进程作为孤儿进程会被托管到 INIT 进程

用上述方式管理容器的生命周期比使用笨重的 sysinit, upstart, 或者 systemd 更优雅

如果需要在一个容器内运行多个服务,你可以通过下面几种不同方式解决。

  • 将全部的命令都封装到脚本中,并将脚本作为 CMD 执行。这是一个非常原生的做法。
    比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #!/bin/bash

    # Start the first process
    ./my_first_process -D
    status=$?
    if [ $status -ne 0 ]; then
    echo "Failed to start my_first_process: $status"
    exit $status
    fi

    # Start the second process
    ./my_second_process -D
    status=$?
    if [ $status -ne 0 ]; then
    echo "Failed to start my_second_process: $status"
    exit $status
    fi

    # Naive check runs checks once a minute to see if either of the processes exited.
    # This illustrates part of the heavy lifting you need to do if you want to run
    # more than one service in a container. The container exits with an error
    # if it detects that either of the processes has exited.
    # Otherwise it loops forever, waking up every 60 seconds

    while sleep 60; do
    ps aux |grep my_first_process |grep -q -v grep
    PROCESS_1_STATUS=$?
    ps aux |grep my_second_process |grep -q -v grep
    PROCESS_2_STATUS=$?
    # If the greps above find anything, they exit with 0 status
    # If they are not both 0, then something is wrong
    if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then
    echo "One of the processes has already exited."
    exit 1
    fi
    done

    Dockerfile

    1
    2
    3
    4
    5
    FROM ubuntu:latest
    COPY my_first_process my_first_process
    COPY my_second_process my_second_process
    COPY my_wrapper_script.sh my_wrapper_script.sh
    CMD ./my_wrapper_script.sh
  • 如果你先启动主进程但暂时需要在运行其他的进程(比如与主进程交互),那你可以使用 bash 的任务控制解决。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #!/bin/bash

    # turn on bash's job control
    set -m

    # Start the primary process and put it in the background
    ./my_main_process &

    # Start the helper process
    ./my_helper_process

    # the my_helper_process might need to know how to wait on the
    # primary process to start before it does its work and returns


    # now we bring the primary process back into the foreground
    # and leave it there
    fg %1

    Dockerfile

    1
    2
    3
    4
    5
    FROM ubuntu:latest
    COPY my_main_process my_main_process
    COPY my_helper_process my_helper_process
    COPY my_wrapper_script.sh my_wrapper_script.sh
    CMD ./my_wrapper_script.sh
  • 使用一个像 supervisord 一样的进程管理器。这是一个重量级的方法,需要将 supervisord 和它的配置文件打包到镜像(或者直接使用基于 supervisord的镜像),然后启动supervisor,它会根据配置文件代你管理进程,下面是一个使用 supervisord 的 Dockerfill,我们假定已经预先写好了 supervisord.confmy_first_process, my_second_process,并放到与 Dockerfile 同级的目录

    1
    2
    3
    4
    5
    6
    7
    FROM ubuntu:latest
    RUN apt-get update && apt-get install -y supervisor
    RUN mkdir -p /var/log/supervisor
    COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
    COPY my_first_process my_first_process
    COPY my_second_process my_second_process
    CMD ["/usr/bin/supervisord"]