Docker 使用数组入口后,Ctrl-C 仍无法结束 PID 1 服务
Linux 容器里的 PID 1 未设置相应信号处理时,不能依赖普通进程的默认退出行为。区分前台连接与日志跟随,用 init 验证,并单独检查业务清理和停止信号。
同一个服务程序在宿主机上按 Ctrl-C 会退出,放进 Linux 容器后却只在终端显示 ^C,进程继续运行。Dockerfile 已经使用数组形式的 ENTRYPOINT,启动时也加了 -it,仍没有改善。
一种需要单独检查的情况是:程序直接成为容器 PID 命名空间中的 PID 1,却没有安装相应的信号处理器。Linux 对 PID 1 的处理与普通进程不同,不能依赖普通进程收到 SIGINT、SIGTERM 后默认终止的行为。数组形式入口避免了额外的启动 shell,但不会替应用添加信号处理。
本文适用于使用独立 PID 命名空间的 Linux 容器,以及直接运行单个服务程序的场景。Windows 容器、共享宿主机 PID 命名空间、复杂进程管理器或多层启动脚本需要另查其信号路径。
确认 Ctrl-C 操作的是容器前台
先区分当前终端在做什么。docker run -it 正在前台运行容器,或 docker attach 连接到容器主进程,才是这里讨论的交互场景。对 docker logs -f 按 Ctrl-C 只是结束日志跟随;在 docker exec -it 打开的另一个 shell 中按 Ctrl-C,也不能据此判断主服务如何处理信号。
对于 docker attach,检查是否关闭了默认启用的 --sig-proxy。终端显示 ^C 本身不能证明应用执行了退出处理器。需要结合应用收到信号的记录、进程是否退出判断。
再检查启动方式。下面的 signal-check 是待检查的测试容器名:
docker inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' signal-check
docker inspect --format '{{json .HostConfig.Init}} {{json .HostConfig.PidMode}}' signal-check
这些字段用来确认入口、init 设置和 PID 模式,不能单凭它们证明应用的实际 PID。如果程序能记录启动 PID,检查其在容器内部看到的值;也可使用镜像已有的进程查看工具。不要把宿主机上的进程号当成容器内部的 PID。
若入口经过 shell 脚本,先确认真正运行服务的进程及信号转发关系。那可能是脚本未转发信号的问题,并非本篇“应用本身就是 PID 1、未设处理器”的条件。
为测试容器添加 init 进程
支持 --init 的 Docker 可以在容器内增加一个 init 进程,让它承担 PID 1 的职责,应用作为其子进程运行。Docker 自带的默认 init 基于 Tini,负责转发信号及回收退出的子进程;内置支持自 Docker 1.13 起提供。
在隔离测试环境用原镜像创建一个新容器,保留应用所需配置,仅增加 --init。下面的镜像名是占位值,需要换成已经构建好的测试镜像;不要同时连接生产数据或让测试实例消费真实任务:
docker run --init --name signal-check -it demo-server:check
如果这个名字已被占用,改用新的测试容器名,不要为了复用名称删除正在运行的业务容器。新容器启动后,在这个前台终端按 Ctrl-C,观察程序是否退出。可在另一终端检查状态:
docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' signal-check
容器应从运行状态转为退出状态。退出码需要按应用的信号和退出约定解释,不能把非零码一律视为修复失败。这里刻意不使用 --rm,让容器退出后仍能查询状态。
--init 改变的是创建容器时的进程组织方式,重启原来的容器不会自动补上这个选项。正式调整时应更新实际部署配置,按既有流程重新创建容器;保留原配置、镜像版本及持久数据备份。回退需要按原配置重新创建,不要通过清空卷或全局清理镜像处理。
能退出与完成业务清理是两件事
init 可以使普通子进程的默认信号行为恢复正常,但不能替应用保存尚未写入的数据、完成在途请求或关闭连接。服务需要这些动作时,应使用语言或框架提供的信号处理机制,明确接收停止请求后如何退出,并记录完成状态。
Ctrl-C 通常涉及 SIGINT;docker stop 默认先发送 SIGTERM,等待宽限时间后仍未退出才发送 SIGKILL。镜像的 STOPSIGNAL 或创建容器时的 --stop-signal 可以改变停止信号。因此,Ctrl-C 验证通过后,还要在隔离环境单独验证实际部署的停止方式,不能认为应用处理了 SIGINT 就一定处理了 SIGTERM。
如果应用已经收到停止信号却仍等待,应继续检查它的退出逻辑与正在处理的工作。单纯增加 init 或延长等待时间,不会修复应用自身无法结束的问题。验收时既要确认进程退出,也要检查业务清理结果;不要把强制终止后的消失称作优雅退出。


