文件描述符耗尽是docker compose多服务部署中“连接被拒绝”的隐性主因,表现为无法建连、日志失败或崩溃;需通过ulimit -n与/proc/1/fd统计确认,再在docker-compose.yml中配置ulimits、daemon.json设default-ulimits,并适配应用层限制。

文件句柄(file descriptor)耗尽是 Docker Compose 多服务部署中出现“连接被拒绝”的典型隐性原因——它不报 Connection refused 字样,却让服务无法接收新连接、日志写入失败、甚至反复崩溃重启。问题常在高并发或依赖密集型场景(如 PHP 应用调用 Composer、Java 服务频繁建连、Nginx 转发大量请求)下集中爆发。
快速确认是否为句柄不足
进入疑似异常的服务容器,执行以下命令:
- ulimit -n:查看当前进程软限制(如输出 1024,即最多打开 1024 个句柄)
- ls /proc/1/fd | wc -l:统计 PID 1 进程实际打开的句柄数(对 Java、Node、PHP-FPM 等主进程有效)
-
docker-compose logs
| grep -i "too many open files\|emfile\|unable to allocate file descriptor" :搜索关键错误线索
若第二项数值持续接近或等于第一项,基本可锁定句柄上限问题。
Compose 启动时正确配置 ulimits
仅改宿主机 ulimit 不起作用,必须在 Compose 层显式声明。推荐写法如下(兼容 v2.1+):
- 在 docker-compose.yml 的服务定义下添加:
services:
api:
image: my-java-app
ulimits:
nofile:
soft: 65536
hard: 65536
- 若使用资源限制(如 memory),ulimits 需置于同一缩进层级,不可嵌套在 resources 下
- 避免只设 soft 值;hard 必须 ≥ soft,且不能超过宿主机对 dockerd 进程的允许上限
同步加固宿主机与 Docker 守护进程
容器 ulimit 继承自 dockerd,而 dockerd 自身受 systemd 限制约束。漏掉任一环,配置都会失效:
- 检查 dockerd 当前限制:cat /proc/$(pgrep dockerd)/limits | grep "Max open files"
- 修改 /etc/docker/daemon.json,加入默认 ulimit:
{
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
- 重启守护进程:sudo systemctl restart docker
- 验证生效:docker info | grep -i ulimit 应显示 default ulimits 已加载
应用层适配不可忽略
某些运行时会主动覆盖系统 ulimit,需额外干预:
- Java 应用:启动参数加 -XX:+UnlockExperimentalVMOptions -XX:MaxFDLimit=65536
- Node.js:启动前执行 ulimit -n 65536(需在 entrypoint.sh 中设置)
- PHP-FPM:在 www.conf 中配置 rlimit_files = 65536
- Composer 场景:若在容器内执行 install/dump-autoload,确保其 shell 环境已继承 ulimit(避免在 alpine 镜像中因 busybox 限制失效)











