服务跑着跑着报 Too many open files,怎么确认是 fd 泄漏、泄漏的是什么、从容器外怎么查?这篇用故意泄漏文件和 socket 的程序复现,并给出不装 lsof 的排查办法。
--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)。一个容器泄漏会占用所有容器共享的额度;容器删除后句柄被回收。这里的变化里还混有虚拟机里别的活动,所以只能看趋势。
日志:连了 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 即可。