Docker 进程不多,exec 仍失败:检查线程是否用满 pids_limit
少量进程也可能因大量线程耗尽任务额度。对照cgroup当前值、上限和事件,定位线程来源,再调整并发或受控容量。
容器仍在运行,进程列表看着只有几个服务,docker exec 却无法启动,或应用创建线程时报 Resource temporarily unavailable。如果配置了 pids_limit,不能只数普通 ps 输出的进程行数:Linux PID 控制器按内核任务编号统计,线程同样占用名额。
以下针对 Docker 的 Linux 容器与实际生效的 pids 控制器。这个错误还可能来自进程用户限额、内存不足等条件,需要结合限制文件和失败时段判断;不是所有 exec 失败都应提高上限。
一个进程可以占用很多任务名额
多线程应用虽然只占 ps 中一行,其线程会计入 PID 控制器。多个 worker、线程池和子进程共同使用容器所在 cgroup 的额度,达到有效上限后,新的 fork 或 clone 可能返回 EAGAIN。exec 本身也要创建新进程,所以“还能运行已有业务,但不能再进一个 shell”并不矛盾。
256 这样的上限可能来自某个应用或部署平台,不是所有 Docker 容器的统一默认值。CPU 配额也不会自动缩减应用线程池,不能用 --cpus=1 推断线程一定很少;这一点可结合CPU 配额与处理器枚举检查。
exec 已失败时,从 daemon 所在主机读取
先查看目标容器保存的配置和状态,worker-app 为待替换名称:
docker inspect worker-app --format '{{.HostConfig.PidsLimit}}'
docker inspect worker-app --format '{{.State.Pid}}'
第一项是配置值,不足以证明内核有效上限。第二项是 daemon 一侧看到的容器主进程编号,也会随重启变化。原生 Linux 宿主机上,可由已有授权的运维入口读取这个进程的 /proc/进程号/cgroup,据实际 cgroup 版本、挂载点和路径找到对应限制文件。
不要把 macOS 上的 /proc、另一个服务器的路径或容器内的 PID 原样用于这个检查。Docker Desktop、OrbStack 等环境的 Linux 容器运行在相应虚拟环境中,需在那里取得可用的诊断数据;编号视角差异可另看docker top 与容器内 ps。
对已确认的 cgroup v2 目标,读取 pids.current、pids.max 和 pids.events。current 包含这个 cgroup 及其后代的当前任务;max 表示该层上限,值为 max 表示这层没有数值限制。上级 cgroup 仍可能有更紧的限制,不能只查容器这一层。
把故障前后快照与应用时间对应起来:当前值逼近有效上限,同时 events 中的 max 计数在失败时增长,是有用的限制证据。计数的层级范围还受挂载选项影响,应一起核对相关上级与本层记录;一份过去累积的非零计数不能证明本次错误就是它造成的。
降低上限或迁移已有任务等操作可能使 current 大于 max,并不表示新的 fork 可以绕过限制。也不要反复开新 shell 取证,满额时检查入口本身就可能失败。
找出线程来源,再决定容量
利用已有应用监控或宿主机允许的线程查看工具,确认增长来自正常并发、过大的线程池、持续产生的子进程,还是任务未能回收。记录时避免公开带有凭据的进程参数。需要观察多个时点,单个低负载快照不代表业务峰值。
应用线程池或并发确实过大时,按它支持的配置收紧范围,并在代表性任务下验证。持续泄漏任务时应修生命周期管理,单纯抬高上限只会推迟下一次耗尽。不要随机强杀 worker 或重启整个 Docker daemon 来释放名额。
如果确认正常业务需要更多任务,应评估宿主机容量、上级限制和其他服务,再选定有边界的新限额,保存原配置,通过既有部署流程实施。不要把 pids_limit 统一改为无限,也不要只修改运行中容器而遗漏下一次部署会恢复的声明配置。
若控制器并未生效,例如 rootless 环境缺少必要的委托,先处理Driver 为 none 的资源限制条件,不能把“参数存在”当作这次达到上限的证据。
在正常负载下观察恢复
验收应包括原来失败的业务线程或子进程能创建、exec 恢复,以及代表性负载下 current 留有合理余量,相关限制事件不再随同类失败持续增加。只看重启后一次 exec 成功,不能证明任务增长已解决。
回退时恢复保存的应用并发与部署限额,并重新核对上级限制。降低上限前确认当前任务数量和业务影响,不能为了恢复旧数字让新任务立即无法创建;已中断的工作则需按应用自己的重试或恢复流程处理。


