真oom需同时满足reason: oomkilled且exitcode=137;若仅exitcode=137但reason非oomkilled,或dmesg无oom记录、memory.events中oom=0,则大概率是nofile不足导致连接积压、内存持续增长引发的“假oom”。

Nginx 容器被 OOMKilled,但实际不是内存真超限——这种“假 OOM”在生产中很常见,尤其当 nofile(文件描述符)限制没配或过低时。根本原因不在内存本身,而在于资源耗尽引发的连锁反应:大量连接堆积 → 进程卡死或僵住 → 内存持续增长(如连接缓冲区、未释放的 socket 结构体)→ 最终触发内核 OOM Killer。
下面分三块讲清这个链路和排查要点:
看懂 OOMKilled 是真是假,先盯住 Reason 和 Exit Code 的组合
-
kubectl describe pod <name></name>输出里必须同时满足两个条件才算真 OOM:-
Reason: OOMKilled(内核级杀进程) -
ExitCode: 137(128 + 9 = SIGKILL)
-
- 如果
Reason: Error或Reason: ContainerCannotRun,哪怕 ExitCode 是 137,也大概率是探针失败、启动异常或资源初始化失败(比如open too many files报错被静默吞掉),不是内存问题。 - 特别注意:Nginx 默认不打印
too many open files错误到 stdout,它会静默拒绝新连接,但 worker 进程仍在运行、持续占用内存,日志里可能只看到accept() failed (24: Too many open files)出现在 error log 中(需挂载并检查/var/log/nginx/error.log)。
确认 nofile 是否成了瓶颈
- 查容器实际生效的
nofile限制:kubectl exec <pod-name> -- sh -c 'ulimit -n' # 或进容器后看: cat /proc/1/limits | grep "Max open files"</pod-name>
- 检查 Nginx 配置是否显式设置了
worker_rlimit_nofile(该值必须 ≤ 容器 ulimit):events { worker_connections 10240; } # 必须有这一行,且数值 ≤ ulimit -n worker_rlimit_nofile 65536; - 若没设
worker_rlimit_nofile,Nginx 默认用系统ulimit -n值;若容器没指定securityContext.ulimits,则继承节点默认值(通常是 1024),远不够高并发场景。
从 cgroup 和内核日志交叉验证是否“伪 OOM”
- 查该 Pod cgroup 的
memory.events(cgroup v2):# 先定位 pod cgroup path(参考 kubectl describe 输出或 /sys/fs/cgroup/kubepods/...) cat /sys/fs/cgroup/kubepods/pod<uid>/.../memory.events # 关注 oom 和 oom_kill 字段: # oom: 0 → 说明内核根本没触发 OOM 判定 # oom_kill: >0 → 真被杀过,但要结合时间戳看是否与连接突增吻合</uid>
- 同时查节点 dmesg:
dmesg -T | grep -i "out of memory" | tail -5 # 如果没输出,或时间点明显晚于 Nginx 日志里 first `Too many open files`,基本可断定是 nofile 引发的内存雪崩,非真实内存不足。
- 补充证据:用
kubectl top pod查内存趋势,如果内存使用呈缓慢、持续、不可回收式上涨(而非尖峰后回落),再配合 error.log 里大量accept() failed (24: ...),就是典型 nofile 不足导致的假 OOM。
本质上,K8s 不管 nofile,它只管 limits.memory。你没配 ulimits,Nginx 就用默认 1024,扛不住 5000 并发,连接积压、缓冲膨胀、GC 压力上升(尤其带 Lua 或动态模块时),最终把内存吃满——OOMKilled 是结果,不是病因。











