CCE 上 Pod 反复 OOMKilled 的三层排查

举报
yd_231746916 发表于 2026/10/30 12:34:24 2026/10/30
【摘要】 一个 Spring Boot 服务迁到 CCE 之后,Pod 每 20 分钟左右重启一次。 kubectl describe pod 里写着 Last State: Terminated, Reason: OOMKillCCE Kubernetes JVM

一个 Spring Boot 服务迁到 CCE 之后,Pod 每 20 分钟左右重启一次。<code>kubectl describe pod</code> 里写着 <code>Last State: Terminated, Reason: OOMKilled, Exit Code: 137</code>,容器 limit 是 2Gi,JVM 参数是 <code>-Xmx1536m</code>——看上去留了 512M 余量,理应安全。第一反应是把 limit 调到 4Gi,结果跑了两小时还是被杀。问题不在 limit 大小,在于那 512M 余量根本不够 JVM 的堆外开销。

这篇把排查顺序固定成三层:先定性是谁杀的,再算清 JVM 在容器里到底占多少,最后拿 NMT 的证据定位。顺序不能跳,跳了就会一直在"调大 limit"这条路上打转。

第一层:确认是 cgroup 杀的,不是 JVM 自己抛的

同样是"内存问题",<code>OOMKilled</code> 和 <code>java.lang.OutOfMemoryError</code> 的处理方式完全相反:前者是 cgroup 把进程杀了,后者是堆内分配失败。先看退出码和原因:

# 退出码 137 = 128 + 9(SIGKILL),配合 reason=OOMKilled 基本可以定性
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}{"\n"}{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'

# 看容器当前内存(--containers 才是单容器粒度)
kubectl top pod <pod> --containers

# 排除节点级内存压力:节点 OOM 会在 dmesg 里留 killed process 记录
kubectl describe node <node> | sed -n '/Allocated resources/,/Events/p'

三种结果对应三条路:

· OOMKilled + 137 → cgroup 层,继续看第二层;

· 日志里有 java.lang.OutOfMemoryError: Java heap space → 堆内泄漏或堆太小,跟 limit 无关,去查堆 dump;

· 节点 MemoryPressure=True、dmesg 里有 Out of memory: Killed process → 是节点整体吃紧,先看这个节点上其他 Pod 的 requests/limits 有没有超卖。

补一个容易搞错的点:kubelet 判 OOM 用的是 <code>container_memory_working_set_bytes</code>(RSS + 活跃 page cache),不是 <code>RSS</code>。所以容器里读写文件产生的 page cache 也会算进这个数——一个频繁写本地日志的服务,很可能"代码没变、内存却涨了"。

第二层:算清 JVM 在容器里到底占多少

<code>-Xmx</code> 只是堆。容器里 JVM 的实际占用至少还有六项:

· 堆:-Xmx 或 MaxRAMPercentage × limit,你唯一显式设过的;

· Metaspace:150~350M,默认不设上限,Spring 应用加载的类多,涨得快;

· CodeCache:50~240M,默认上限 240M,JIT 之后接近上限;

· 线程栈:线程数 × -Xss,64 位 Linux 默认 1M,200 个线程就是 200M 虚拟内存;

· DirectMemory:可达堆大小,Netty、NIO、gzip 都用它,默认上限等于 -Xmx;

· JVM 自身 + agent:100~300M,监控 agent、APM 探针经常被忽略。

按这六项算一下就清楚了:1536M 堆 + 250M Metaspace + 200M CodeCache + 200M 线程栈 + 若干 DirectMemory,2Gi 的 limit 早就被打穿。所谓"留了 512M",实际是留给六项开销的,不是留给堆的余量。

我这次实测的一组数(环境:CCE 1.25 集群,4C8G 节点,JDK 17.0.9,Spring Boot 3.1 + Netty):

· 堆:调整前 -Xmx1536m,调整后 MaxRAMPercentage=65;

· Metaspace:186M 且无上限 → 上限 320M,实际 178M;

· CodeCache:92M,未变;

· 线程数:214 → 214(-Xss512k);

· DirectMemory:260M → 上限 256M;

· working_set 峰值:2.1G(超 2Gi,被杀)→ 1.62G。

这组数只代表我这套依赖组合,你的框架、线程池、agent 不同,绝对值会差很多,方法可以照搬。

第三层:用 NMT 把账对上

算完是估算,要拿证据。JDK 自带 Native Memory Tracking,加上参数重启后可以按分类看真实占用:

# 启动参数(Java 17 需要 UnlockDiagnosticVMOptions 才能开 summary 之外的细节)
# -XX:NativeMemoryTracking=summary -XX:+UnlockDiagnosticVMOptions

# 进容器取一次快照,按 MB 输出
kubectl exec -it <pod> -- jcmd 1 VM.native_memory summary scale=MB

输出里重点看三行:<code>Java Heap</code>(堆)、<code>Class</code>(Metaspace + CodeCache)、<code>Other</code>(DirectMemory、GC 结构、内部结构)。如果 <code>Other</code> 明显大于你的预期,基本就是 DirectByteBuffer 或者 GC 的辅助结构在吃内存;如果 <code>Class</code> 一路涨不回落,是动态代理/热加载产生的类没释放。

NMT 本身有 5%~10% 的性能开销,只在排查期间开,定位完就摘掉。

修复:把堆改成按 limit 的比例算

resources:
  requests:
    memory: "1Gi"
    cpu: "500m"
  limits:
    memory: "2Gi"
    cpu: "2"
env:
  - name: JAVA_TOOL_OPTIONS
    value: >-
      -XX:MaxRAMPercentage=65
      -XX:InitialRAMPercentage=65
      -XX:MaxMetaspaceSize=320m
      -XX:MaxDirectMemorySize=256m
      -Xss512k
      -XX:+ExitOnOutOfMemoryError

三个要点:

1. 用 <code>MaxRAMPercentage</code> 而不是硬编码 <code>-Xmx</code>。JDK 10+ 和 8u191+ 支持容器感知(<code>UseContainerSupport</code> 默认开启),堆会跟着 limit 自动算,不会再出现"调了 limit 忘了调 Xmx"这种错配。65% 是给堆外留 35%,堆外开销大的应用(Netty、大量线程)可以压到 60%。 2. 给 Metaspace 和 DirectMemory 都设上限。不设上限等于把不确定性留给 cgroup——它会以 SIGKILL 的形式还给你,而不是一个可读的 <code>OutOfMemoryError</code>。 3. <code>-XX:+ExitOnOutOfMemoryError</code> 让 JVM 在堆内 OOM 时直接退出,交给 K8s 重启,避免进程半死状态下继续接流量。

边界

· requests 别贴着 limits 设。requests 决定调度,给得太高会让 Pod 排不上去;给 1Gi 是留出稳态占用,让调度器知道这台机器至少要被占掉 1Gi。

· 频繁重启还会掩盖真实问题。如果调整后 working_set 仍然阶梯式上涨,那就是泄漏,去 dump 堆(jcmd 1 GC.heap_dump /tmp/heap.hprof)而不是继续调参数。

· 只调大 limit 是把问题往后推。同样是"没被杀",2Gi 撑 20 分钟、4Gi 撑两小时,泄漏的斜率一点没变。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。