十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PID耗尽之谜彻底破解:tini僵尸进程收割原理图文详解

PID耗尽之谜彻底破解:tini僵尸进程收割原理图文详解 PID耗尽之谜彻底破解tini僵尸进程收割原理图文详解【免费下载链接】tiniA tiny but valid init for containers项目地址: https://gitcode.com/gh_mirrors/ti/tini容器跑着跑着突然起不来、系统报 no available ptty 或进程创建失败十有八九是PID 耗尽罪魁祸首正是容器里无人认领的僵尸进程。而 tini 就是一个体积极小的容器 init 进程专门负责两件事收割僵尸进程和转发信号。本文用大白话带你彻底搞懂 tini 的僵尸进程收割原理并教你 3 步在容器里用上它。一、PID 是怎么被吃光的先记住两个 Linux 冷知识概念一句话解释后果僵尸进程Zombie进程死了但退出码还留在内核里等父进程来取占用一个 PID 不释放PID 1 的特殊待遇容器里的 1 号进程通常是你的业务程序本身大多数业务程序不会去收尸 死亡链条是这样的子进程退出 → 变成僵尸等待父进程调用wait类系统调用领取退出状态父进程自己先死 → 僵尸被重新挂靠re-parent给 PID 1PID 1比如 nginx、Java 应用根本不去收这些别人家的僵尸僵尸越积越多直到 PID 池/proc/sys/kernel/pid_max耗尽 →所有新进程都无法创建容器假死。这就是 README.md 开头就警告的风险starve your entire system for PIDs (and make it unusable)。二、tini 是怎么接住这些僵尸的tini 的解法极其朴素让自己当容器里的 1 号进程然后干三件事—— 核心思路让业务程序成为 tini 的唯一直接子进程僵尸 re-parent 后自然落到 tini 头上由 tini 统一收割。对应 src/tini.c 的main主循环L672-L687它就是一个永动的两步循环┌──────────────────────────────────────────┐ │ tini 主循环while(1) │ │ │ │ ① wait_and_forward_signal │ │ 等信号 → 原样转发给子进程 │ │ ↓ │ │ ② reap_zombies │ │ 非阻塞地收割所有僵尸进程 │ │ ↓ │ │ 子进程已退出→ 带着子进程退出码退出 │ └──────────────────────────────────────────┘收割动作本体reap_zombies真正收尸的代码在 reap_zombies 函数核心只有几行循环调用waitpid(-1, status, WNOHANG)——参数-1表示任何子进程WNOHANG表示取不到就立刻走人别阻塞取到一个就检查是不是主业务子进程是 → 记录它的退出码不是 → 只是个顺路收掉的僵尸继续循环直到ECHILD没有子进程可等才停下。这样即使你的业务程序是僵尸制造机所有孤儿僵尸也都会被 tini 在下一轮循环里清空PID 得以循环复用。✅信号转发收割之外的另一半tini 还顺手解决了另一个经典坑PID 1 会直接忽略SIGTERM等信号导致docker stop卡 10 秒强杀。wait_and_forward_signal 函数 做的事是把 tini 收到的SIGTERM、SIGINT等信号原样转发给业务子进程SIGCHLD除外它只用来触发收尸。配合-g参数时信号会发给整个进程组就像你在终端按 Ctrl-C 的体验。 信号配置逻辑可参考 configure_signals 函数。三、不是 PID 1 也能收subreaper 模式有些场景如嵌套容器、调试tini 抢不到 PID 1。Linux 3.4 提供了child subreaper机制通过prctl(PR_SET_CHILD_SUBREAPER, 1)向内核登记见 register_subreaper 函数之后整个子树里的孤儿进程都会优先 re-parent 给 tini而不是 PID 1。开启方式二选一方式用法命令行参数tini -s -- 你的程序环境变量export TINI_SUBREAPER贴心细节如果 tini 发现自己既不是 PID 1 又没注册 subreaper会主动打印警告reaper_check 函数提示你收割可能失效。四、3 步在容器里用上 tini第 1 步如果你用 Docker ≥ 1.13什么都不用装直接加参数docker run --init 你的镜像--init启用的就是 tini。第 2 步其他场景把 tini 写成 entrypointREADME.md 推荐写法ENTRYPOINT [/tini, --] CMD [/your/program, -and, -its, arguments]注意--之后才是你程序的参数tini 自己的参数如-v写在--之前。第 3 步按需微调常用参数速查参数作用适用场景-s注册为 subreapertini 不是 PID 1 时-g信号发给整个进程组前台 Ctrl-C 不生效时-e 143把退出码 143 映射为 0Java 应用被 SIGTERM 时-p SIGKILL父进程死亡时自杀宿主机上调试容器-w每次收尸都打印警告排查僵尸来源Alpine 用户一条命令即可apk add --no-cache tinitini 位于/sbin/tini。五、眼见为实项目自带的收割测试不信能收干净看仓库里的官方测试 test/reaping/stage_2.py它会一口气fork出100 个短命sleep子进程全部变成僵尸然后由 test/reaping/stage_1.py 轮询 PID 1 的子进程列表断言除预期进程外一个都不剩、无任何僵尸残留。整个测试套件由 run_tests.sh 驱动内部用例见 test/run_inner_tests.py。你可以放心收割逻辑是被 100 个僵尸同时压力验证过的。六、总结一张图记住 tini防 PID 耗尽作为 PID 1或 subreaper持续waitpid收割僵尸PID 池永不枯竭信号不丢失SIGTERM/SIGINT等被可靠转发容器优雅退出小到无感单文件实现全部逻辑就在 src/tini.c动态链接版仅 10KB 量级构建脚本见 CMakeLists.txt✅完全透明原有镜像不改一行也能无缝接入。 一句话行动建议凡是业务程序直接当 PID 1的容器都建议加上 tini。这是成本最低、收益最确定的容器稳定性加固手段。【免费下载链接】tiniA tiny but valid init for containers项目地址: https://gitcode.com/gh_mirrors/ti/tini创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表