新部署的Kubernetes集群版本为v1.34.1,在部署应用后发现所有Java服务都频繁出现OOM Kill现象。这些服务均已配置了明确的内存request和limit,并将堆内存设置为容器内存的80%。值得注意的是,同样的部署文件在1.30版本的集群上可以正常运行。
经过排查,发现被OOM Kill的Pod中,容器并未正确识别到为其设定的内存限制。例如,一个容器内存限制为1Gi,JVM参数为-XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0 -XX:MetaspaceSize=200m,而宿主机内存为32G。通过Arthas在Pod内查看,发现堆内存远超预期,基本上是按照宿主机内存来计算的,这直接导致了OOM的发生。

在Pod内执行以下命令进一步确认:
root@xxxxxx:/# java -XshowSettings:vm -version
Picked up JAVA_TOOL_OPTIONS: -XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0 -XX:MetaspaceSize=200m
VM settings:
Max. Heap Size (Estimated): 23.44G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
这显然不符合预期。深入排查后发现,问题的根源在于集群使用的CGroup版本。新集群启用了cgroup v2,这导致运行在Pod内的Java应用错误地将宿主机内存当作了容器的可用内存上限。这类似于早年cgroup v1时代,JDK 8u131版本之前的JVM无法自动识别CGroup资源限制的情况,如今在cgroup v2环境下重现了。
如何识别Linux节点上的cgroup版本
cgroup版本取决于Linux发行版及其默认配置。要检查节点使用的cgroup版本,请在该节点上执行以下命令:
stat -fc %T /sys/fs/cgroup/
- 如果输出为
cgroup2fs,则表示使用的是 cgroup v2。
- 如果输出为
tmpfs,则表示使用的是 cgroup v1。
在容器内部,同样可以使用此命令进行验证:
root@xxxx:/# stat -fc %T /sys/fs/cgroup/
cgroup2fs
cgroup v2简介
cgroup v2是Linux cgroup API的下一个演进版本,它提供了一个统一的控制系统,具备更强的资源管理能力。Kubernetes 1.35版本已计划彻底移除对cgroup v1的支持。
相比cgroup v1,cgroup v2带来了多项重要改进,例如:
- 单一、统一的层次结构设计。
- 更安全的子树委派机制,更适合容器场景。
- 新增如压力阻塞信息(Pressure Stall Information,PSI)等特性。
- 跨CPU、内存、IO等多种资源的增强分配管理与隔离。
- 统一核算不同类型的内存分配(如网络内存、内核内存)。
- 更好地处理非即时资源变化,例如页面缓存回写。
对于现代容器化部署和云原生环境,一些Kubernetes高级特性(如MemoryQoS)正是依赖于cgroup v2的原语来实现更精细的内存服务质量控制与隔离。
解决方案
要彻底解决此问题,关键在于为Java应用使用已完全支持cgroup v2的JDK版本。
以下是各主流JDK发行版中,开始完整支持cgroup v2的最低版本号:
- OpenJDK / HotSpot: jdk8u372、11.0.16、15 及更高的版本
- IBM Semeru Runtimes: 8.0.382.0、11.0.20.0、17.0.8.0 及更高的版本
- IBM Java: 8.0.8.6 及更高的版本
验证效果
我们将基础镜像升级至JDK 8u472并重新部署服务。服务配置保持不变:
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=80.0 -XX:InitialRAMPercentage=80.0 -XX:MinRAMPercentage=80.0 -XX:MetaspaceSize=200m"
...
resources:
requests:
memory: 1Gi
limits:
memory: 1Gi
容器内存Limit为1Gi,预期堆内存应为1Gi的80%,即约800MiB。再次使用Arthas在Pod内查看,结果符合预期,堆内存上限被正确识别为792M,应用运行稳定,未再发生OOM Kill。
Memory used total max usage GC
heap 164M 792M 792M 20.73% gc.copy.count 31
...
Runtime
os.name Linux
java.version 1.8.0_472
...
这表明升级JDK版本后,应用已能正确识别cgroup v2施加的资源限制,内存配置生效,问题得到解决。