我在一个容器中有一个spring-boot应用程序v2.4.3,该容器使用官方的spring-boot-gradle插件创建,该插件使用buildpakcs来完成此操作。
其中一层是jvm kill:
[creator] Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
这是非常好的,它正在为jvm添加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
jvm kill启动,打印堆转储并发送kill信号
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构建包本身构建的,我希望它知道PID 1不能被杀死,并以不同的方式启动应用程序以正常工作。
我是否缺少或需要为buildpack jvm kill配置一些东西,以便开箱即用?
解决方法:
- 如果我直接使用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
,让它以PID而不是1运行,但由于安全合规性要求,很难证明这一点。我可以添加jvm标志
-XX:+ExitOnOutOfMemoryError
,但没有jvm kill显示的漂亮堆转储加上jvm kill线程创建失败覆盖率。
给定中提到的解决方法https://github.com/airlift/jvmkill#using-在docker容器中,您需要一些其他内容才能成为pid 1。Kubernetes没有--init
的等价物,但如果在映像中包含tini
,则可以通过container命令手动使用它。您也可以类似地使用sh
。
还要记住,这只会保护您不受Java级别堆大小限制的影响。如果您达到了实际的容器内存限制,那么您的进程将立即终止,而没有机会捕获它并执行任何操作。