在单一容器中运行多个进程

之前我 Ubuntu镜像(采用tini替代systemd) 等实践中,都采用了 tini 来作为初始化进程管理器,通过脚本来启动多个进程。不过,这种方式需要自己手写启动脚本,相应带来了维护成本。

按照Docker官方文档 Run multiple processes in a container ,确实可以通过脚本包装方式来启动多个进程(先启动的进程通过 & 放到后台),但是脚本包装不如进程管理器,所以需要选择一个轻量级的进程管理器。按照官方文档,推荐的是 supervisor ,所以我现在改进维护,使用 supervisor 来替代早先选择的 tini ,带来如下优势:

  • supervisor 是一个相对规范的启动管理器,通过配置文件来管理多个进程

  • 无需编写脚本

  • 提供了稳定的进程管理功能,包括自动重启服务,管理继承祖以及使用 supervisorctl 进行交互管理

如果不需要 supervisor 提供的全面的多进程管理,而遵循容器设计的单进程架构,多进程的调度完全采用 Docker ComposeKubernetes ,那么就不需要使用 supervisor ,采用 tini 来解决进程管理更为轻量级且推荐。

supervisor

supervisor 是一个 client/server 系统,用于监控一系列进程,有点类似 launchd , daemontoolsrunit 。不过, supervisor 并不是仅仅作为 process id 1 启动的 init ,而是用于控制和一个项目或用户相关的进程,这意味着 supervisor 实际上可以在启动时类似其他程序一样启动(来管理一组进程)。

在Docker容器中,需要安装 supervisor 并复制配置文件到进行中,这样一旦启动了 supervisord 服务,就能够管理一组程序进程。

备注

我发现在 Alpine Linux 中安装 supervisor 需要依赖安装 25个 软件包,让我实在有点震撼。为了能够更轻量级,我后续在 Alpine Docker镜像 将继续使用 tini

仅在 Ubuntu Linux 等重量级容器中尝试实践 supervisor ,仅作为技术积累备用

在镜像中加入 supervisor
FROM debian:latest

ENV container=docker

RUN apt update -y
RUN apt upgrade -y

# 安装supervisor
RUN apt install -y supervisor

# Copy supervisord.conf
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

RUN apt -y install sudo passwd openssh-client openssh-server curl
RUN apt -y install nginx

RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# add account "admin" and give sudo privilege
RUN groupadd -g 501 admin
RUN useradd -g 501 -u 501 -d /home/admin admin
RUN adduser admin sudo
RUN echo "%sudo        ALL=(ALL)       NOPASSWD: ALL" >> /etc/sudoers

# set TIMEZONE to Shanghai
RUN unlink /etc/localtime
RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

RUN mkdir /run/sshd
RUN ssh-keygen -A

# run service when container started - sshd
EXPOSE 22:1122

# Run your program under supervisor
# CMD ["/your/program", "-and", "-its", "arguments"]
CMD ["/usr/bin/supervisord"]
  • 配套提供 supervisord.conf :

配置 supervisord.conf 同时启动nginx和sshd
[supervisord]
nodaemon=true ; Run supervisord in the foreground
logfile=/var/log/supervisord.log
pidfile=/var/run/supervisord.pid

[program:sshd]
command=/usr/sbin/sshd -D ; Run sshd in the foreground

; 以下运行nginx参考 https://www.tothenew.com/blog/dockerizing-nginx-and-ssh-using-supervisord/
; 日志直接输出到控制台,以便标准化采集
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stdout_logfile=/dev/stdout  ; Redirect Nginx logs to Docker's log collector
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

; 以下段落参考 https://learn.arm.com/learning-paths/servers-and-cloud-computing/supervisord/example/
; 运行脚本以及apache
; [program:remoteit-agent]
; command=/usr/share/remoteit/refresh.sh

; [program:apache2]
; command=/usr/sbin/apache2ctl -DFOREGROUND
  • 创建镜像:

创建镜像
docker build --rm -t debian-ssh-supervisor .
  • 运行容器

运行容器(注意这里将Lima从Host主机映射的目录再绑定到容器内部,这样就能够从容器访问Host主机目录)
docker run -dt --name debian-ssh-supervisor --hostname debian-ssh-supervisor \
    -p 1122:22 \
    -v /Users/admin/secrets:/home/admin/.ssh \
    -v /Users/admin/docs:/home/admin/docs \
    debian-ssh-supervisor:latest

# 修正 /home/admin 目录属主权限,因为Lima虚拟机的映射Host主机 /Users/admin 目录后属主是 lima
# 绑定目录到容器以后,如果账号id不能一致,就会导致 /home/admin 目录下映射的 .ssh 和 docs 目录无法读写

# 我原本想使用 docker exec debian-ssh-supervisor chown -R admin:admin /home/admin
# 但是容器内无法修改host主机目录属主

# 最终解决需要统一 Host, Lima, Container 中 admin 账号的 id
# 举例: 
# - macOS 的 admin 账号id 是 uid=501(admin) gid=20(staff)
# - Lima 的 lima 账号id 也需要调整为 uid=501(lima) gid=20(dialout)
# - Container 的 admin 账号id 调整为 uid=501(admin) gid=20(dialout)

参考