1. 前言
前两天有同事发现,通过华为云 ServiceStage 的流水线部署基于模板创建的 CSEJavaSDK demo 服务时,会在容器启动过程中报错。初步排查是由于 JVM 占用的内存超出了 docker 内存配额的上限,导致容器被 kill 掉。于是我们需要排查一下问题出在哪里,为什么以前没有这类问题,而现在却发生了。
2. 基本定位
要确定 docker 容器内存超限问题的直接原因并不难。直接进入docker容器,执行 top 命令,我们发现宿主机是一台8核16G的机器,而且 docker 并不会屏蔽这些信息,也就是 JVM 会认为自己工作于一台 16G 内存的机器上。而查看 demo 服务的 Dockerfile,发现运行服务时并没有对 JVM 的内存进行任何限制,于是 JVM 会根据默认的设置来工作 —— 最大堆内存为物理内存的1/4(这里的描述并不完全准确,因为 JVM 的默认堆内存大小限制比例其实是根据物理内存有所变化的,具体内容请自行搜索资料),而基于模板创建的 ServiceStage 流水线,在部署应用堆栈的时候会把 docker 容器的内存配额默认设置为 512M,于是容器就会在启动的时候内存超限了。至于以前没有碰到过这种问题的原因,只是因为以前没将这么高规格的 ECS 服务器用于流水线部署应用堆栈。
在查询过相关资料后,我们找到了两种问题解决方案,一个是直接在 jar 包运行命令里加上 -Xmx 参数来指定最大堆内存,不过这种方式只能将 JVM 堆内存限制为一个固定的值;另一个方法是在执行 jar 包时加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 参数,让 JVM 能够感知到docker容器所设置的 cgroup 限制,相应地调整自身的堆内存大小,不过这个特性是 JDK 8u131 以上的版本才具备的。
最终,我们提醒 ServiceStage 流水线的同学将 CSEJavaSDK demo 的创建模板做了改进,在 Dockerfile 中将打包的基础镜像版本由原先的 java:8u111-jre-alpine 升级为了 openjdk:8u181-jdk-alpine,并且在运行服务 jar 包的命令中加上了 -Xmx256m 参数。问题至此已经解决了。
3. 进一步的探究
虽然问题已经解决,但是在好奇心的驱使下,我还是打算自己找个 demo 实际去触发一下问题,另外看看从网上搜到的解决方法到底好不好用 : )