什么是容器中的多进程管理
在容器中的主进程 (main running process) 是指 Dockerfile中 ENTRYPOINT
或 CMD
指定运行的命令,通常情况下一个进程(服务)为一个容器;也存在一种场景,就是主进程会fork多个子进程,例如nginx,不过这种多进程通常为nginx主进程进行管理。而一些场景下,我们的业务本身就需要多个启用独立的多个进程。
在Docker官方提到了在容器中运行多个服务的方式,官方提出,应该避免这种情况
but to get the most benefit out of Docker, avoid one container being responsible for multiple aspects of your overall application.
但也给出了如何管理多进程的一种思路,
- Use a wrapper script
- Use Bash job controls
- Use a process manager
下面就通过官方给出的这三种方式阐述容器中的多进程管理
Use a wrapper script
对于使用脚本来管理多进程来说,本质上是可以实现多进程的启动,但是你没法去监控(管理)多个进程的运行时,例如 Nginx + PHP 模式, PHP或nginx全部挂掉,只要脚本还在运行,那么这个容器的生命周期还是处于Running
Use Bash job controls
这种模式是利用了Bash的后台模式进行短暂的切换进程,但有些镜像不提供Bash这时应该怎么办
Use a process manager
进程管理器,通常情况下大家想到的就是顶顶大名的 supervisor 和 systemd,但这两个程序运行的环境十分苛刻,例如 supervisor 是Python开发的程序,运行需要依赖 Python;而 systemd 的运行条件更为苛刻,例如需要额外运行dbus-damon进行注册到dbus总线之上,这种进程管理器可能运行的进程比我们要管理的进程都要多。在这种场景下,有一个部署简单,配置简单,无依赖的轻量级容器多进程管理器 s6-overlay
s6-overlay
s6-overlay 一组脚本,只需要简单解压就可以使现有的 Docker 镜像通过将 s6 用作容器的 pid 1 和服务的来管理多个进程。
s6-overlay 包含两个组件,s6-overlay-noarch.tar.xz
与 s6-overlay-x86_64.tar.xz
- noarch 包含了一些脚本,是s6运行的所必须有的一个组件,他包含了 /init 作为 pid 为1 的进程
- x86 是作为 x86系统下运行 s6 所需要的 所有二进制文件
编写服务启动脚本
需要在 /etc/s6-overlay/s6-rc.d/ 与 /etc/services.d/ 中配置你要启动的app,例如
|
|
run则代表启动的命令
|
|
除上述提到的内容外,还需一个 type 来指明 启动的模式
- longrun 运行为daemon模式被s6进行管理
- oneshot 类似一个脚本,但通过s6-rc进行管理,类似于初始化任务
所以你需要在 /etc/s6-overlay/s6-rc.d/myapp/type
中定义其 type
文件,这个文件内填写这两种类型的文字即可
到这里完成了一个基本的进程的配置,例如还有 finish
脚本,当在失败时执行的
S6 init 的阶段
s6官方对init阶段省略了用户不需要关心的一个阶段后,为 3 个阶段
- 初始化阶段 (initialization),这里是内核启动的第一个用户态进程,该阶段作为init唯一的持久进程
- 巡航阶段 (cruising),这个阶段init负责启动与维护其他进程,比如运行s6系列,init 的职责是清除孤儿进程并监督进程,同时允许管理员添加或删除服务,例如上面的
longrun
与oneshot
类的服务,都是在这个阶段被启动 - 关闭阶段 (shutdown),在此阶段结束时,所有进程都将被终止
- 发送 TERM 信号 到遗留的
longrun
服务,如果需要将等待结束后退出 - 有序的关闭用户 s6-rc
- 运行 finalization 脚本
- 向进程发送
TERM
signal,最终不会留下任何的进程 - sleep一阵,允许驻留的进程退出完
- 发送 KILL 信号,退出所有进程,这时容器退出
- 发送 TERM 信号 到遗留的
S6的安装
S6的安装很简单,步骤只需要如下几步:
- 只需要下载对应的两个tar包
- 将
init
作为pid为1的进程 - 准备 installiation阶段 和 finalization 阶段的脚本 复制到对应路径内就可以正常启动了
finalization 通常使用场景为:当你的程序在退出时存在一些特定的结束命令的场景,官方给出的通常是用于进程结束后的清理动作
Note that in general, finish scripts should only be used for local cleanups after a daemon dies. If a service is so important that the container needs to stop when it dies, we really recommend running it as the CMD.
下面是一个完整的使用了 s6 的多进程容器的 Dockerfile
|
|
在容器中进程内可以看出对应进程图 s6init 作为所有进程的父进程管理着supervise,之后管理者你需要管理的进程;如果进程异常,他会不断地拉起对应的进程,当然,如果是启动参数错误问题,那么永远不会被拉起,当然容器是出于 Running,这时就需要自行做服务检测
|
|