Skip to content

Latest commit

 

History

History
413 lines (331 loc) · 14.5 KB

ch03-build-image-java-9.adoc

File metadata and controls

413 lines (331 loc) · 14.5 KB

Build a Docker Image with JDK 9

PURPOSE: This chapter explains how to create a Docker image with JDK 9.

The prior chapter explained how, in general, to build a Docker image with Java. This chapter expands on this topic and focuses on JDK 9 features.

Create a Docker Image using JDK 9

Create a new directory, for example docker-jdk9.

In that directory, create a new text file jdk-9-debian-slim.Dockerfile. Use the following contents:

# A JDK 9 with Debian slim
FROM debian:stable-slim
# Download from http://jdk.java.net/9/
# ADD http://download.java.net/java/GA/jdk9/9/binaries/openjdk-9_linux-x64_bin.tar.gz /opt
ADD openjdk-9_linux-x64_bin.tar.gz /opt
# Set up env variables
ENV JAVA_HOME=/opt/jdk-9
ENV PATH=$PATH:$JAVA_HOME/bin
CMD ["jshell", "-J-XX:+UnlockExperimentalVMOptions", \
               "-J-XX:+UseCGroupMemoryLimitForHeap", \
               "-R-XX:+UnlockExperimentalVMOptions", \
               "-R-XX:+UseCGroupMemoryLimitForHeap"]

This image uses debian slim as the base image and installs the OpenJDK build of JDK for linux x64 (see the setup section for how to download this into the current directory).

The image is configured by default to run jshell the Java REPL. Read more JShell at Introduction to JShell. The experimental flag -XX:+UseCGroupMemoryLimitForHeap is passed to the REPL process (the frontend Java process managing user input and the backend Java process managing compilation). This option will ensure container memory constraints are honored.

Build the image using the command:

docker image build -t jdk-9-debian-slim -f jdk-9-debian-slim.Dockerfile .

List the images available using docker image ls:

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
jdk-9-debian-slim       latest              023f6999d94a        4 hours ago         400MB
debian                  stable-slim         d30525fb4ed2        4 days ago          55.3MB

Other images may be shown as well but we are interested in these two images for now. The large difference in size is attributed to JDK 9, which is larger in size than JDK 8 because it also explicitly provides Java modules that we shall see more of later on in this chapter.

Run the container using the command:

docker container run -m=200M -it --rm jdk-9-debian-slim

to see the output:

INFO: Created user preferences directory.
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell>

Query the available memory of the Java process by typing the following expression into the Java REPL:

Runtime.getRuntime().maxMemory() / (1 << 20)

to see the output:

jshell> Runtime.getRuntime().maxMemory() / (1 << 20)
$1 ==> 100

Notice that the Java process is honoring memory constraints (see the --memory of docker container run) and will not allocate memory beyond that specified for the container.

In a future release of the JDK it will no longer be necessary to specify an experimental flag (-XX:+UnlockExperimentalVMOptions) once the mechanism by which memory constraints are efficiently detected is stable.

JDK 9 supports the set CPUs constraint (see the --cpuset-cpus of docker container run) but does not currently support other CPU constraints such as CPU shares. This is ongoing work tracked in the OpenJDK project.

Note: the support for CPU sets and memory constraints have also been backported to JDK 8 release 8u131 and above.

Type Ctrl + D to exit out of jshell.

To list all the Java modules distributed with JDK 9 run the following command:

docker container run -m=200M -it --rm jdk-9-debian-slim java --list-modules

This will show an output:

java.activation@9
java.base@9
java.compiler@9
java.corba@9
java.datatransfer@9
java.desktop@9
java.instrument@9
java.logging@9
java.management@9
java.management.rmi@9
java.naming@9
java.prefs@9
java.rmi@9
java.scripting@9
java.se@9
java.se.ee@9
java.security.jgss@9
java.security.sasl@9
java.smartcardio@9
java.sql@9
java.sql.rowset@9
java.transaction@9
java.xml@9
java.xml.bind@9
java.xml.crypto@9
java.xml.ws@9
java.xml.ws.annotation@9
jdk.accessibility@9
jdk.aot@9
jdk.attach@9
jdk.charsets@9
jdk.compiler@9
jdk.crypto.cryptoki@9
jdk.crypto.ec@9
jdk.dynalink@9
jdk.editpad@9
jdk.hotspot.agent@9
jdk.httpserver@9
jdk.incubator.httpclient@9
jdk.internal.ed@9
jdk.internal.jvmstat@9
jdk.internal.le@9
jdk.internal.opt@9
jdk.internal.vm.ci@9
jdk.internal.vm.compiler@9
jdk.jartool@9
jdk.javadoc@9
jdk.jcmd@9
jdk.jconsole@9
jdk.jdeps@9
jdk.jdi@9
jdk.jdwp.agent@9
jdk.jlink@9
jdk.jshell@9
jdk.jsobject@9
jdk.jstatd@9
jdk.localedata@9
jdk.management@9
jdk.management.agent@9
jdk.naming.dns@9
jdk.naming.rmi@9
jdk.net@9
jdk.pack@9
jdk.policytool@9
jdk.rmic@9
jdk.scripting.nashorn@9
jdk.scripting.nashorn.shell@9
jdk.sctp@9
jdk.security.auth@9
jdk.security.jgss@9
jdk.unsupported@9
jdk.xml.bind@9
jdk.xml.dom@9
jdk.xml.ws@9
jdk.zipfs@9

In total there should be 75 modules:

$ docker container run -m=200M -it --rm jdk-9-debian-slim java --list-modules | wc -l
      75

Create a Docker Image using JDK 9 and Alpine Linux

Instead of debian as the base image it is possible to use Alpine Linux with an early access build of JDK 9 that is compatible with the muslc library shipped with Alpine Linux.

Create a new text file jdk-9-alpine.Dockerfile. Use the following contents:

# A JDK 9 with Alpine Linux
FROM alpine:3.6
# Add the musl-based JDK 9 distribution
RUN mkdir /opt
# Download from http://jdk.java.net/9/
# ADD http://download.java.net/java/jdk9-alpine/archive/181/binaries/jdk-9-ea+181_linux-x64-musl_bin.tar.gz
ADD jdk-9-ea+181_linux-x64-musl_bin.tar.gz /opt
# Set up env variables
ENV JAVA_HOME=/opt/jdk-9
ENV PATH=$PATH:$JAVA_HOME/bin
CMD ["jshell", "-J-XX:+UnlockExperimentalVMOptions", \
               "-J-XX:+UseCGroupMemoryLimitForHeap", \
               "-R-XX:+UnlockExperimentalVMOptions", \
               "-R-XX:+UseCGroupMemoryLimitForHeap"]

This image uses alpine 3.6 as the base image and installs the OpenJDK build of JDK for Alpine Linux x64 (see the Setup Environments chapter for how to download this into the current directory).

The image is configured in the same manner as for the debian-based image.

Build the image using the command:

docker image build -t jdk-9-alpine -f jdk-9-alpine.Dockerfile .

List the images available using docker image ls:

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
jdk-9-debian-slim       latest              023f6999d94a        4 hours ago         400MB
jdk-9-alpine            latest              f5a57382f240        4 hours ago         356MB
debian                  stable-slim         d30525fb4ed2        4 days ago          55.3MB
alpine                  3.6                 7328f6f8b418        3 months ago        3.97MB

Notice the difference in image sizes. Alpine Linux by design has been carefully crafted to produce a minimal running OS image. A cost of such a design is an alternative standard library musl libc that is not compatible with the C standard library (libc). As a result the JDK requires modifications to run on Alpine Linux. Such modifications have been proposed by the OpenJDK Portola Project.

Create a Docker Image using JDK 9 and a Java application

Clone the GitHib project https://github.com/PaulSandoz/helloworld-java-9 that contains a simple Java 9-based project:

git clone https://github.com/PaulSandoz/helloworld-java-9.git

(If you have a github account you may wish to fork it and then clone the fork so you can make modifications.)

Enter the directory helloworld-java-9 and build the project from within a running Docker container with JDK 9 installed:

docker container run --volume $PWD:/helloworld-java-9 --workdir /helloworld-java-9 \
    -it --rm openjdk:9-jdk-slim \
    ./mvnw package

(If you have JDK 9 installed locally on the host system you can build directly with ./mvnw package.)

In this case we are using the openjdk:9-jdk-slim on Docker hub that has been configured to work with SSL certificates so that the maven wrapper tool can successfully download the maven tool. This image is not produced or in anyway endorsed by the OpenJDK project (unlike the JDK 9 distributions that were previously required). It is anticipated that future releases of the JDK from the OpenJDK project will have root CA certificates (see issue JDK-8189131)

To build Docker image for this application use the file helloworld-jdk-9.Dockerfile from the checked out repo to build your image. The contents of the file are shown below:

# Hello world application with JDK 9 and Debian slim
FROM jdk-9-debian-slim
COPY target/helloworld-1.0-SNAPSHOT.jar /opt/helloworld/helloworld-1.0-SNAPSHOT.jar
# Set up env variables
CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \
  -cp /opt/helloworld/helloworld-1.0-SNAPSHOT.jar org.examples.java.App

Build a Docker image containing the simple Java application based of the Docker image jdk-9-debian-slim:

docker image build -t helloworld-jdk-9 -f helloworld-jdk-9.Dockerfile .

List the images available using docker image ls:

REPOSITORY              TAG                 IMAGE ID            CREATED              SIZE
helloworld-jdk-9        latest              eb0539e9529a        19 seconds ago       400MB
jdk-9-debian-slim       latest              023f6999d94a        5 hours ago          400MB
jdk-9-alpine            latest              f5a57382f240        5 hours ago          356MB
openjdk                 9-jdk-slim          6dca67f4790e        3 days ago           372MB
debian                  stable-slim         d30525fb4ed2        4 days ago           55.3MB
alpine                  3.6                 7328f6f8b418        3 months ago         3.97MB

Notice how large the application image helloworld-jdk-9.

Run the jdeps tool to see what modules the application depends on:

docker container run -it --rm helloworld-jdk-9 jdeps --list-deps /opt/helloworld/helloworld-1.0-SNAPSHOT.jar

and observe that the application only depends on the java.base module.

Reduce the size of a Docker Image using JDK 9 and a Java application

The Java application is extremely simple and as a result uses very little of the functionality shipped with JDK 9 distribution, specifically the application only depends on functionality present in the java.base module. We can create a custom Java runtime that only contains the java.base module and include that in application Docker image.

Create a custom Java runtime that is small and only contains the java.base module:

docker container run --rm \
  --volume $PWD:/out \
  jdk-9-debian-slim \
  jlink --module-path /opt/jdk-9/jmods \
    --verbose \
    --add-modules java.base \
    --compress 2 \
    --no-header-files \
    --output /out/target/openjdk-9-base_linux-x64

This command exists as create-minimal-java-runtime.sh script in the repo earlier checked out from helloworld-java-9.

The JDK 9 tool jlink is used to create the custom Java runtime. Read more jlink in the Tools Reference. The tool is executed from with the container containing JDK 9 and directory where the modules reside, /opt/jdk-9/jmods, is declared in the module path. Only the java.base module is selected.

The custom runtime is output to the target directory:

$ du -k target/openjdk-9-base_linux-x64/
24      target/openjdk-9-base_linux-x64//bin
12      target/openjdk-9-base_linux-x64//conf/security/policy/limited
8       target/openjdk-9-base_linux-x64//conf/security/policy/unlimited
24      target/openjdk-9-base_linux-x64//conf/security/policy
68      target/openjdk-9-base_linux-x64//conf/security
76      target/openjdk-9-base_linux-x64//conf
44      target/openjdk-9-base_linux-x64//legal/java.base
44      target/openjdk-9-base_linux-x64//legal
72      target/openjdk-9-base_linux-x64//lib/jli
16      target/openjdk-9-base_linux-x64//lib/security
19824   target/openjdk-9-base_linux-x64//lib/server
31656   target/openjdk-9-base_linux-x64//lib
31804   target/openjdk-9-base_linux-x64/

To build Docker image for this application use the file helloworld-jdk-9-base.Dockerfile from the checked out repo. The contents of the file are shown below:

# Hello world application with custom Java runtime with just the base module and Debian slim
FROM debian:stable-slim
COPY target/openjdk-9-base_linux-x64 /opt/jdk-9
COPY target/helloworld-1.0-SNAPSHOT.jar /opt/helloworld/helloworld-1.0-SNAPSHOT.jar
# Set up env variables
ENV JAVA_HOME=/opt/jdk-9
ENV PATH=$PATH:$JAVA_HOME/bin
CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \
  -cp /opt/helloworld/helloworld-1.0-SNAPSHOT.jar org.examples.java.App

Build a Docker image containing the simple Java application based of the Docker image debian:stable-slim:

docker image build -t helloworld-jdk-9-base -f helloworld-jdk-9-base.Dockerfile .

List the images available using docker image ls:

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
helloworld-jdk-9-base   latest              7052483fdb77        24 seconds ago      87.7MB
helloworld-jdk9         latest              eb0539e9529a        17 minutes ago      400MB
jdk-9-debian-slim       latest              023f6999d94a        5 hours ago         400MB
jdk-9-alpine            latest              f5a57382f240        5 hours ago         356MB
openjdk                 9-jdk-slim          6dca67f4790e        3 days ago          372MB
debian                  stable-slim         d30525fb4ed2        4 days ago          55.3MB
alpine                  3.6                 7328f6f8b418        3 months ago        3.97MB
[source, text]

The helloworld-jdk-9-base is much smaller and could be reduced further if Alpine Linux was used instead of Debian Slim.

A realistic application will depend on more JDK modules but it’s still possible to significantly reduce the Java runtime to only the required modules (for example many applications will not require Corba or RMI nor the compiler tools).