bigywater.tech
Docker · 97

文件描述符泄漏:容器里怎么发现

服务跑着跑着报 Too many open files,怎么确认是 fd 泄漏、泄漏的是什么、从容器外怎么查?这篇用故意泄漏文件和 socket 的程序复现,并给出不装 lsof 的排查办法。

第 198 次
nofile=200 下泄漏程序 open 失败的次数(0、1、2 号标准流占去 3 个)
60 个 CLOSE_WAIT
客户端连了 60 个、服务端关闭后客户端不 close,容器里 /proc/net/tcp 的 CLOSE_WAIT 数
约 94 个/秒
无上限时 fd 泄漏的速度(每 0.01 秒开一个)

到上限

实测 · 本机执行输出
  --ulimit nofile=200:200,普通写法的泄漏程序:容器状态 exited ExitCode=1;日志:第 198 次 open 失败:[Errno 24] No file descriptors available: '/etc/hostname' | Traceback (most recent call last): |   File "/leak.py", line 26, in <module> | OSError: [Errno 24] No file descriptors available: '/proc/self/fd'
  同样的上限,出错后先关掉 20 个再汇报的写法:日志:第 198 次 open 失败:[Errno 24] No file descriptors available: '/etc/hostname' | fd 数 181
  docker exec 数 /proc/1/fd:180 个(上限 200)
  这些 fd 指向什么(按目标分组):/etc/hostname×177 pipe:×2 /dev/null×1

错误是 [Errno 24] No file descriptors available(EMFILE,有些地方显示 Too many open files)。要小心的是:处理这个错误的代码本身也需要 fd,没留余地时诊断、写日志都会失败,进程可能直接崩掉。

全局影响

实测 · 本机执行输出
  默认 nofile 上限(104 万)下:1 秒时 97 个 fd,3 秒时 286 个 fd(每 0.01 秒开一个,约 94 个/秒)
  虚拟机的 /proc/sys/fs/file-nr(已分配的文件句柄数,全局):3858
  再过 3 秒:4160(增加 302,是整台虚拟机共享的计数)
  删掉泄漏的容器后:3576(回落 584)

泄漏的 fd 对应内核里已分配的文件句柄,/proc/sys/fs/file-nr 是整台机器共享的计数(上限是 fs.file-max,全局参数,容器不能改,见 c90)。一个容器泄漏会占用所有容器共享的额度;容器删除后句柄被回收。这里的变化里还混有虚拟机里别的活动,所以只能看趋势。

socket

实测 · 本机执行输出
  日志:连了 60 个连接,服务端都已关闭,客户端一个都没 close | fd 数 65
  容器里 /proc/net/tcp 各状态的连接数:{'LISTEN': 1, 'FIN_WAIT2': 60, 'CLOSE_WAIT': 60}
  fd 里 socket 的数量:61

对端关闭而本端不 close 的连接停在 CLOSE_WAIT,数量等于泄漏的连接数,是应用忘了关连接的典型迹象。看状态可以直接读 /proc/net/tcp(第 4 列 08 是 CLOSE_WAIT)。

怎么查

实测 · 本机执行输出
  /proc/1/limits 里的 Max open files:Max open files 300 300 files
  /proc/1/fdinfo/<某个 fd> 的内容(位置、标志):pos:	0 flags:	02400000 mnt_id:	2361 ino:	123364
  各 fd 的数量随时间(已在上限附近,不再增加):280

/proc/<pid>/limits 看上限,/proc/<pid>/fd 里每个符号链接指向文件、socket 或 pipe,按目标分组统计就能看出泄漏的是什么(这里 177 个都指向 /etc/hostname);fdinfo 给出位置和标志。容器里没有 lsof 也不影响,docker exec 进去读 /proc 即可。

分步演示

文件描述符泄漏
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。泄漏程序是 python:3.12-alpine 里的小脚本(文件模式每 0.01 秒 open 一次不 close;socket 模式连本地服务端 60 次不 close);上限用 --ulimit nofile 设置;全局计数读自虚拟机的 /proc/sys/fs/file-nr(特权容器加 nsenter,方法见 c19),期间虚拟机里别的活动会让数字有噪声;状态码读自 /proc/net/tcp;实验后已删除容器
    • 没有验证:真实服务(Nginx、Java)的 fd 泄漏、fs.file-max 被耗尽时的全局故障(不做)、epoll 与 inotify 实例的上限(fs.inotify.max_user_instances)、fd 泄漏与线程数的关系、lsof / ss 的输出对照
    京ICP备2026067502号-1京公网安备11010502063487号