mysql容器默认无法优雅退出,因shell启动方式使mysqld非pid 1且不转发sigterm;须改用exec格式cmd、显式stopsignal,并推荐tini作为init进程确保信号转发与僵尸回收。

MySQL 官方镜像默认不响应 SIGTERM,直接 kill -9 是常态——除非你手动干预信号处理链路。
MySQL容器为什么收不到SIGTERM?
官方 mysql:8.0 镜像启动时用的是 shell 形式(如 CMD ["mysqld"]),实际 PID 1 是 shell 进程,而真正 mysqld 是子进程。SIGTERM 发给 shell 后,shell 不会自动转发给 mysqld,导致信号“消失”。日志里看不到 received SIGTERM,docker ps -a 显示退出码 137(即被 SIGKILL 终止),就是典型症状。
- mysqld 本身支持
SIGTERM:收到后会执行正常关机流程(刷盘、关闭连接、写 shutdown marker) - 但前提是它得是 PID 1,或有进程能可靠转发信号
- shell 启动方式天然阻断信号传递,这是最常被忽略的底层原因
必须用 exec 模式启动 + 显式声明 STOPSIGNAL
在 Dockerfile 中不能写 CMD mysqld(shell 形式),必须用 exec 格式,并配合 STOPSIGNAL 声明:
FROM mysql:8.0 STOPSIGNAL SIGTERM CMD ["mysqld", "--innodb-flush-log-at-trx-commit=1"]
关键点:
-
STOPSIGNAL SIGTERM告诉 Docker:停机时发 SIGTERM,不是默认的 SIGTERM(虽默认一致,但显式声明可避免某些旧版 daemon 解析歧义) -
CMD ["mysqld", ...]是 exec 格式,确保 mysqld 直接作为 PID 1 运行,能原生接收信号 - 加
--innodb-flush-log-at-trx-commit=1是为了强化事务安全性,与信号无关但影响停机数据一致性
生产环境强烈建议加 dumb-init 或 tini
即使用了 exec 模式,mysqld 作为 PID 1 仍存在两个隐患:一是无法 reap 僵尸子进程;二是某些系统调用(如 waitpid)行为异常。更稳妥的做法是引入轻量 init 进程:
- 在
Dockerfile中安装tini:RUN apt-get update && apt-get install -y tini - 改 CMD 为:
ENTRYPOINT ["/sbin/tini", "--"],再CMD ["mysqld", "--innodb-flush-log-at-trx-commit=1"] -
tini会作为 PID 1 接收 SIGTERM,并原样转发给 mysqld,同时负责僵尸进程回收
不用 docker run --init:该参数在 compose 场景下不生效,且无法保证所有运行时环境都启用。
验证是否真正优雅退出
别只看命令是否返回,重点检查三处:
- 停机后执行
docker logs <container> | tail -20</container>,应看到类似Shutting down the server、mysqld: Shutdown complete的日志行 - 运行
docker inspect <container> | jq '.State.ExitCode'</container>,正常退出应为0;若为137,说明触发了 SIGKILL - 进容器查数据目录(如
/var/lib/mysql/ib_logfile0),关机前后的文件大小应一致——突变说明刷盘未完成
真正棘手的不是“怎么让 MySQL 停”,而是“怎么确认它停得干净”:日志可能骗人,退出码可能被 wrapper 掩盖,只有数据状态和磁盘行为不会说谎。











