根本原因是进程启动时内核fd软限过低,go runtime初始化即失败;需查/proc//limits确认真实软限值,而非依赖ulimit -n。

Go进程启动就报too many open files,不是代码问题
Go二进制一执行就崩在accept4: too many open files,说明它根本没跑进main()逻辑——是系统限制在进程启动瞬间就卡死了。这不是文件没关、也不是HTTP超时没设,而是runtime连初始化都失败了。根本原因只有一个:进程启动时,内核给它的fd软限(Soft limit)太低,且这个值在Go runtime初始化阶段就被硬编码读取,后续再调ulimit -n或改/proc/sys/fs/file-max都无效。
查清当前进程真实的fd限制值
别信ulimit -n输出——它查的是当前shell的限制,不是你的Go进程。容器里尤其容易错判,因为PID 1可能没继承限制,或者被entrypoint脚本重置。
- 进容器后先找Go进程PID:
pidof yourapp或ps aux | grep yourapp - 看真实生效值:
cat /proc/<pid>/limits | grep "Max open files"</pid>,重点看第二列(Soft limit) - 在
main()开头加校验代码(需// +build !windows):var rlim unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlim); err == nil { if rlim.Cur
Linux系统级ulimit必须在Go启动前固定
单纯ulimit -n 65535只对当前shell有效;写进/etc/security/limits.conf后必须重新登录才生效;而supervisor、systemd、docker这些init管理器,往往有自己的默认限制,且不自动继承宿主机配置。
- supervisor:必须在
[program:xxx]段显式加minfds=65535,否则默认是1024 - systemd:在service文件中加
LimitNOFILE=65535,并确认DefaultLimitNOFILE没被覆盖 - Docker:用
--ulimit nofile=65535:65535,但若entrypoint是sh -c "exec ./app",shell会重置限制——改用exec ./app直接替换PID 1 - K8s:securityContext.ulimits必须作用于主容器的PID 1,initContainer改的只是自己进程的/proc,不影响主进程
真正难定位的是“哪些goroutine卡着fd不放”
当lsof -p <pid> | wc -l</pid>持续上涨,但unix.Getrlimit显示软限充足,说明泄漏发生在运行时——比如阻塞在os.File.Close或syscall.Read的goroutine。这时候defer f.Close()写了也没用,因为调用根本没执行到那一步。
- 开block profile:
runtime.SetBlockProfileRate(1),然后访问/debug/pprof/goroutine?debug=2 - 重点关注停在
os.(*File).Close、net.(*conn).Read、http.(*persistConn).readLoop的协程 - HTTP客户端泄漏常见于没复用
http.Transport,或并发调用client.CloseIdleConnections()没加锁导致panic后跳过关闭











