Based on Java in a World of Containers, which was given in Japanese at the Oracle Groundbreakers APAC Tour in Tokyo on 11/13. I am writing in.
The figure below is a module graph of java9 or later.
When writing an application in Java, it is rare to use everything in this module graph. For example, java.xml, corba, javaws are often unnecessary.
If you create a Docker image with Linux and JDK without thinking about it with Docker, it will be quite large. Therefore, it seems that containers and java are often said to be incompatible. Large capacity, heavy, slow startup, etc. Still, Java's features are also ideal for containers. There are many benefits such as runtime, hardware and OS independence, JVM-guaranteed security compatibility, guaranteed stable execution when the environment changes, ecosystem, and the best choice of containers. .. You can improve the JDK that is too heavy by default by modularizing the JDK itself with Jigsaw and organizing the dependencies.
Dockerfile
The OS creates an image with ubuntu: latest. At first, let's create an environment and play Hello World with the default full size.
Dockerfile
FROM ubuntu:latest
ADD openjdk-11.0.1_linux-x64_bin.tar.gz /opt/jdk/
ADD HelloWorld.class HelloWorld.class
ENV PATH /opt/jdk/jdk-11.0.1/bin:$PATH
CMD [ "java", "-showversion", "HelloWorld" ]
Build and run the image.
$ docker build -t helloworld-java .
$ docker run --rm helloworld-java
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
Hello World!
Let's take a look at the created image.
$ docker images helloworld-java
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld-java latest 682ae9922b5b 2 minutes ago 396MB
There is about 400MB.
Create a custom JRE using jdeps and jlink. You can see the module dependencies by using jdeps. Let's take a look at the Hello World module.
$ jdeps --list-deps HelloWorld.class
java.base
Since the content is Hello World, there are few dependencies. At the HelloWorld level, it seems that you are only using the java.base module.
Let's create a custom JRE with jlink.
jlink --compress=2 --module-path $JAVA_HOME/jmods --add-modules java.base --output jre-min
Since we are only using java.base, add-modules only this is specified. After execution, jre-min is created. In this environment, Hello World is the minimum required to run. The capacity was also 27MB.
python
.
├── Dockerfile
├── HelloWorld.class
├── HelloWorld.java
├── hello.jar
├── jre-min
│ ├── bin
│ │ ├── java
│ │ └── keytool
│ ├── conf
│ │ ├── net.properties
│ │ └── security
│ │ ├── java.policy
│ │ ├── java.security
│ │ └── policy
│ │ ├── README.txt
│ │ ├── limited
│ │ │ ├── default_US_export.policy
│ │ │ ├── default_local.policy
│ │ │ └── exempt_local.policy
│ │ └── unlimited
│ │ ├── default_US_export.policy
│ │ └── default_local.policy
│ ├── include
│ │ ├── classfile_constants.h
│ │ ├── darwin
│ │ │ └── jni_md.h
│ │ ├── jni.h
│ │ ├── jvmti.h
│ │ └── jvmticmlr.h
│ ├── legal
│ │ └── java.base
│ │ ├── COPYRIGHT
│ │ ├── LICENSE
│ │ ├── aes.md
│ │ ├── asm.md
│ │ ├── c-libutl.md
│ │ ├── cldr.md
│ │ ├── icu.md
│ │ ├── public_suffix.md
│ │ └── unicode.md
│ ├── lib
│ │ ├── classlist
│ │ ├── jli
│ │ │ └── libjli.dylib
│ │ ├── jrt-fs.jar
│ │ ├── jspawnhelper
│ │ ├── jvm.cfg
│ │ ├── libjava.dylib
│ │ ├── libjimage.dylib
│ │ ├── libjsig.dylib
│ │ ├── libnet.dylib
│ │ ├── libnio.dylib
│ │ ├── libosxsecurity.dylib
│ │ ├── libverify.dylib
│ │ ├── libzip.dylib
│ │ ├── modules
│ │ ├── security
│ │ │ ├── blacklisted.certs
│ │ │ ├── cacerts
│ │ │ ├── default.policy
│ │ │ └── public_suffix_list.dat
│ │ ├── server
│ │ │ ├── Xusage.txt
│ │ │ ├── libjsig.dylib
│ │ │ └── libjvm.dylib
│ │ └── tzdb.dat
│ └── release
└── openjdk-11.0.1_linux-x64_bin.tar.gz
Let's run Hello World in this minimal environment.
$ jre-min/bin/java -showversion HelloWorld
java version "11.0.1" 2018-10-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.1+13-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)
Hello World!
Let's create a custom JRE with jlink and build it.
Dockerfile
FROM ubuntu:latest AS build
ADD openjdk-11.0.1_linux-x64_bin.tar.gz /opt/jdk/
ENV PATH /opt/jdk/jdk-11.0.1/bin:$PATH
RUN ["jlink", "--compress=2", "--module-path", "/opt/jdk/jdk-11/jmods", "--add-modules", "java.base", "--output", "/linked"]
FROM ubuntu:latest
COPY --from=build /linked /opt/jdk/
ENV PATH=$PATH:/opt/jdk/bin
ADD HelloWorld.class /
CMD [ "java", "-showversion", "HelloWorld" ]
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld-java latest e71329542c40 2 minutes ago 123MB
$ docker run -it helloworld-java
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
Hello World!
The size is less than a quarter of the previous one.
We have the minimum required Java, but we still have 123MB. If only the method was used earlier, only Java could be made lighter, so we will also modify the OS. If you just want to run ava, ubuntu is too luxurious. Therefore, use alpine linux to create the minimum required linux environment. alpine is a lightweight Linux based on musl libc and BusyBox (it really doesn't contain anything ...). The minimum configuration seems to be lighter than 5MB. Refer to alpine-based Java environment Dockerfile published by AdoptOpenJDK Create an image with minimal configuration.
Dockerfile
FROM adoptopenjdk/openjdk11:alpine-slim AS jlink
RUN ["jlink", "--compress=2", \
"--module-path", "/opt/java/openjdk/jmods", \
"--add-modules", "java.base", \
"--output", "/jlinked"]
FROM alpine
RUN apk --update add --no-cache ca-certificates curl openssl binutils xz \
&& GLIBC_VER="2.28-r0" \
&& ALPINE_GLIBC_REPO="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" \
&& GCC_LIBS_URL="https://archive.archlinux.org/packages/g/gcc-libs/gcc-libs-8.2.1%2B20180831-1-x86_64.pkg.tar.xz" \
&& GCC_LIBS_SHA256=e4b39fb1f5957c5aab5c2ce0c46e03d30426f3b94b9992b009d417ff2d56af4d \
&& ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.9-1-x86_64.pkg.tar.xz" \
&& ZLIB_SHA256=bb0959c08c1735de27abf01440a6f8a17c5c51e61c3b4c707e988c906d3b7f67 \
&& curl -Ls https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
&& curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/${GLIBC_VER}.apk \
&& apk add /tmp/${GLIBC_VER}.apk \
&& curl -Ls ${GCC_LIBS_URL} -o /tmp/gcc-libs.tar.xz \
&& echo "${GCC_LIBS_SHA256} /tmp/gcc-libs.tar.xz" | sha256sum -c - \
&& mkdir /tmp/gcc \
&& tar -xf /tmp/gcc-libs.tar.xz -C /tmp/gcc \
&& mv /tmp/gcc/usr/lib/libgcc* /tmp/gcc/usr/lib/libstdc++* /usr/glibc-compat/lib \
&& strip /usr/glibc-compat/lib/libgcc_s.so.* /usr/glibc-compat/lib/libstdc++.so* \
&& curl -Ls ${ZLIB_URL} -o /tmp/libz.tar.xz \
&& echo "${ZLIB_SHA256} /tmp/libz.tar.xz" | sha256sum -c - \
&& mkdir /tmp/libz \
&& tar -xf /tmp/libz.tar.xz -C /tmp/libz \
&& mv /tmp/libz/usr/lib/libz.so* /usr/glibc-compat/lib \
&& apk del binutils \
&& rm -rf /tmp/${GLIBC_VER}.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*
COPY --from=jlink /jlinked /opt/jdk/
ADD HelloWorld.class /
CMD ["/opt/jdk/bin/java", "-showversion", "HelloWorld"]
$ docker build -t helloworld-java .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld-java latest 407614883e2b 38 seconds ago 52.1MB
It is less than half of the previous one. It is about 1/8 of the original one.
Of course it can be executed
$ docker run -it helloworld-java
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.1+13)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.1+13, mixed mode)
Hello World!
It seems that containers and java are often said to be incompatible with each other due to their large capacity, heavy weight, and slow startup. After JDK9, you can create a dedicated environment with jdeps and jlink. Since it is possible to create the minimum necessary environment, it is possible to create a light container. By modifying the OS, you can create an environment that is even lighter and you like.
Recommended Posts