可以把 s6-overlay 理解成"装在 Docker 容器里的轻量级管家":
- 容器启动时,先帮你做准备工作。
- 把需要运行的程序启动起来。
- 程序意外退出时,重新拉起。
- 容器停止时,安排这些程序退出。
- 顺便处理 Linux 进程管理中的一些细节。
它不是容器编排工具,也不是完整的 systemd,而是专门适合容器的一套初始化和进程监管方案。
下面以 s6-overlay v3 为例讲解。
一、为什么会需要它?
1. Docker 不会自动管理容器里的所有程序
例如:
dockerfile
CMD ["nginx", "-g", "daemon off;"]
容器启动后,Nginx 就是容器里的主进程,通常也是 PID 1。
只有一个服务时,这往往已经够了。
但如果你需要同时运行:
text
Nginx
定时任务 crond
后台 worker
直接写:
sh
nginx &
crond &
worker &
wait
虽然可能跑起来,但还有很多问题:
- worker 崩溃后,谁负责重启?
- Docker 发来停止信号,谁负责通知其他进程?
- 如何保证数据库初始化完成后再启动应用?
- 某些子进程退出后,谁负责回收?
- 如何控制整个容器何时退出?
这就是 s6-overlay 要解决的问题。
2. PID 1 不是普通进程
可以把 PID 1 理解成容器里的"家长"。
它除了运行自己的逻辑,还涉及:
- 接收容器停止信号。
- 管理关闭流程。
- 收养并回收孤儿进程,避免它们退出后留下僵尸进程。
这里要注意:PID 1 不能替一个仍然活着、却不回收自己子进程的程序解决所有僵尸进程问题。
普通应用未必适合承担这些职责,而 s6-overlay 就是为这些场景设计的。
二、它的工作原理
1. 三个名字分别是什么?
| 名称 | 通俗解释 |
|---|---|
| s6 | 底层进程监管工具:盯着程序,退出后可以重新启动 |
| s6-rc | 服务管理工具:管理服务之间的依赖和启停 |
| s6-overlay | 把这些工具包装成适合容器使用的一套启动、初始化、关闭方案 |
因此,不必把 s6-overlay 理解成一个神奇的大程序。
它更像一套"工具 + 目录约定 + 启动流程"。
2. 启动流程
使用它时,Docker 的入口改成:
dockerfile
ENTRYPOINT ["/init"]
大致启动过程是:
text
Docker 启动容器
│
▼
/init
│
├── 准备运行环境
├── 执行初始化任务
├── 建立进程监管体系
└── 启动服务
进入正常运行状态后,可以简化理解为:
text
PID 1:s6-svscan
│
├── s6-supervise nginx
│ └── nginx
│
└── s6-supervise cron
└── crond
实际还会有内部管理进程,这里省略了。
关键点是:
每个被监管的服务,通常都有一个
s6-supervise专门盯着它。
如果服务进程退出,监管器通常会再次启动它。
这与 Docker 的重启策略不同:
text
s6-overlay:重启容器内部的服务进程
Docker:重启整个容器
两者不在同一个层级。
3. 停止流程
执行:
bash
docker stop demo
大致会发生:
text
Docker 发出停止信号
│
▼
s6-overlay 开始关闭流程
│
├── 停止服务
├── 等待服务退出
├── 执行收尾任务
└── 结束容器
如果超过 Docker 的停止等待时间,Docker 仍然可能强制杀掉容器。
所以,有较长清理流程时,要给足时间:
bash
docker stop -t 30 demo
三、最快上手:同时运行 Nginx 和 cron
s6-overlay v3 支持两种常见配置方式:
/etc/services.d:简单直观,兼容式配置。/etc/s6-overlay/s6-rc.d:支持依赖关系的原生配置。
先用第一种理解基本用法。
1. 目录结构
text
demo/
├── Dockerfile
└── rootfs/
└── etc/
├── nginx/
│ └── nginx.conf
└── services.d/
├── nginx/
│ └── run
└── cron/
└── run
这里的核心约定是:
/etc/services.d/服务名/run用来描述"这个服务怎么启动"。
2. Dockerfile
下面固定使用一个 v3 版本演示,架构为 x86_64 / amd64:
dockerfile
FROM alpine:3.21
ARG S6_OVERLAY_VERSION=3.2.0.2
RUN apk add --no-cache nginx xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp/s6-overlay-x86_64.tar.xz
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz \
&& tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz \
&& rm /tmp/s6-overlay-*.tar.xz
COPY rootfs/ /
RUN chmod +x \
/etc/services.d/nginx/run \
/etc/services.d/cron/run
EXPOSE 80
ENTRYPOINT ["/init"]
这里安装了两份文件:
noarch:与 CPU 架构无关的部分。x86_64:对应 CPU 架构的可执行程序。
ARM64 环境需要使用 aarch64 包,不能直接使用 x86_64。
生产环境还应固定版本,并校验下载文件的校验和。
3. Nginx 启动脚本
rootfs/etc/services.d/nginx/run:
sh
#!/command/with-contenv sh
exec nginx -g 'daemon off;'
重点有三个:
with-contenv
用于把容器环境变量导入脚本的运行环境,方便读取:
bash
docker run -e APP_ENV=production ...
传入的变量。
exec
把当前 shell 替换成 Nginx,让监管器直接监管真正的服务进程。
daemon off
让 Nginx 在前台运行。
被 s6 监管的服务通常必须以前台模式运行,不能自行放到后台。
4. cron 启动脚本
rootfs/etc/services.d/cron/run:
sh
#!/command/with-contenv sh
exec crond -f -l 2
这里使用 Alpine 自带的 BusyBox crond:
-f:前台运行。-l 2:日志级别。
这只是启动 cron 守护进程;具体定时任务需要另外配置。
5. Nginx 配置
rootfs/etc/nginx/nginx.conf:
nginx
worker_processes 1;
error_log /dev/stderr info;
pid /run/nginx.pid;
events {
worker_connections 128;
}
http {
access_log /dev/stdout;
server {
listen 80;
location / {
default_type text/plain;
return 200 "Hello from s6-overlay!\n";
}
}
}
日志写到标准输出和标准错误,就能通过 Docker 查看。
6. 构建与运行
bash
docker build -t s6-demo .
docker run -d --name s6-demo -p 8080:80 s6-demo
访问:
bash
curl http://localhost:8080
输出:
text
Hello from s6-overlay!
查看日志:
bash
docker logs -f s6-demo
停止:
bash
docker stop s6-demo
此时 Nginx 和 cron 都由 s6-overlay 管理。
四、启动前要做初始化,怎么办?
简单场景可以使用:
text
/etc/cont-init.d/
例如:
text
rootfs/etc/cont-init.d/10-prepare
内容:
sh
#!/command/with-contenv sh
set -eu
mkdir -p /data/cache
echo "初始化完成"
别忘了添加执行权限:
dockerfile
RUN chmod +x /etc/cont-init.d/10-prepare
这类脚本适合:
- 创建目录。
- 调整文件权限。
- 根据环境变量生成配置。
- 执行一次性的准备工作。
在前面的简单配置模式中,可以把流程理解为:
text
初始化脚本执行完成
↓
启动长期运行的服务
不要把一个永不退出的后台服务放进初始化脚本,否则启动流程可能一直卡在那里。
五、有依赖关系时,用 s6-rc 原生配置
如果你需要:
text
准备目录
↓
生成配置
↓
启动应用
建议使用:
text
/etc/s6-overlay/s6-rc.d/
1. 两种基本服务类型
| 类型 | 用途 |
|---|---|
oneshot |
执行一次就结束,例如创建目录、生成配置 |
longrun |
持续运行,例如 Nginx、worker |
2. 一个最小的 Nginx 原生配置
目录如下:
text
/etc/s6-overlay/s6-rc.d/
├── nginx/
│ ├── type
│ ├── run
│ └── dependencies.d/
│ └── base
└── user/
└── contents.d/
└── nginx
各文件含义:
nginx/type
text
longrun
nginx/run
sh
#!/command/with-contenv sh
exec nginx -g 'daemon off;'
这个文件需要可执行权限。
nginx/dependencies.d/base
空文件,表示依赖 s6-overlay 的基础服务集合。
user/contents.d/nginx
空文件,表示把 Nginx 加入默认启动的用户服务集合。
可以简单记成:
text
type 这个服务是什么类型
run 服务怎么启动
dependencies.d/ 服务依赖谁
user/contents.d/ 哪些服务默认启动
如果改用这个配置,删除之前的 /etc/services.d/nginx,不要把同一个服务注册两遍。
3. "先启动"不等于"已经可用"
这是非常重要的区别。
例如:
text
数据库进程已经启动
不代表:
text
数据库已经能接受连接
依赖一个没有配置就绪通知的 longrun 服务,不能自动得到"业务已经准备好"的保证。
需要明确的就绪通知,或者额外的检查任务,才能实现真正的:
text
数据库可用 → 启动应用
六、最容易踩的坑
1. 不要在 run 里加 &
错误:
sh
nginx &
脚本马上结束,监管器可能认为服务已经退出,不断重新启动。
正确:
sh
exec nginx -g 'daemon off;'
2. 不要写成"启动后立即返回"的命令
例如:
sh
service nginx start
这种传统服务启动命令通常会把程序放到后台,不适合直接拿来监管。
应直接运行实际程序,并使用前台模式。
3. 服务退出,不代表容器退出
默认情况下,一个被监管服务退出,通常会被重新启动,而不是立刻让整个容器结束。
如果你的要求是:
核心应用一旦失败,整个容器就必须失败退出。
需要额外配置退出策略,不能只依赖默认监管行为。
同样,s6-overlay 发现的是进程退出,不是所有业务故障。程序卡死、接口持续报错等问题,通常还需要健康检查。
4. 管理多个服务,不代表必须都用 root
可以在启动服务时降权,例如:
sh
#!/command/with-contenv sh
exec s6-setuidgid app /app/server
前提是镜像中已经创建 app 用户,并设置好目录权限。
5. 不要混用不同大版本的教程
v2、v3 的安装包、目录和部分操作方式有差异。
尤其要注意:
- v3 通常需要同时安装
noarch和架构包。 /etc/services.d是兼容式方式。/etc/s6-overlay/s6-rc.d是支持依赖管理的原生方式。
七、什么时候值得用?
适合
- 一个容器里确实需要运行多个紧密关联的进程。
- 需要启动前初始化。
- 需要内部进程自动重启。
- 需要有序启停和服务依赖。
不一定需要
如果容器只有:
dockerfile
CMD ["python", "app.py"]
而应用已经能正确处理停止信号和子进程,那么未必需要 s6-overlay。
如果只缺少简单的 PID 1 信号处理和孤儿进程回收能力,可以考虑:
bash
docker run --init ...
或 tini。它们比 s6-overlay 更简单,但不提供同等级的多服务监管和依赖管理。
另外,数据库、缓存、Web 应用如果可以独立部署,通常优先考虑用 Docker Compose 或 Kubernetes 拆成不同容器。
一句话总结:s6-overlay 把容器从"只执行一条启动命令",变成"拥有初始化、进程监管和有序关闭能力的小型运行环境"。