根本原因是nfs不支持flock跨节点同步,导致~/.composer/cache写冲突和json损坏;必须隔离各节点cache目录至本地ssd或tmpfs,并禁用nfs共享,同时ci中加--no-cache参数跳过缓存锁竞争。

共享 NFS 上的 ~/.composer/cache 是死锁根源,不是配置问题,而是 POSIX 锁在分布式文件系统上失效导致的写冲突和等待超时。
为什么 NFS + flock 会卡住甚至 hang 住 CI Job
NFS v3/v4 默认不支持 byte-range locking 或跨节点 flock 同步,PHP 的 flock() 在挂载点上退化为本地空操作——多个编译机同时尝试写同一 cache 文件(如 repo/https---packagist.org/packages.json)时,既无互斥也无重试,结果是文件被截断、JSON 损坏、后续读取报 JSON decode error,进程卡在 fopen(..., 'c') 或 file_put_contents() 等阻塞调用上。
- 现象:Job 日志停在
Writing /root/.composer/cache/repo/https---packagist.org/packages.json不再输出,ps aux | grep composer显示进程状态为D(uninterruptible sleep) - 根本原因:NFS 客户端内核无法将
flock映射为服务端有效锁,而 Composer 没有 fallback 机制,也不检查写入是否完整 -
strace -p $PID可见反复write(3, "...", 8192)后卡在fsync(3)—— NFS server 正在串行化元数据提交,但客户端已超时放弃
必须隔离每个节点的 cache 目录并禁用 NFS 共享
不要试图调大 nfs mount 参数(如 hard,intr,rsize=1048576,wsize=1048576),这些对锁失效无效。唯一解法是让 cache 落在本地 SSD 或 tmpfs,并确保路径包含节点标识。
- 查当前路径:
composer config -g cache-dir - 强制重定向(所有编译机执行一次):
composer config -g cache-dir /var/cache/composer-$(hostname -s) - 若用 systemd 启动编译 Agent,加环境变量:
COMPOSER_CACHE_DIR=/var/cache/composer-%H(%H为 hostname) - tmpfs 方案(推荐):
sudo mount -t tmpfs -o size=2G,mode=0755 tmpfs /var/cache/composer-$(hostname -s),重启后自动清空,避免残留损坏 - 禁止把
/tmp或/var/tmp当 cache 目录——某些发行版每日清理,导致重复下载和file could not be downloaded
CI 流水线里要显式跳过 cache 写入竞争点
即使 cache 隔离了,composer install 在多 Job 并行时仍可能因读取同一 composer.lock 并写 vendor 而触发 NFS 元数据风暴(参考知识库中“IO 风暴”条目)。这时需双重规避:
- 所有
composer install必须加--no-cache参数:它不读~/.composer/cache,但更重要的是——它跳过从 cache 中提取 dist 包的flock尝试,直接走 HTTP 下载(配合镜像源更稳) - 禁止在 CI 中运行
composer update:它会重写composer.lock,而该文件若放在 NFS 上,就又回到多写竞争起点 - 构建产物(
vendor/)绝不挂载或同步:用tar -cf - -C vendor . | ssh nodeX 'tar -xf - -C /app/vendor'替代rsync -av vendor/,避开逐文件stat()压力
验证是否真正生效的三个检查点
改完别只看 Job 是否跑通,重点确认这三件事:
- 每台编译机执行
ls -ld $(composer config -g cache-dir),输出路径必须不同,且挂载类型为tmpfs或本地xfs/ext4,绝不能是nfs - 任意一台上运行
lsof +D $(composer config -g cache-dir),不应看到其他节点 IP 的连接;若有,说明某处还硬编码了共享路径 - 抓一个失败 Job 的
strace -e trace=openat,write,fsync,flock -p $PID 2>&1 | tail -20,确认没有flock(3, LOCK_EX调用,或调用后立即返回 0(表示 NFS 上 lock 失效)
真正麻烦的不是锁本身,而是你以为加了 flock 就安全了,其实 NFS 上它根本没起作用——得先承认这个前提,才能往下走。











