Return to site

Java containers: What I wish I knew before I used it! By Elder Moraes, Java Champion

· mashup,java

  1. Long build time
  2. Huge image size
  3. Hard maintanability
  4. Resources allocation

How to avoid long build time

It is all about making the best use of caching.

When you build a container image, the engine that you are using will deal with some caches.

So let's look at some practices to make that better.

Usually when you build a docker file, you build it like this:

#time docker build -t jc-cache01-do -f 01.02.D0.Dockerfile ../quarkus/target

FROM debian:stretch

COPY lib/* /deployment/lib/

COPY *-runner.jar /deployment/

RUN apt-get update

RUN apt-get -y install openjdk-8-jdk ssh vim

CMD ["java", "-jar", "/deployment/quarkus-1.0.0-SNAPSHOT-runner.jar"]

From, copy some files, and run some system updates, and your cmd, alright?

cachingOrder

Let's launch this docker file! It is quite long (1min44s)... Let's make this shorter.

The main problem is that I am copying the file of my application before running the system update.

The drawback of that is that the system update (apt-get) does not change a lot, while my app files do change a lot (lib).

Be aware that each line of the docker file is handled by cache.

If you re-execute the build without changing anything, it will be done instantly.

Now, repackage your application code, the cache of its related docker file lines get broken, breaking also all the caches coming next.

So the idea is to order your docker files lines by the changeable trait of your line: the less changeable at the begining and the most changeable in the end.

#time docker build -t jc-cache01-do -f 01.02.D0.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update

RUN apt-get -y install openjdk-8-jdk ssh vim

COPY lib/* /deployment/lib/

COPY *-runner.jar /deployment/

CMD ["java", "-jar", "/deployment/quarkus-1.0.0-SNAPSHOT-runner.jar"]

You get the picture! Nice!

By inverting the system update lines with application lines, it tooks 99%% less time. (From 1min and a half to 1 sec). Good job!

specificCaching

Other point: don't use star in your docker file, 

else any new file in the directory targeted by the star will dodge the cache use losing precious time

(even if it is one second, one second times numerous builds is too worthy to be lost).

So the previous docker file becomes:

#time docker build -t jc-cache02-do -f 01.02.D0.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update

RUN apt-get -y install openjdk-8-jdk ssh vim

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/

CMD ["java", "-jar", "/deployment/quarkus-1.0.0-SNAPSHOT-runner.jar"]

cachingGroupUnits

Next point: Use gp units (group unit) to reduce the number of docker file caching

Giving this code

#time docker build -t jc-cache03-do -f 01.02.D0.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update \

  && apt-get -y install \

      openjdk-8-jdk ssh vim

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/

CMD ["java", "-jar", "/deployment/quarkus-1.0.0-SNAPSHOT-runner.jar"]

This make the life of the container easier to manage. Do that for docker files lines that do not change frequently.

In above snippet, we get to 4 layer caching rather than the initial five.

How to get rid of huge image size

First case, let's trim the docker file.

Given this docker file:

#time docker build -t jc-size01-dont -f 01.01.D0NT.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update \

  && apt-get -y install \

      openjdk-8-jdk ssh vim

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/app.jar

CMD ["java", "-jar", "0-SNAPSHOT-runner.jar"]

We are installing open jdk, ssh and vim; but why ssh and vim in a container: for debugging reasons.

Okay, so it is not required every times, so please don't install it because it will increase the size of your image.

unnecessary dependencies

Let's remove "ssh vim" from docker file.

Other tip: use the flag --no-install-recommends, this flag block the apt-get recomnmendations install because it will incresase the size of the container.

Giving the following doker file:

#time docker build -t jc-size01-dont -f 01.02.DO.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update \

  && apt-get -y install --no-install-recommends\

      openjdk-8-jdk

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/app.jar

CMD ["java", "-jar", "0-SNAPSHOT-runner.jar"]

Doing so, you get from 662Mb to 504Mb. Greetings! Just by removing "ssh", "vim", and not installing recommendations thanks to "--no-install-recommends".

Be aware, that every megabyte counts because you will not work with only one container but hundreds of containers.

packageManagerCacheRemoval

Next case, let's remove the cache of the apt-get package manager by adding "&& rm -rf /var/lib/apt/lists/*".

This cache is useless because it is not a machine you will be maintaining.

FYI, this is not the cache of the container but the apt-get cache inside Linux, so just remove it.

So with this new docker file:

#time docker build -t jc-size02-do -f 02.02.DO.Dockerfile ../quarkus/target

FROM debian:stretch

RUN apt-get update \

  && apt-get -y install --no-install-recommends\

      openjdk-8-jdk \

 && rm -rf /var/lib/apt/lists/*

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/app.jar

CMD ["java", "-jar", "0-SNAPSHOT-runner.jar"]

we get from 504Mb to 488Mb. Yippee Ki Yay!

toolsAndFrameworkOptimizer

Use quarkus because Quarkus is built for reducing application.

Use GraalVM, especially for native applications.

How to stay away from hard maintainability

officialImagesUse

Use official image, this give this docker file:

#time docker build -t jc-maintain01-do -f 01.02.DO.Dockerfile ../quarkus/target

FROM openjdk

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/app.jar

CMD ["java", "-jar", "0-SNAPSHOT-runner.jar"]

Using official image brings a lot of good practices, best practices and, patches.

This docker file is easier to maintain.

specificTagsUse

Be specific also on the official image you use by defining its tag:

#time docker build -t jc-maintain03-do -f 03.01.DO.Dockerfile ../quarkus/target

FROM openjdk:11-jre-slim

COPY lib/* /deployment/lib/

COPY quarkus-1.0.0-SNAPSHOT-runner.jar /deployment/app.jar

CMD ["java", "-jar", "0-SNAPSHOT-runner.jar"]

This will avoid you unexpected behavior from change on the base official image.

Here the change of title of official image nakes your image jump from  639Mb to 215Mb. Outstanding!

How to manage resources allocation

For Java 8u121 and before

  • ParallelGCThreads 
  • helps to limit the cpu usage of a container.
  • UseCGroupMemoryLimitForHeap

: JVM uses the cgroups limits to calculate memory defaults.

  • MaxRAMFraction

: percentage of available RAM that can be used.

For Java 8u191 and Java 10

  • InitialRAMPercentage
  • : initial percentage of heap allocation
  • MaxRAMPercentage

: maximum percentage of heap allocation

  • MiniRAMPercentage

: minimum percentage of heap allocation

 of CPUs is calculated from container allocation by default (JDK-8196595)

For Java 11

  • -
  • XshowSettings 
  • (Container Metrics): display the system or container configuration
  • JDK-8197867

: improve CPU calculations for both containers and JVM hotspot (see PreferContainerQuotaForCPUCount)

For Java 12 and 13

  • jhsdb now can be attached to Java processes running containers 
  • (JDK-8205992)
  • Container support improved for Java Flight Recorder

 (JDK-8203359)

  • Improve systemd slice memory limit support

 (JDK-8217338)

For Java 14

  • JFR Event Streaming
  • : expose JDK Flight Recorder data for continuous monitoring (easier for observability in clusters)
  • Packaging Tool

: tool for packaging self-contained Java applications (incubator)

  • Yes, Java and containers can get along!
  • Be intentional when building your Dockerfiles -- know what you are doing --
  • Better start with Java 11+
  • If you really need 8 (why?), be extra cautious