
概述
在 Docker 与 PHP 的生态环境中,多进程管理一直是个绕不开的话题。过去几年,supervisord 因其配置简单(一个 ini 文件管理所有服务)和稳定的特性,成为许多 PHP 容器(如 Nginx + PHP-FPM + cron 组合)的事实标准。众多老项目和内部镜像都沿用此方案,平稳运行了相当长的时间。
然而,从 2025 年下半年开始,在为新核心服务构建镜像时,一个趋势变得愈发明显:社区的主流选择正在发生转变。许多新兴项目和主流镜像开始放弃传统的 Supervisord,转而投向 S6-overlay 的怀抱。
为什么 Supervisord 在容器中越来越力不从心?
尽管 supervisord 稳定可靠,但在现代的容器化部署场景下,其设计上的某些“不适应症”也逐渐暴露:
-
容器停止时难以优雅退出
Docker stop 会发送 SIGTERM 信号,supervisord 收到后会尝试关闭子进程。但主进程自身时常无法及时退出,导致容器卡在 “Stopping” 状态长达 10 到 30 秒,最终不得不依赖 kill -9 强制终止。这在依赖健康检查、滚动更新和蓝绿部署的现代运维体系中,尤其令人头疼。
-
僵尸进程回收不彻底
PHP 脚本偶尔会通过 exec() 等方式 fork 出子进程,supervisord 在僵尸进程收割方面的能力有限,往往需要额外的配置或引入 dumb-init 这类工具来“套娃”解决。工具链的叠加,无疑增加了镜像的复杂度和体积。
-
日志输出不够“Docker友好”
在默认配置下,子进程的 stdout/stderr 会被 supervisord 接管并写入指定文件。若想通过 docker logs 命令实时查看日志,就必须为每个 [program:xxx] 单独配置 stdout_logfile=syslog 或进行重定向。服务一多,配置就变得繁琐且混乱。
-
Python 依赖与镜像体积
即便在 Alpine 这样的轻量级基础镜像中,引入 Python(哪怕是 python3-minimal)也会使镜像体积增加 30 到 50 MB。与纯 C 实现的方案相比,启动速度也会稍慢一筹。
-
社区风向已变
观察 linuxserver.io 的全家桶镜像、Home Assistant 插件、Nextcloud 官方 fpm 镜像、serversideup/php 系列,乃至 SeleniumHQ/docker-selenium 等项目,在 2025 年都陆续出现了迁移到 s6-overlay 的相关议题。在 Reddit 的 /r/docker 等社区搜索 “supervisord vs s6”,新帖的推荐也几乎一边倒地倾向于后者。
S6-overlay 的优势究竟在哪里?
与 supervisord 相比,s6-overlay 更像是为容器环境量身定制的解决方案,其设计哲学与现代容器化理念高度契合:
-
天生为容器设计
s6-overlay 的 /init 进程直接作为容器的 PID 1,能够正确处理所有信号转发、僵尸进程回收,并管理服务间的依赖启动顺序。这使得 docker stop 的响应速度极快,通常能在 2 秒内完成优雅退出。
-
日志直接输出到 stdout
服务脚本中的输出无需任何重定向,便会直接进入 docker logs 的流中。这让调试和实时监控变得异常便捷。
-
极致轻量
其官方发布的 noarch 和 arch 两个 tar.xz 包,加起来仅约 3 到 5 MB。基于纯 C 和 execline,没有任何 Python 运行时依赖。
-
优雅的依赖关系管理
通过 s6-rc 的 bundle 概念和 contents.d/ 目录结构,可以清晰定义服务间的依赖树。例如,让 Nginx 等待 PHP-FPM 就绪后再启动,或者编写一次性的初始化脚本(如检查权限、创建目录),都变得非常直观。
-
就绪状态通知机制
只需在服务目录下放置一个包含数字“3”的 notification-fd 文件,s6 就能精确知道服务何时真正进入就绪状态。这比 supervisord 简单的重启策略要可靠得多。
迁移实战:以 PHP-Nginx 容器为例
迁移过程并不复杂,核心是将基于配置文件的静态管理,转变为基于目录结构的声明式管理。
-
更换基础镜像
将基础镜像从 php:8.3-fpm-alpine 改为 alpine:3.21,然后手动添加指定版本(如 3.2.x)的 s6-overlay。
-
重构服务目录结构
删除原有的 supervisord.conf 文件,建立如下的 s6-rc 目录结构:
/etc/s6-overlay/s6-rc.d/
├── nginx/
│ ├── run # 脚本内容:#!/usr/bin/execlineb -PW with-contenv nginx -g "daemon off;"
│ └── notification-fd # 文件内容:3
├── php-fpm/
│ ├── run
│ └── notification-fd
├── init-permissions/ # oneshot 类型服务,用于启动前的初始化脚本
│ ├── run
│ └── type # 文件内容:oneshot
└── user/ # 默认的 bundle
└── contents.d/
├── php-fpm
└── nginx
-
修改 Dockerfile
将构建好的目录树(例如放在 rootfs/ 下)复制到镜像根目录,并将入口点设置为 /init:
COPY rootfs/ /
ENTRYPOINT ["/init"]
迁移完成后,通常会观察到几个明显的改进:镜像体积减少 20 到 40 MB,docker stop 时间从 15 秒以上降至 1-2 秒,docker logs 的输出干净实时,服务的健康检查也更为准确。
在 Webman 项目中使用
这里提供一个基于 S6-overlay 的 PHP 镜像在 Webman 框架下的使用示例。
拉取镜像
docker pull tinywan/docker-php-webman:8.4.16-s6
挂载并启动容器
docker run --name webman-s6 --rm -it -p 8787:8787 -v /home/www/webman:/app tinywan/docker-php-webman:8.4.16-s6
启动后,可以在日志中看到 s6-rc 和服务顺利启动的信息:
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
Workerman[start.php] start in DEBUG mode
Master pid:67 is not alive
-------------------------------------------- WORKERMAN ---------------------------------------------
Workerman/5.1.6 PHP/8.4.16 (JIT off) Linux/6.6.87.2-microsoft-standard-WSL2
--------------------------------------------- WORKERS ----------------------------------------------
event-loop proto user worker listen count state
event tcp root webman http://0.0.0.0:8787 16 [OK]
event tcp root monitor none 1 [OK]
----------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
查看容器内进程
使用 docker top 命令可以清晰地看到由 s6-svscan 和 s6-supervise 管理的进程树,这正是 s6-overlay 多进程管理能力的体现。
$ docker top webman-s6 acxf
PID TTY STAT TIME COMMAND
60483 ? Ss 0:00 \_ s6-svscan
60515 pts/0 Ss+ 0:00 \_ rc.init
60566 pts/0 S+ 0:00 | \_ php
60568 pts/0 S+ 0:00 | \_ php
... (多个php worker进程)
60516 ? S 0:00 \_ s6-supervise
60519 ? Ss 0:00 | \_ s6-linux-init-s
60526 ? S 0:00 \_ s6-supervise
60533 ? Ss 0:00 | \_ s6-ipcserverd
60527 ? S 0:00 \_ s6-supervise
写在最后
客观地说,supervisord 本身并没有错。在 2015 年至 2024 年这段时期,它几乎是 Docker 容器内管理多进程的事实标准,很好地完成了它的历史使命。
然而,技术是不断演进的。到了 2025-2026 年,容器的设计哲学更加清晰和纯粹:一个容器应当像单个进程一样运作。s6-overlay 无论是在轻量性、信号处理的准确性,还是对容器原生生态的贴合度上,都更符合这一现代理念,为运维部署带来了更顺畅的体验。
如果你的老项目仍在稳定运行,继续使用 supervisord 完全没有问题。但对于全新的项目,尤其是经典的 PHP + Nginx/FPM + 定时任务的组合,笔者现在会毫无保留地推荐 s6-overlay。如果你也遇到了类似的痛点,或已在使用 S6,欢迎到 云栈社区 的相关板块交流心得。