

S6 Overlay 是一款专为容器化场景从零设计的进程管理工具,是 Supervisor 的现代替代方案。它基于 S6 这套轻量级、高可靠性的进程监督工具集构建,由 just-containers 社区将其封装为适合 Docker 容器使用的形态。
由于 PHP 通常需要同时运行多个进程(例如 Web 服务器 + PHP 解释器),S6 Overlay 与之配合使用非常合适。它能够帮助我们在单个容器内协调这些进程的生命周期,而无需引入额外的编排复杂度。
我们仅在需要同时运行两个进程才能提供完整服务的镜像中使用 S6 Overlay,包括:
serversideup/php:*-fpm-apache — Apache 作为反向代理 + PHP-FPMserversideup/php:*-fpm-nginx — NGINX 作为反向代理 + PHP-FPM而像 cli 和 fpm 这类只需运行单一进程的镜像变体,则不需要 S6 Overlay。
运行一个 PHP 应用可以拆分为两个独立的职责:
早期可以通过 Apache 的 "mod_php" 模块将两者统一处理。这种方式配置简单,但实践证明其资源利用率很低。
使用 "mod_php" 意味着每个 Apache 工作进程(Worker)都会内嵌一个 PHP 解释器。即使只是请求一个 JavaScript 文件或一张图片,Apache 也会加载完整的 PHP 运行时来处理——对于那些完全不需要 PHP 的静态资源来说,这造成了大量不必要的内存和 CPU 开销。在高并发场景下,问题尤为明显。
PHP-FPM 正是为了解决这一效率问题而诞生的。它将 PHP 解释器从 Web 服务器中剥离出来,作为独立的进程池运行。Web 服务器只在需要执行 PHP 代码时才与 PHP-FPM 通信,大幅降低了整体资源消耗。
但更高效的同时也带来了一个新问题:PHP-FPM 本身只负责执行 PHP,静态内容仍然需要一个 Web 服务器来处理。这正是我们引入"反向代理"的原因。
反向代理是一种根据请求内容来决定如何路由流量的服务器。它位于客户端和后端服务之间,充当"智能调度员"的角色。
更进一步说,一个 Web 服务器可以同时扮演反向代理和静态文件服务器两个角色(这正是我们使用 NGINX 的方式)。Apache 同样可以这样配置——我们的 php:*-fpm-apache 镜像就是如此运行的。
如上图所示,Web 请求从顶部进入,首先到达 NGINX(或 Apache),由它来分析和判断请求类型:
静态文件
如果请求的是静态文件(jpeg、js、png、css 等),NGINX 会直接从磁盘返回文件,无需调用 PHP-FPM。由于跳过了 PHP 的整个执行链路,响应速度非常快,资源消耗也极低。这也是为什么在高流量网站中,静态资源的分发效率至关重要。
PHP 文件
如果请求以 .php 结尾,NGINX 会通过 FastCGI 协议将请求转发给 PHP-FPM。PHP-FPM 从其进程池中取出一个空闲 Worker 执行 PHP 代码,完成后将响应结果经由 NGINX 返回给客户端。整个过程对客户端完全透明。
"Docker 最佳实践"确实建议一个容器只运行一个进程。理想状态下确实如此,但现实中往往不太可行。从上面的例子可以看出,我们至少需要两个进程同时运行:
如果为了严格遵守"一容器一进程"的原则而将它们拆分到两个容器中,你就需要处理容器间网络通信、服务发现、端口映射等额外复杂度——对于中小型项目来说,这往往是得不偿失的。
如果你希望在一个容器中完整运行应用,又不想引入多台服务器或多容器编排带来的复杂性,那就需要像 S6 Overlay 这样的工具来妥善管理进程启动、监督和退出,并确保健康状态能够被准确上报。
S6 Overlay 的设计理念与 PHP 的运行需求高度契合:
一个容器应该只做一件事(这件事可以由多个进程共同完成)。当这件事停止时,容器也应该随之停止。
换句话说,容器的边界应该由"功能"来定义,而不是由"进程数量"来定义。
使用 S6 Overlay 来运行 PHP 具有以下优势:
可能与部分 PaaS 平台不兼容,具体取决于其容器运行方式。某些平台会接管容器的 init 进程或使用自己的进程管理方案,这与 S6 Overlay 的工作方式存在冲突(详见 S6 Overlay 作者的说明)。如果你的部署平台有此类限制,需要提前确认兼容性。
Supervisor 是容器化时代之前非常流行的进程管理方案,至今仍有很多人在使用。它本身是一个优秀的工具,但在容器场景下存在一些设计上的局限。以下几点或许能帮助你理解为什么值得考虑换用 S6 Overlay:
在容器中启动 Supervisord 时,它会被分配为 PID 1,再由它拉起各子进程。
当子进程(比如 PHP-FPM)发生故障时,Supervisor 可以尝试重启来恢复。但问题在于:容器编排系统(如 Docker、Kubernetes)是通过 PID 1 来判断容器是否存活的。由于占据 PID 1 的 supervisord 本身并未挂掉,编排系统会认为容器一切正常,即使实际的业务进程已经处于故障状态。
👉 这种设计会导致容器在故障期间,健康状态上报失真,可能导致流量继续被路由到一个实际上已经无法正常服务的容器上。
S6 Overlay 天生就是为容器环境设计的。当它发现被监督的关键进程无法恢复时,S6 Overlay 会让整个容器退出,返回一个非零退出码。容器编排系统能够立刻感知到这一点,并做出相应处理——比如重启容器、将流量切换到健康实例等。
它同样支持故障恢复(可配置重试次数和间隔),但在判断"是否应该放弃恢复、直接退出"这一点上,比 Supervisor 更为果断和准确。
👍 S6 Overlay 在设计上能够准确检测故障并直接退出——这正是应用出现故障时我们期望的行为。
由于 S6 Overlay 围绕容器化理念打造,它还为容器启动阶段的自定义操作提供了精细的时序控制能力。整个启动流程是分阶段、有依赖关系的,而不是简单地"把所有东西一股脑启动"。
S6 Overlay 提供了丰富的选项来编写自定义服务脚本,并精确编排各步骤的执行顺序。你可以定义脚本之间的依赖关系、执行条件、超时时间等。
以上图为例:
runas-user 脚本用于自定义文件权限的 UID 和 GID,确保容器内进程以正确的用户身份运行,避免常见的权限问题laravel-automations 脚本会检查并执行待运行的自动化迁移任务(如数据库 migration、缓存清理等)这两个脚本都必须在主进程 php-fpm 启动之前成功完成——因为 php-fpm 将它们都声明为自己的依赖项。只有当所有前置条件都满足后,S6 Overlay 才会启动 PHP-FPM 服务。
这种机制在自定义配置方面非常强大,让你能够完全掌控应用的启动行为。无论是执行数据库迁移、预热缓存、检查外部服务连通性,还是注入运行时配置,都可以通过编写 S6 服务脚本来实现,且能保证它们在正确的时机被执行。