How to set JVM arguments when running mvn spring-boot:build-image command in Spring Boot 2.3.0 to generate a docker image?

Viewed 5360

I am trying to build a docker image using the latest version of Spring Boot (2.3.0) . All I have to create an image now is run the command mvn:spring-boot:build-image . This will create a docker image for me. How do I set the JVM arguments (Max , Min heap sizes) in this case?

4 Answers

As of today, you can't set JVM arguments in spring-boot:build-image.

Spring boot build image uses Packeto internally and it accepts following 4 environment variables as mentioned in bellsoft-liberica GitHub.

"BP_JVM_VERSION" : "13.0.1",  
"BPL_JVM_HEAD_ROOM" : "10",
"BPL_JVM_LOADED_CLASS_COUNT" : "35", 
"BPL_JVM_THREAD_COUNT" : "10"

As alternate option, you can pass JVM arguments when you run the image.

docker run -p 8080:8080 --env JAVA_OPTS="-Xmx300m -Xms200m" -t youImageName

If using Kubernetes, you can configure JVM options at deployment level.

    spec:
      containers:
      - name: yourapp
        image: image path
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"    
        - name: BPL_JVM_HEAD_ROOM
          value: "2"  
        - name: BPL_JVM_LOADED_CLASS_COUNT
          value: "35"  
        - name: BPL_JVM_THREAD_COUNT
          value: "10"  
        - name: JAVA_OPTS
          value: >-
                -XX:ReservedCodeCacheSize=40M
                -XX:MaxMetaspaceSize=60M
                -Xlog:gc
                -Xms34m
                -Xmx40m
                -Xss256k
                -XX:MaxRAM=150M

There's a GitHub issue open in Spring boot repository discussing this Failed to change JVM arguments for buildpacked image

Build Back Environment Variables

I tried setting JVM arguments when I started using Spring Boot 2.3 & BuildPacks in November 2020, got nowhere and gave up / put it to one side.

Two weeks ago I picked it up again and purely by chance found this: https://github.com/paketo-buildpacks/environment-variables

Basically, you prefix your environment variable with BPE_APPEND_ and this triggers the Environment Variables Build Pack to append your value to the environment variable.

NB: JAVA_TOOL_OPTIONS is what you want here, not JAVA_OPTS.

I needed to attach a Java Agent to monitor our microservices and something like this build.gradle snippet was what worked:

bootBuildImage {
  environment = [
    'BPE_DELIM_JAVA_TOOL_OPTIONS' : ' ',
    'BPE_APPEND_JAVA_TOOL_OPTIONS' : '-javaagent:my-java-agent.jar'
    ]
}

I used BPE_DELIM_JAVA_TOOL_OPTIONS to make sure a space was added to the existing value of JAVA_TOOL_OPTIONS before my value was appended (the buildpack also allows you to override or prepend to existing value - see their README).

PS: my value was more like '-javaagent:my-java-agent-${some-dynamic-version}.jar', so I needed double quotes, but that made it a Gradle String which didn't work so I had to write this instead "-javaagent:my-java-agent-${some-dynamic-version}.jar".toString().

None of these answers quite get it right. Here's the full situation at the time of writing.

It's important to understand that there are two different times where code runs: at build time and at runtime.

  1. You can set env variables at build time using Spring Boot in your Maven or Gradle config. You just give it a list of env variables to set. See here https://docs.spring.io/spring-boot/docs/2.3.x/maven-plugin/reference/html/#build-image-customization. Those will only be set at build time though. Not what you want to set things like -Xmx.

  2. If you want env variables set at runtime, then it's not something Spring Boot or the buildpacks can control. It's not a failing of either, it's just that neither are involved in actually running your app image/containers.

    To pass env variables to your app image/containers at runtime, you need to use the facilities of whatever is running your app image, like Docker or Kubernetes. For Docker, it's just docker run -e foo=bar .... For Kubernetes, it's in the pod spec.

    FYI. Any env variable that is prefixed with BPL_ means it only applies at run time (the L is for launch, which is what buildpacks term the runtime environment). Thus it would only make sense to set those when your app runs.

  3. The environment variables buildpack, mentioned in the other answers, is a mix of the two. If you set env variables using option #1 above but prefixed in a certain way, this buildpack will see them and turn them into env variables that are embedded into the image. This will have the effect of setting them at runtime.

    This works well if you want to set default values on your images. Then users of your image won't need to set them unless they need to deviate from the default values you, the image author set. Having said that, you would never want to use this for anything that is sensitive or secret. This is because the env variable and value end up in the image and thus anyone with access to the image can see that value.

To answer the question:

How do I set the JVM arguments (Max , Min heap sizes) in this case?

  1. If you want to set default values that your image users can override, if needed, then you want the environment variables buildpack.

    Ex:

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <env>
                            <BPE_APPEND_JAVA_TOOL_OPTIONS>-Xss256k</BPE_APPEND_JAVA_TOOL_OPTIONS>
                            <BPE_DELIM_JAVA_TOOL_OPTIONS> </BPE_DELIM_JAVA_TOOL_OPTIONS>
                        </env>
                    </image>
                    <layers>
                        <enabled>true</enabled>
                    </layers>
                </configuration>
            </plugin>
    

    You'll see output like this:

    [INFO]     [creator]     Paketo Environment Variables Buildpack 3.1.1
    [INFO]     [creator]       https://github.com/paketo-buildpacks/environment-variables
    [INFO]     [creator]       Launch Configuration:
    [INFO]     [creator]         $BPE_<NAME>             prepend value to $NAME, delimiting with OS path list separator
    [INFO]     [creator]         $BPE_APPEND_<NAME>      append value to $NAME
    [INFO]     [creator]         $BPE_DEFAULT_<NAME>     set default value for $NAME
    [INFO]     [creator]         $BPE_DELIM_<NAME>       set delimeter to use when appending or prepending to $NAME
    [INFO]     [creator]         $BPE_OVERRIDE_<NAME>    set $NAME to value
    [INFO]     [creator]         $BPE_PREPEND_<NAME>     prepend value to $NAME
    [INFO]     [creator]       Environment Variables: Contributing to layer
    [INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.append
    

    When you run an instance of your image, the values you set should get passed through to the container, unless the user overrides them.

    > docker run -it apps/maven
    Setting Active Processor Count to 6
    Calculating JVM memory based on 9057276K available memory
    Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx8699636K -XX:MaxMetaspaceSize=88839K -XX:ReservedCodeCacheSize=240M (Total Memory: 9057276K, Thread Count: 50, Loaded Class Count: 13271, Headroom: 0%)
    Adding 129 container CA certificates to JVM truststore
    Spring Cloud Bindings Enabled
    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 -Xss256k -XX:ActiveProcessorCount=6 -XX:MaxDirectMemorySize=10M -Xmx8699636K -XX:MaxMetaspaceSize=88839K -XX:ReservedCodeCacheSize=240M -Dorg.springframework.cloud.bindings.boot.enable=true
    
  2. If you want to set the values, then just set them at runtime. For example: docker run -e JAVA_TOOL_OPTIONS '-Xss256k' ... or in your Kubernetes pod spec like this.

    apiVersion: v1
    kind: Pod
    metadata:
      name: my-cool-app
      labels:
        purpose: my-cool-app-label
    spec:
      containers:
      - name: my-cool-app
        image: my-cool-image
        env:
        - name: JAVA_TOOL_OPTIONS
          value: "-Xss256k"
    

    You'll see similar output when the container runs and Picked up JAVA_TOOL_OPTIONS: will show the values you set, -Xss256k from this example.

As @jeremyt suggests, you can override the value of this property using BPE_OVERRIDE_BPL_JVM_THREAD_COUNT build variable. Example:

bootBuildImage {
    environment('BPE_OVERRIDE_BPL_JVM_THREAD_COUNT', '150')
}

Sadly, as of now, BPE_DEFAULT_BPL_JVM_THREAD_COUNT does not work (the value is ignored). The downside of BPE_OVERRIDE_BPL_JVM_THREAD_COUNT is that it ignores the environment variable set for runtime (in docker run).

Related