根本原因是多个ci任务并发写nfs挂载的$gocache触发锁失效;应为每个job单独设置本地磁盘上的gocache路径,如export gocache=$(mktemp -d -p /tmp),并确保gopath、gobin也隔离,避免共享nfs路径。

Go模块依赖在CI中报Permission denied或cannot lock cache directory,根本不是权限配置错了,而是多个构建任务并发写$GOCACHE触发了NFS文件锁失效。
为什么go mod download在CI里频繁卡住或失败
云原生CI(比如GitHub Actions、GitLab CI、Argo CD)常复用同一台Runner或共享NFS挂载的$HOME,而Go默认把缓存放在$HOME/.cache/go-build。NFS对flock()支持极弱,多个job同时尝试加锁cache.lock会直接返回失败,表现为permission denied——不是磁盘没权限,是锁机制崩了。
- 错误现象:
go: downloading github.com/some/pkg@v1.2.3卡住几十秒后报failed to lock cache directory - 只在多job并行时出现,单job跑没问题
-
go env GOCACHE显示路径确实在NFS上(如/home/ci/.cache/go-build) -
strace -e trace=flock go mod download能看到大量flock(…, LOCK_EX) = -1 ENOLCK
必须为每个CI job隔离GOCACHE路径
不能靠“改全局权限”或“重试三次”,得让每个job用独立缓存目录,彻底避开争用。关键点:路径必须本地磁盘、属主明确、不跨job复用。
- CI脚本开头显式设置:
export GOCACHE=$(mktemp -d -p /tmp)(Linux)或export GOCACHE=$(mktemp -d)(macOS) - 如果要用持久缓存(比如GitHub Actions的
actions/cache),缓存key必须包含job唯一标识,例如go-cache-${{ runner.os }}-${{ hashFiles('**/go.sum') }} - 绝对不要用
go env -w GOCACHE=...——它写入$HOME/go/env,会被后续所有job继承,反而放大冲突 - 验证是否生效:
go env GOCACHE输出应为/tmp/tmp.XXXXXX类路径,且ls -ld $GOCACHE显示属主是当前CI用户
GOBIN和GOPATH也要隔离
go install失败报cannot write to $GOBIN,往往是因为多个job试图往同一个$GOPATH/bin写二进制,尤其当$GOPATH被设为/opt/gopath这类共享只读路径时。
- CI中统一设:
export GOPATH=$(mktemp -d)+export GOBIN=$GOPATH/bin - 避免fallback到
$HOME/go/bin——这个路径在LDAP/NFS环境下常被设为只读 - 如果必须复用
GOBIN(比如部署到固定路径),改用go build -o /tmp/myapp ./cmd/myapp绕过go install,再手动cp - 检查
go env GOPATH GOBIN,确保两者都不指向NFS或系统级路径
真正麻烦的不是锁本身,而是CI环境里$GOCACHE路径的隐式继承和NFS的锁语义缺失——哪怕你代码里sync.Mutex用得再漂亮,也拦不住两个进程在底层抢同一个inode的锁文件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











