容器进程管理之S6和S6-overlay

December 24, 2019

什么是s6

s6 是一个用于 UNIX 的小型的、安全的守护进程管理组件,其英文全称 skarnet.org’s small and secure supervision software suite,因为首字母有6个s,所以被称为 s6。

s6包含的组件

s6 包含很多组件,其核心组件有四个,分别是:s6-svscan、s6-supervise、s6-svscanctl、s6-svc。理论上来说,只要有了这四个核心组件,就可以使用 s6 的功能了,其他的组件只是提供一些附加功能。

其中,s6-svscan 和 s6-supervise 是进程管理树的组件,他们是常驻的,而 s6-svscanctl 和 s6-svc 是用于控制 s6-svscan 和 s6-supervise的,他们不是常驻的。

  • s6-supervise s6-supervise 用于监听和维护守护进程的状态,当守护进程挂掉以后,s6-supervise 会重启该进程,它是每个守护进程的直接父进程。

  • s6-svscan s6-svscan 用于为每个需要启动的守护进程启动一个 s6-supervise 进程,并监听和维护 s6-supervise 进程的状态,是 s6-supervise 的直接父进程。

  • s6-svc s6-svc 用于控制运行中的 s6-supervise 进程,如 s6-svc -k /var/run/s6/services/nginx表示杀掉 nginx 进程,s6-svc -r /var/run/s6/services/nginx 表示重启 nginx 进程。

  • s6-svscanctl s6-svscanctl 是用于控制 s6-svscan 进程的命令行工具,类似 s6-svc 控制 s6-supervise。

s6是如何启动的

s6 是用于管理守护进程的,那这些守护进程是如何被启动的呢?

从上面几个组件可以看出,守护进程是由 s6-supervise 启动的,而 s6-supervise 又是由 s6-svscan 启动的,因此,只需要启动 s6-svscan 就行了。

s6-svscan 启动时需要指定一个目录,如 s6-svscan -t0 /var/run/s6/services(若不指定则为当前目录),这个目录用于存放一些子目录,每个子目录表示需要启动的守护进程,子目录的目录名一般为进程名,如下表示需要启动 cron、nginx、ssh三个进程。

[root@0491df61dd4a services]# pwd
/var/run/s6/services
[root@0491df61dd4a services]# ls
cron  nginx  ssh

各个子目录中包含 run 和 finish 两个脚本文件,run 脚本文件用于启动守护进程,进程必须是前台常驻的,finish 脚本用于进程退出后执行一些清理操作。如下用于启动 nginx 进程的 run 脚本文件:

[root@0491df61dd4a nginx]# cat run
#!/usr/bin/with-contenv sh
exec /usr/local/nginx/sbin/nginx -g 'daemon off;

s6-svscan 会进入到指定的目录,并扫描该目录下的所有子目录。对于每一个子目录,都会创建一个对应的 s6-supervise 进程,再由 s6-supervise 创建对应的守护进程。

通过 ps -ef 可以看到进程树如下所示:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 11:23 ?        00:00:00 s6-svscan -t0 /var/run/s6/services
root        25     1  0 11:23 ?        00:00:00 s6-supervise s6-fdholderd
root       155     1  0 11:23 ?        00:00:00 s6-supervise cron
root       156     1  0 11:23 ?        00:00:00 s6-supervise ssh
root       158     1  0 11:23 ?        00:00:00 s6-supervise nginx
root       159   155  0 11:23 ?        00:00:03 /usr/sbin/crond -n
root       160   156  0 11:23 ?        00:00:03 /usr/sbin/sshd -D
root       162   158  0 11:23 ?        00:00:04 nginx: master process /usr/local/nginx/sbin/nginx -g daemon off;
daemon     166   162  0 11:23 ?        00:00:00 nginx: worker process
daemon     167   162  0 11:23 ?        00:00:00 nginx: worker process
daemon     168   162  0 11:23 ?        00:00:00 nginx: worker process
daemon     169   162  0 11:23 ?        00:00:00 nginx: worker process

通过 pstree 命令查看树形图:

s6-svscan─┬─s6-supervise
                  ├─s6-supervise───crond
                  ├─s6-supervise───sshd
                  └─s6-supervise───nginx───4*[nginx]

什么是s6-overlay

s6-overlay 是基于 s6 的用于容器内多进程管理的工具。其实就是对 s6 做了一下封装,通常使用是在构建镜像时将 s6 的压缩包 s6-overlay.tag.gz 解压到镜像中,并指定镜像的启动命令为其 init 脚本。

FROM busybox
ADD https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-amd64.tar.gz /tmp/
RUN gunzip -c /tmp/s6-overlay-amd64.tar.gz | tar -xf - -C /
ENTRYPOINT ["/init"]

s6-overlay的执行过程

s6-overlay的执行过程分为 3 个阶段:

  1. 预处理阶段 用于准备一些环境变量,并检查一些文件的权限。

  2. 启动阶段 启动阶段又可以分为三个阶段,分别是:
    • 执行修改相关权限的脚本,脚本位于 /etc/fix-attrs.d 目录。
    • 执行初始化脚本,用于处理一些初始化的操作,脚本位于 /etc/cont-init.d目录。
    • 拷贝用户的 /etc/services.d 目录中的文件到 s6 启动时指定的目录中,该 /etc/services.d 目录中存放的内容就是用于启动守护进程的子目录。
  3. 结束阶段 容器退出时, 会先执行 /etc/cont-finish.d 目录中的脚本文件,用于清理相关内容,最后停止容器中的服务。

查看容器启动日志:

[s6-init] making user provided files available at /var/run/s6/etc...exited 0.
[s6-init] ensuring user provided files have correct perms...exited 0.
[fix-attrs.d] applying ownership & permissions fixes...
[fix-attrs.d] done.
[cont-init.d] executing container initialization scripts...
[cont-init.d] done.
[services.d] starting services
[services.d] done.

s6-init 即为预处理阶段,后面的 fix-attrs.d、cont-init.d、services.d 即为启动阶段。

查看停掉容器时的日志:

[cont-finish.d] executing container finish scripts...
[cont-finish.d] done.
[s6-finish] waiting for services.
[s6-finish] syncing disks.
[s6-finish] sending all processes the TERM signal.
[s6-finish] sending all processes the KILL signal and exiting.

以上即为结束阶段。

启动脚本init

s6-overlay的启动命令就是一个 init 脚本,通过查看该脚本内容,发现它调用了以下命令:

[root@test /]# cat /init
#!/usr/bin/execlineb -S0
/etc/s6/init/init-stage1 $@

其中,/etc/s6/init/init-stage1 后面有个 $@ 参数,说明我们也可以在 init 后面指定要启动的服务,比如:

...
ENTRYPOINT ["/init"]
CMD ["nginx"]

表示仅启动 nginx 服务。

继续查看 /etc/s6/init/init-stage1 文件内容,发现最后一行也是调用了另一个脚本文件:

[root@test /]# cat /etc/s6/init/init-stage1
...
/etc/s6/init-no-catchall/init-stage1 $@

继续查看 /etc/s6/init-no-catchall/init-stage1 内容:

...
s6-svscan -t0 /var/run/s6/services

发现最终调用了 s6-svscan -t0 /var/run/s6/services,进入容器查看ps -ef,可以看到这个 s6-svscan -t0 /var/run/s6/services 也是 1 号进程。所以,到这里也就可以看出 s6-overlay 实际上就是调用了 s6 的功能,是使用 s6-svscan 来管理进程的。