1

I have a spring boot application v2.4.3 in a container created using the official spring boot gradle plugin which uses buildpakcs to do so.

One of the layers is jvmkill:

[creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'

which is perfectly fine and it is adding the jvm arg for jvmkill properly

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)

The app is running in Kubernetes (AWS EKS) but when I got an OOM

java.lang.OutOfMemoryError: Java heap space

jvmkill kicks in, prints the heap dump and sends the kill signal

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                          ```

but the jvm is never killed. Inspecting the container, I notice that the app runs with PID 1 which cannot be killed (or signaled) from inside the container.

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

since all this has been built by the java buildpack itself, I would expect that it is aware that PID 1 cannot be killed and launch the app differently to work properly.

Is there something that I am missing or need to configure for buildpack jvmkill to work out-of-the-box?

Workarounds:

  1. If I were running the image directly with docker, I can use docker run --init to be able to signal the jvm process (my app runs with PID 7). Not valid for 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...
  1. I can shareProcessNamespace: true in my k8s spec to have it running with PID other than 1, but would be hard to justify due to security compliance requirements.

  2. I could just add the jvm flag -XX:+ExitOnOutOfMemoryError but then don't have the nice heap dump that jvmkill shows plus jvmkill thread creation failure coverage.

vcardenas
  • 11
  • 1

1 Answers1

0

Given the workaround mentioned in https://github.com/airlift/jvmkill#using-inside-docker-containers, you need something else to be pid 1. Kubernetes does not have the equiv of --init but if you include tini in your image, you can use it manually via the container command. You could also use sh similarly.

Also keep in mind this will only protect you from hitting the Java-level heap size limit. If you hit the actual container memory limit then your process is immediately terminated with no chance to catch it and do anything.

coderanger
  • 52,400
  • 4
  • 52
  • 75
  • You could use https://github.com/paketo-buildpacks/tini to get `tini` included in the image generated. You can also use the instructions here:https://buildpacks.io/docs/app-developer-guide/run-an-app/#user-provided-shell-process. Just copy & paste the start command generated by the builpacks (you can see it with `pack inspect `) and use that command. By using the instructions I linked to, that should launch your process in a shell, which will make it not PID1. You can use those instructions to run with `tini` as well. – Daniel Mikusa Jun 03 '21 at 15:48