问题描述
我在使用官方 spring boot gradle 插件创建的容器中有一个 spring boot 应用程序 v2.4.3,该插件使用 buildpakcs 来执行此操作。
其中一层是 jvmkill:
[creator] Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
这非常好,它正确地为 jvmkill 添加了 jvm arg
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx387804K -XX:MaxMetaspaceSize=148771K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G,Thread Count: 250,Loaded Class Count: 23852,Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=2 -XX:MaxDirectMemorySize=10M -Xmx387804K -XX:MaxMetaspaceSize=148771K -XX:ReservedCodeCacheSize=240M -Xss1M
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__,| / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.3)
该应用程序正在 Kubernetes (AWS EKS) 中运行,但是当我遇到 OOM 时
java.lang.OutOfMemoryError: Java heap space
jvmkill 启动,打印堆转储并发送终止信号
Heap
def new generation total 116736K,used 90606K [0x00000000e8400000,0x00000000f02a0000,0x00000000f02a0000)
eden space 103808K,87% used [0x00000000e8400000,0x00000000edc7bae0,0x00000000ee960000)
from space 12928K,0% used [0x00000000ef600000,0x00000000ef600000,0x00000000f02a0000)
to space 12928K,0% used [0x00000000ee960000,0x00000000ee960000,0x00000000ef600000)
tenured generation total 259456K,used 249951K [0x00000000f02a0000,0x0000000100000000,0x0000000100000000)
the space 259456K,96% used [0x00000000f02a0000,0x00000000ff6b7f18,0x00000000ff6b8000,0x0000000100000000)
Metaspace used 74292K,capacity 76000K,committed 76824K,reserved 208160K
class space used 8689K,capacity 9350K,committed 9600K,reserved 140576K
jvmkill killing current process ```
但 jvm 永远不会被杀死。 检查容器时,我注意到应用程序以 PID 1 运行,无法从容器内部终止(或发出信号)。
cnb@myhost-6968d47f4b-2cnlj:/$ ps -fea
UID PID PPID C STIME TTY TIME CMD
cnb 1 0 6 19:49 ? 00:00:54 java org.springframework.boot.loader.JarLauncher
cnb 121 0 0 20:02 pts/0 00:00:00 bash
cnb 131 121 0 20:02 pts/0 00:00:00 ps -fea
由于所有这些都是由 java buildpack 本身构建的,我希望它知道 PID 1 不能被终止并以不同的方式启动应用程序以正常工作。
是否有我遗漏的东西或需要为 buildpack jvmkill 进行配置才能开箱即用的东西?
解决方法:
- 如果我直接使用 docker 运行图像,我可以使用
docker run --init
来向 jvm 进程发送信号(我的应用程序使用 PID 7 运行)。对 Kubernetes 无效。
cnb 1 4.0 0.0 1120 4 ? Ss 20:10 0:00 /sbin/docker-init -- java etc...
cnb 7 4.0 0.0 18372 1580 ? S 20:10 0:00 java etc...
-
我可以在我的 k8s 规范中
shareProcessNamespace: true
让它以 1 以外的 PID 运行,但由于安全合规性要求,很难证明其合理性。 -
我可以只添加 jvm 标志
-XX:+ExitOnOutOfMemoryError
,但是没有 jvmkill 显示的良好堆转储以及 jvmkill 线程创建失败覆盖率。
解决方法
鉴于 https://github.com/airlift/jvmkill#using-inside-docker-containers 中提到的解决方法,您需要其他东西才能成为 pid 1。Kubernetes 没有 --init
的等效项,但如果您在映像中包含 tini
,则可以通过容器命令手动使用它。您也可以类似地使用 sh
。
还要记住,这只会保护您免于达到 Java 级别的堆大小限制。如果您达到实际的容器内存限制,那么您的进程将立即终止,没有机会捕获它并执行任何操作。