ARTICLE DETAIL

资讯详情

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

Docker报错too many open files?文件描述符限制排查与修复

Docker报错too many open files?文件描述符限制排查与修复 今天想聊一个排查了挺久的 Docker 问题报错长这样Accept error: accept unix /run/docker.sock: accept4: too many open files。如果你在用 Docker而且部署环境里并发连接比较多、容器频繁起停或者 CI/CD 任务跑得比较猛大概率会遇到。这个报错乍一看像是 Docker 本身坏了但实际是 Linux 系统对进程打开文件数量做了限制Docker 进程没法再接受新的客户端连接所以不管你是执行docker ps还是docker logs都会卡住或者直接报错。这篇文章我把整个排查思路、根因原理、修复步骤和踩坑过程完整记录下来涉及 ulimit、systemd 的 LimitNOFILE、sysctl 内核参数还有 Docker Desktop 和 WSL2 场景下的特殊情况。不管你是刚接触 Docker 的新手还是被这个问题折磨过的老手按下面的步骤走一遍基本都能解决。1. 问题现象先确认你遇到的和我是同一件事1.1 报错到底长什么样不同的 Docker 版本和操作系统错误信息会有一点差异。比较典型的几种表现执行docker ps、docker logs等命令时直接报Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?/var/log/syslog或者journalctl -u docker里反复出现Accept error: accept unix /run/docker.sock: accept4: too many open files容器运行本身可能没有立刻挂掉但新连接根本进不来客户端命令一直卡住我在排查时先在宿主机上执行systemctl status docker看到的是active (running)服务并没有死掉但日志里已经刷了大量上面的 accept 错误。这种服务还活着但连接不进来的状态最迷惑人容易让人误判为网络问题或者 socket 权限问题。1.2 这个报错的高发场景我整理了一下自己遇到和帮别人排查过的案例有几个典型的触发场景机器上跑了很多容器容器的端口映射很密集同时有大量外部请求进来CI/CD 流水线并发构建镜像短时间内在 Docker socket 上建立大量连接监控类容器频繁调用 Docker API 采集数据比如 cAdvisor、Prometheus 的 Docker exporterDocker Desktop 在 Windows/Mac 上跑了很久WSL2 后端长时间运行文件句柄慢慢涨上去如果你在其中一个场景里而且系统跑了很久一直没重启大概率就是这个限制的问题。关键是要分清这是连接量确实太大还是文件描述符泄漏导致限制被提前打满。2. 根因分析accept4 和文件描述符的故事2.1 Linux 的文件描述符到底是什么理解这个报错之前需要先搞懂一个基础概念文件描述符file descriptor简称 FD。在 Linux 世界里一切皆文件——打开一个文件、建立一个网络连接、监听一个 socket内核都会返回一个非负整数来指向这个对象这个整数就是文件描述符。每个进程能同时打开多少个文件描述符不是无限的。内核给每个进程设了一个上限这就是RLIMIT_NOFILE。当进程调用open()、socket()、accept()等会分配新文件描述符的系统调用时如果已经达到上限内核就会返回EMFILE错误信息就是 too many open files。Docker 的守护进程 dockerd 本身是一个长期运行的进程它要处理大量来自客户端的请求。C/S 之间通过/var/run/docker.sock这个 unix socket 通信每来一个连接dockerd 就需要调用accept4()从监听队列里取出新连接这个新连接同样占用一个文件描述符。一旦 dockerd 的 FD 数量达到上限accept4()就失败日志里就会出现accept4: too many open files。2.2 docker.sock 通信链路中的瓶颈Docker 的体系里存在两个层面需要注意。第一个层面是客户端与 Docker daemon 之间的连接。平时执行docker ps、docker exec就是通过 unix socket 把请求发送给 dockerd/run/docker.sock是守护进程监听的文件。每个请求都会建立连接再断开如果并发请求很多同一时刻会有大量连接处于等待状态逐个占用 FD。第二个层面是 dockerd 与容器之间的通信以及容器内部进程自己的 FD 数量。容器里跑的进程同样受/proc/sys/fs/file-max和容器自身的ulimit -n限制。不过本文这个报错docker.sock路径已经明确指向了第一个层面——dockerd 对外监听 socket 的 accept 环节出了问题。所以第一步应该确认到底是 dockerd 自身 FD 上限太低了还是系统全局的 file-max 被打满又或者是某个连接在短时间内疯狂增长导致 FD 数量被瞬间冲高。2.3 限制从哪来三层限制一层套一层Linux 下文件描述符限制其实分三层很多人只改了一层就以为完事了结果重启后又复现。第一层是系统全局限制由内核参数fs.file-max控制它决定整个操作系统所有进程加起来能打开的最大文件数。查看方式cat /proc/sys/fs/file-max cat /proc/sys/fs/file-nrfile-nr显示三个数字系统当前已分配的文件句柄数、已分配但未使用的句柄数、文件句柄的最大值。如果第一个数字已经非常接近file-max说明全局限制快被打满了。第二层是进程级限制也就是RLIMIT_NOFILE。用户态通过ulimit -n查看每个进程有软限制soft和硬限制hard。软限制是内核实际执行的上限硬限制是软限制能上调到的最高值。普通用户可以把软限制调到硬限制以内但调到超过硬限制需要 root 权限。第三层是 systemd 对服务进程的限制。在 systemd 管理 Docker 的机器上即使你改了/etc/security/limits.conf对 dockerd 也不一定生效因为 systemd 启动服务时会忽略 limits.conf改用 service 单元文件里的LimitNOFILE配置。这就是很多人改了 ulimit 却没用甚至重启后立刻失效的原因。3. 定位思路三步确认问题范围3.1 先看日志别急着改参数遇到问题先别急着把ulimit -n改成 65535先看日志确认报错出现的时间点、频率和触发动作。我在宿主机上执行journalctl -u docker --since 2 hours ago | grep -i too many open files如果日志里大量出现再看一看报错前后有没有其他异常比如容器退出、网络断连、大量 exec 操作。很多时候报错不是孤立的前面可能还有一条container ... failed to start或者Levelerror之类的信息这些线索能帮你判断是全局打满还是进程级打满。另外可以用dmesg看一下内核有没有相关报错dmesg | tail -n 100 | grep -i open files如果内核也报了Too many open files那大概率全局 file-max 也有问题需要往上面两层去查。3.2 检查当前 FD 用量和进程限制确认是 Docker 的问题后用下面几个命令快速定位当前状态# 找到 dockerd 的 PID pgrep -a dockerd # 查看该进程当前打开的 FD 数量 ls /proc/$(pgrep dockerd)/fd | wc -l # 查看该进程当前的 limits cat /proc/$(pgrep dockerd)/limits | grep open files/proc/pid/limits里会显示软限制和硬限制。比如输出Max open files 65535 65535 files说明这个进程的 FD 上限是 65535如果当前已经打开了 65000 个那就很明显了。再检查系统全局用量cat /proc/sys/fs/file-nr假如输出2097152 0 2097152说明系统总共 2097152 个句柄已经用完了这种情况下不只是 Docker 受影响整个系统几乎所有需要新建连接的服务都可能出问题。还有一个重要的指标是监听队列长度。Docker socket 上堆积的连接数可以用ss查看ss -lx | grep docker.sock如果输出里Recv-Q很大说明有大量连接在排队等 accept和accept4: too many open files是吻合的。3.3 判断是不够用还是泄漏这是整个排查里最有价值的一步。我的经验是先把当前的 FD 数量记下来过 5 分钟再看一次。如果数量持续上涨但容器数量没有变化那大概率是某个组件在反复建立连接而没有释放也就是我们常说的 FD 泄漏。比如我遇到过一台机器docker 本身没有大量容器但 Prometheus 的 node-exporter 和 docker-exporter 每 10 秒采集一次采集逻辑写得不好每次请求都不关闭响应体导致 dockerd 上的连接越积越多最后把 FD 打满。这种情况下单纯调大限制只是拖延问题过几天又会打满。可以用下面命令找出具体是哪个进程在频繁和 docker socket 通信ss -xp | grep docker.sock这个命令会列出 unix socket 的连接信息以及对应的进程 PID。如果看到很多 ESTAB 状态的连接都来自同一个 PID那问题基本就锁定在那个进程上了。4. 解决方案从临时到持久按层修复4.1 紧急处理先恢复服务如果服务已经连不上了第一要务是恢复可用而不是分析半天。最快的办法是把系统所有进程的 FD 上限临时调大然后重启 Docker。临时调整 dockerd 的限制最常见的办法是在 shell 里执行ulimit -n 1048576然后手动启动 dockerd。但是生产环境一般用 systemd 管理直接改 limits 文件更可靠。如果是通过 systemd 启动的服务先临时把服务的 limit 调大并重启systemctl edit docker在打开的 override 文件里加入[Service] LimitNOFILE1048576然后重载并重启systemctl daemon-reload systemctl restart docker重启后 Docker 服务会立刻恢复这时候再执行docker ps应该就正常了。这属于临时止血但系统重启后是否保留取决于 systemd 配置是否生效所以后面还要做持久化。4.2 持久化方案systemd 是优先级最高的配置项如果你的 Docker 是用 systemd 管理的Ubuntu 16.04、CentOS 7 基本都是/etc/security/limits.conf对服务进程基本无效必须用 systemd 的 service override 配置。我的建议是直接创建一个独立的 override 文件不加#注释掉默认配置mkdir -p /etc/systemd/system/docker.service.d cat /etc/systemd/system/docker.service.d/limits.conf EOF [Service] LimitNOFILE1048576 LimitNPROC1048576 EOF systemctl daemon-reload systemctl restart docker这里LimitNOFILE设置的是最大打开文件数LimitNPROC是最大进程数。为什么我建议 1048576因为这是很多高并发环境验证过的合理值既不会小到很快又打满也不会大到超过内核限制而导致 systemd 拒绝启动。设完之后要验证是否生效cat /proc/$(pgrep dockerd)/limits | grep open files看到输出变成 1048576 就说明成功了。4.3 系统级开放限制fs.file-max 与 limits.confsystemd 配置解决的是 dockerd 这一个进程的上限。但如果是系统全局 file-max 被打满还要动内核参数。修改/etc/sysctl.conf增加fs.file-max 2097152 fs.nr_open 2097152然后执行sysctl -p立即生效。fs.nr_open是系统级单个进程能打开的最大文件数硬上限必须大于等于所有进程的RLIMIT_NOFILE。同时为了让所有用户在登录 shell 时也能获得更大的 FD 上限修改/etc/security/limits.conf* soft nofile 1048576 * hard nofile 1048576 root soft nofile 1048576 root hard nofile 1048576注意*号在 limits.conf 里代表所有用户但没有写 root 的话root 不生效所以需要单独把 root 也写上。改完这个文件后新登录的会话会按新的限制走已经运行的进程不会自动更新所以如果某些老进程也需要调大要么重启它们要么在启动前用ulimit -n设置。4.4 Docker Desktop / WSL2 特殊场景如果你是用 Docker DesktopWindows 或 macOS而且是在 WSL2 后端里跑容器情况稍有不同。Docker Desktop 有自己的 Linux 虚拟机但 WSL2 发行版里的 docker 命令如果没有用 Docker Desktop 的 CLI 代理会直接连接自己发行版内的 docker daemon或者根本没有这时候限制受~/.wslconfig和 WSL 发行版内部 systemd 配置影响。常见操作是编辑用户目录下的.wslconfig限制 WSL2 虚拟机的内存和 CPU不过 FD 限制一般在 WSL2 发行版内部配置。WSL2 默认没有启用 systemd启动 Docker 的方式可能是 service 脚本也可能是手动执行。如果你在 WSL2 里跑的是 Ubuntu需要在/etc/wsl.conf里加上[boot] systemdtrue才能用 systemctl 管理 Docker然后按照上面 systemd 的 override 配置来设置。还有一点容易被忽略Docker Desktop 在 Windows 上如果提示virtualization support not detected那是因为 WSL2 需要 CPU 虚拟化支持跟 FD 限制无关别把两个问题混在一起。先确保 VT-x/AMD-V 在 BIOS 里开启再排查 FD 问题。4.5 应用层优化减少连接占用调大限制是治标治理根才是长久之计。我见过很多系统把限制调到 1048576 之后还是慢慢涨上去根源是某个组件在疯狂建立 Docker API 连接。常见优化手段监控采集器增加连接复用不要每次请求都新建 HTTP clientCI 脚本避免在循环里执行大量 docker 命令改用批量操作在daemon.json里限制并发下载上传数{ max-concurrent-downloads: 5, max-concurrent-uploads: 5, max-download-attempts: 3 }容器内部应用也要注意连接池配置数据库连接池、HTTP 客户端连接池都要设置最大连接数和空闲回收时间我遇到过一个案例Java 应用使用 Docker API 客户端每秒钟轮询一次容器状态连接没有正确关闭最后把 dockerd 的 FD 打满。加了连接池、设置空闲超时之后FD 数量稳定在几百个左右再也没出现过 accept error。要让daemon.json生效修改后需要重启 Dockersystemctl restart docker5. 常见问题与排查技巧实录5.1 常见原因与对应排查速查表我把实际中遇到过的各种情况整理成一个速查表方便你对照排查现象可能原因优先排查命令accept4: too many open files频繁出现dockerd 进程 FD 达到 RLIMIT_NOFILE 上限或系统 file-max 打满cat /proc/$(pgrep dockerd)/limits、cat /proc/sys/fs/file-nr改了 limits.conf 后 Docker 重启又恢复systemd 服务不读 limits.conf需要配置 overridesystemctl cat docker检查是否有 LimitNOFILE容器内应用报 too many open files容器进程自身的 ulimit 限制或宿主机 inotify、FD 限制docker exec 容器 cat /proc/1/limits重启 Docker 后直接无法启动systemd 配置的 LimitNOFILE 超过系统fs.nr_open限制sysctl fs.nr_openDocker Desktop 一直 startingWSL2 未启用、虚拟化未开启、磁盘空间不足等查看 Docker Desktop 日志日志持续涨但没有新容器某个客户端组件连接泄漏ss -xp | grep docker.sock修改 sysctl.conf 后不生效没有执行sysctl -p或值被其他配置覆盖执行sysctl -p并检查/etc/sysctl.d/目录5.2 踩过的坑改了没生效的几种情况这里有几个我真实踩过的坑专门拿出来说一下。第一个坑是只改了/etc/security/limits.conf以为系统重启后会生效。实际上 systemd 管理的服务根本不读这个文件必须用systemctl edit docker或手动创建/etc/systemd/system/docker.service.d/limits.conf。这一点踩的人最多我群里好几个朋友都是这个原因改完后重启 Docker 发现限制没变。第二个坑是把LimitNOFILE1048576写进 override 后重启时 systemd 报错说限制超过系统最大允许值。这是因为fs.nr_open默认值可能就是 1048576某些发行版甚至更低。解决方案是先把fs.nr_open调大比如sysctl -w fs.nr_open2097152再设置LimitNOFILE1048576。第三个坑是修改了 daemon.json 后重启 Docker 报 JSON 解析错误导致整个服务起不来。这是因为 JSON 格式写错了比如多了一个逗号。我的建议是修改前先备份原文件修改后用docker daemon --validate或者python -m json.tool /etc/docker/daemon.json检查格式。第四个坑是 WSL2 场景下在 Windows 上改 Docker Desktop 设置保存后提示成功但实际没有重启 Linux 后端日志里还是老问题。Docker Desktop 的Apply Restart按钮一定要点并且要等它完全重启完成否则配置不生效。5.3 长期运维建议怎么避免再次被打满解决一次问题不难难的是以后别再犯。我在生产环境长期运维下来总结了几个习惯性操作。建立每日巡检。用 cron 或者 systemd timer 定时执行脚本检查file-nr的百分比超过 70% 就告警。脚本很简单读取/proc/sys/fs/file-nr的第一个数字除以第三个数字算出使用率。同样检查 dockerd 的 FD 数量占软限制的百分比。监控容器数量变化。如果业务没有扩容但容器从 50 个涨到 200 个这本身可能就是一个 bug比如某个编排脚本重复启动容器。我用docker ps -q | wc -l做了定时统计配合监控系统看曲线能及时发现异常增长。上线前压测。凡是涉及大量容器并发启动、频繁 exec 的应用上线前一定要在当前环境做一次压测。我之前部署一个批量处理任务同时启动 500 个容器每个容器里都调用docker logs如果 FD 限制是 1024系统瞬间就崩了。压测能提前发现限制瓶颈。定期升级 Docker 版本。新版本对连接管理和 FD 分配有持续优化很多历史问题在新版里已经修复。只要做好镜像兼容性测试升级是低风险高收益的事情。最后再分享一个实用小技巧。如果问题已经发生了但你想跟踪是谁在不停建立 Docker socket 连接可以在重启 Docker 后用strace -p $(pgrep dockerd)抓系统调用看accept和close的频率。如果看到大量accept后没有对应的close那就是连接泄漏直接顺着这个方向查客户端代码就行不用瞎猜。
返回列表