docker stats 的 PIDS 列飙高是因为其统计的是 cgroup v2 的 pids.current 值,即容器内所有 Task(含进程与内核线程/TID)总数,而非仅 ps aux 显示的进程数;Java 线程池无上限、Node.js 子进程泄漏、Python multiprocessing 未回收等均会导致 Task 数激增,触达 pids.max 限制后引发 fork failed。

docker stats 的 PIDS 列为什么突然飙高?
当你执行 docker stats,发现某个容器的 PIDS 值远高于 ps aux 显示的进程数(比如 stats 显示 420,而容器内 ps aux | wc -l 只有 12),这不是统计错误,而是 Linux 内核把线程(Thread)和轻量级进程(LWP)都算作独立 Task —— 它们共享同一个 PID namespace,但各自拥有唯一 TID,全部计入 pids.current。
常见诱因包括:
- Java 应用未配置线程池上限,
Executors.newCachedThreadPool()在高并发下持续 fork 新线程 - Node.js 使用
child_process.fork()启动子进程但未回收,或 Pythonmultiprocessing模块未调用join() - 应用日志框架(如 Log4j2、Winston)启用了异步写入且线程数失控
- 容器内运行了未限制并发数的 cron job 或定时扫描脚本
怎么确认是不是 pids_limit 被打满了?
直接查容器的 cgroup v2 接口(Docker Desktop 默认使用 cgroup v2):
docker exec <container_id> cat /sys/fs/cgroup/pids.max</container_id>
如果输出是 max,说明没设限;如果是具体数字(如 512),再查当前用量:
docker exec <container_id> cat /sys/fs/cgroup/pids.current</container_id>
若 pids.current ≥ pids.max,新 fork() 就必然失败 —— 此时连 sh 都可能进不去,docker exec -it 报错 fork/exec /bin/sh: resource temporarily unavailable。
注意:docker stats 的 PIDS 列就是 pids.current 的实时快照,不是估算值。
如何临时绕过并验证问题?
不要重启容器,先尝试在宿主机上动态放宽限制(仅限调试):
- 找到容器在宿主机上的 cgroup 路径:
docker inspect,拿到| grep Pid Pid后执行ls -l /proc/,定位到类似/cgroup 8:pids:/docker/abc123...的行 - 手动写入更大值:
echo 1024 | sudo tee /sys/fs/cgroup/pids/docker/abc123.../pids.max - 立刻再试
docker exec -it—— 如果能进去,且sh ps可以跑,基本锁定是 PID 耗尽
这个操作不持久,容器重启后恢复原设置。别在生产环境长期这么干,只是用来快速验证。
怎么永久修复?
根本解法是限制容器最大 PID 数,并让应用适配它:
- Docker CLI 启动时加参数:
--pids-limit=512 - Docker Compose v2.20+ 支持:
pids_limit: 512(写在 service 下) - 镜像构建阶段,在
ENTRYPOINT脚本里用ulimit -u 512设置RLIMIT_NPROC(对 shell 子进程生效,但无法约束内核线程)
关键点:Java 应用必须显式配置线程池大小(corePoolSize/maxPoolSize),Node.js 要避免无节制 fork(),Python 多进程要控制 processes= 参数 —— 单纯调大 pids_limit 只是掩盖泄漏,不是解决泄漏。
最容易被忽略的是:Java 的 ForkJoinPool.commonPool() 默认线程数 = CPU 核心数,但在容器里它读的是宿主机核数,不是 --cpus 限制值。必须用 -Djava.util.concurrent.ForkJoinPool.common.parallelism=4 手动压低。











