在容器里遇到 ELOOP 报错但翻遍了路径也找不到软链接?大概率是你的 autofs 或挂载命名空间在搞鬼。
结论就是:别在 Docker 容器里用 autofs 做 bind mount 来映射本地代码,直接用 Docker 的 volume bind 挂载到目标路径,把不必要的 automount 映射删掉,这才是最稳的。
为什么没软链接也会报 ELOOP 错误
很多人看到 Too many levels of symbolic links 就习惯性去查 ln -s,但 ELOOP 实际上是一个更底层的机制。在 Linux v6.1 内核里,follow_automount() 函数会增加 nd->total_link_count 的计数,这个计数器是和软链接共用的。一旦达到 MAXSYMLINKS(也就是 40 次),内核就会直接抛出 -ELOOP。
这意味着即便你一个软链接都没有,只要路径查找过程中触发的 automount 次数过多,或者触发了某种循环,依然会报这个错。根据 autofs 的官方文档,如果一个 autofs 文件系统在多个地方可见,但 daemon 创建的挂载点没有正确传播,调用者可能会陷入一种「触发-未见挂载-再次触发」的死循环,最后直接触发 ELOOP。
我们在排查时发现,这次故障和当时的 NFS 事故以及一个状态不健康的 automounter 有关。虽然没抓到具体的内核 trace,但这种依赖关系本身就是冗余的。
那个导致崩溃的诡异配置
当时我们的 cron 容器在 docker-compose 里已经把代码挂载进去了:
宿主机 /srv/code/app-a -> 容器 /opt/code/app-a
宿主机 /srv/code/app-b -> 容器 /opt/code/app-b
但问题是,应用程序代码里写死的路径是 /srv/apps/app-a。为了兼容,镜像里居然用 autofs 的 direct map 做了二次映射:
# /etc/auto.master 里的配置
/- /etc/auto.apps --ghost,--timeout=30
# /etc/auto.apps 里的具体条目
/srv/apps/app-a -fstype=bind :/opt/code/app-a
/srv/apps/app-b -fstype=bind :/opt/code/app-b
这就导致一个极其离谱的链路:打开一个本地 PHP 文件,居然要依赖一个管理网络挂载的守护进程。只要 autofs 服务稍微不稳定,读取文件的路径就会在内核里打转,最后直接崩掉。
最终的解决方案
9 月 10 号我们决定彻底砍掉这个依赖。既然代码已经在宿主机上了,没必要在容器内部绕弯子,直接在 docker-compose 的 volumes 里增加直接映射,把代码挂载到程序预期的那个路径上,然后把 autofs 映射表里的相关项删掉。
修改后的 Compose 配置片段如下:
volumes:
- type: bind
source: /srv/code/app-a
target: /srv/apps/app-a
- type: bind
source: /srv/code/app-b
target: /srv/apps/app-b
这样操作后,路径查找直接走 Docker 的挂载点,不再经过 autofs 守护进程。只要你不希望本地代码的读取速度和稳定性取决于一个网络挂载服务,就赶紧检查一下你的容器挂载路径。
这坑太深了,我上周刚被这个折磨了三小时,其实还跟内核版本有关,2.6.32 以后是不是才稳点?