[DOCKER] How to minimize Java images

This article uses a ** Spring Boot ** based ** Java ** application as an example to give you some common tips for minimizing a ** Java ** image.

background

With the spread of container technology, container-based applications are increasing. Containers are used a lot, but most container users may be ignoring the simple but important issue of container image size. This article briefly describes the need to simplify container images and presents some common tricks for minimizing Java images, using Spring Boot-based Java applications as an example.

Need to simplify container image

Simplification of the container image is very necessary. We'll talk about this in terms of both security and agility.

About security

By removing unnecessary components from the image, you can reduce the attack surface and security risk. Docker uses Seccomp to restrict operations inside the container, and AppArmor You can also use //docs.docker.com/engine/security/apparmor/?spm=a2c65.11461447.0.0.4c817eccQzbPjk) to set the security policy for your container. However, you need to be proficient in the security field to use them.

Agility

By simplifying the container image, you can speed up the deployment of containers. Suppose you have a sudden burst of access traffic and you need to increase the number of containers to handle the sudden increase in pressure. If some hosts do not contain the target image, you must first pull the image and then start the container. In this case, you can speed up the process and shorten the scale-up period by making the image smaller. Also, smaller images can be built faster, saving storage and transmission costs.

Common tips

To containerize your Java application, complete the following steps:

  1. Compile the Java source code and generate a JAR package.
  2. Move the JAR package and third party JAR dependencies to the proper location. The example used in this section is a Spring Boot-based Java application spring-boot-docker is. The non-optimized dockerfile used in this example is:
FROM maven:3.5-jdk-8
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package
ENTRYPOINT ["java","-jar","/usr/src/app/target/spring-boot-docker-1.0.0.jar"]

The application was created using Maven, and maven: 3.5.5-jdk-8 is specified as the base image of the dockerfile. The size of this image is 635MB. The size of the final image created this way is quite large at 719MB. The reason is that the base image is large and Maven downloads many JAR packages to build the final image.

Multi-stage build

You only need the Java Runtime Environment (JRE) to run Java applications. No Maven or Java Development Kit (JDK) compilation, debugging, or execution tools are required. Therefore, a simple optimization method is to separate the image that you create by compiling the Java source code from the image that runs your Java application. To do this, you need to maintain two dockerfile files before the release of Docker 17.05, which adds to the complexity of image building. From Docker 17.05, multiple in one docker file by multistage build function You can now use the FROM statement of. You can specify a different base image for each FROM statement and start a whole new image building process. You can choose to copy the product from the previous image building stage to another stage and leave only what you need in the final image. The optimized dockerfile is below It will be like.

FROM maven:3.5-jdk-8 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package

FROM openjdk:8-jre
ARG DEPENDENCY=/usr/src/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

The dockerfile uses maven: 3.5-jdk-8 as the build image for the first stage and ʻopenjdk: 8-jreas the base image for running Java applications. Only the.class` files compiled in the first stage are copied to the final image along with the dependencies of the third party JAR. As a result of the optimization, the size of the image has been reduced to 459MB.

Use a distroless image as the base image

The final image is smaller due to the multi-stage build, but 459MB is still too big. As a result of comprehensive analysis, it was found that the size of the base ʻopenjdk: 8-jre` is 443MB, which is too large. Therefore, as the next optimization step, we decided to reduce the size of the base image.

Google's open source project Distroless was developed to solve this problem. The Distroless image contains only the application and its run-time dependencies. These images do not include package managers, shells, or any other programs that you might find in a standard Linux distribution. Currently, Distroless is Java, [Python](https:: //github.com/GoogleContainerTools/distroless/blob/master/experimental/python2.7/README.md?spm=a2c65.11461447.0.0.4c817eccQzbPjk&file=README.md), [Node.js](https://github. com / GoogleContainerTools / distroless/blob/master/experimental/nodejs/README.md?spm=a2c65.11461447.0.0.4c817eccQzbPjk&file=README.md), .NET /master/experimental/dotnet/README.md?spm=a2c65.11461447.0.0.4c817eccQzbPjk&file=README.md) Provides a base image for applications that run in environments such as.

Dockerfile file using Distroless image is as follows It will be like.

FROM maven:3.5-jdk-8 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package

FROM gcr.io/distroless/java
ARG DEPENDENCY=/usr/src/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

The only difference between this dockerfile and the previous one is that the base image for running the application has changed from ʻopenjdk: 8-jre(443 MB) togcr.io/distroless/java` (119 MB). That is. The result is a final image size of 135MB.

The only inconvenience of using a distroless image is that the image does not contain a shell. You cannot use docker attach to attach standard inputs, standard outputs, and standard errors (or combinations of these three) to a running container for debugging. The distroless debug image (https://github.com/GoogleContainerTools/distroless?spm=a2c65.11461447.0.0.4c817eccQzbPjk#debug-images) provides a busybox shell. However, I have to repackage this image and deploy the container, which is not useful for containers deployed based on non-debug images. From a security point of view, this may be an advantage as an attacker cannot attack through the shell.

Use the alpine image as the base image

If you need to use docker attach and want to minimize the image size, you can use an Alpine image as the base image. Alpine The image is incredibly small, and the size of the base image is only about 4MB.

Dockerfile using alpine images is as follows Will be.

FROM maven:3.5-jdk-8 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package

FROM openjdk:8-jre-alpine
ARG DEPENDENCY=/usr/src/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

ʻOpenjdk: 8-jre-alpine` is built on alpine and includes the Java runtime. The image built with this dockerfile is 99.2 MB in size, which is smaller than the image built on the distroless image.

To attach to a running container, run the docker exec -ti <container_id> sh command.

Comparison of Distroless and Alpine

Both Distroless and Alpine can provide very small base images. Which one should I use in a production environment? If security is your top priority, distroless is recommended because packaged applications can only run binary files. If you value the size of the image, we recommend alpine.

Other tips

In addition to the above tips, you can further simplify the image size by performing the following operations.

  1. Combine multiple instructions in the dockerfile into one. This reduces the number of layers in the image and reduces the image size.
  2. Place large, stable content at the bottom of the image, and small, frequently changing content at the top. You cannot directly reduce the image size with this method. However, it takes full advantage of the image cache mechanism to speed up image building and container deployment. For tips on optimizing Dockerfiles, see Best Practices for Writing DockerfilesPlease refer to.

Overview

  1. Through a series of optimizations, the image size of Java applications has been reduced from 719MB to about 100MB. If your application runs in other environments, you can optimize it on a similar principle.
  2. For Java images, another tool provided by Google, jib, automates the complex image building process. You can process it and provide a simplified Java image. With it, you don't have to write a dockerfile or even install Docker.
  3. For containers such as distro-less, which are inconvenient for debugging, if you save the log centrally, it will be easier to trace and troubleshoot problems. For more information, see the article on technical best practices for container logging.

Recommended Posts

How to minimize Java images
[Java] How to use Map
[rails] How to post images
How to lower java version
[Java] How to use Map
How to uninstall Java 8 (Mac)
Java --How to make JTable
How to handle uploaded images
How to write java comments
How to use java class
[Java] How to use Optional ②
[Java] How to use removeAll ()
[Java] How to display Wingdings
[Java] How to use string.format
How to use Java Map
How to set Java constants
How to use Java variables
How to convert Java radix
[Java] How to implement multithreading
[Java] How to use Optional ①
How to initialize Java array
How to study Java Silver SE 8
How to use Java HttpClient (Get)
Studying Java # 6 (How to write blocks)
[Java] How to update Java on Windows
How to make a Java container
How to disassemble Java class files
How to use Java HttpClient (Post)
[Java] How to use join method
How to learn JAVA in 7 days
[Processing × Java] How to use variables
[Java] How to create a folder
How to decompile java class files
[Java] How to use LinkedHashMap class
[JavaFX] [Java8] How to use GridPane
How to write Java variable declaration
How to use class methods [Java]
[Java] How to use List [ArrayList]
How to use classes in Java?
How to name variables in Java
How to pass Oracle Java Silver
How to turn Iterator Dojo (Java)
java Eclipse How to debug javaScript
[Processing × Java] How to use arrays
How to make a Java array
How to use Java lambda expressions
[Java] How to use Math class
How to find Java prime numbers
How to use Java enum type
How to concatenate strings in java
How to make a Java calendar Summary
How to implement date calculation in Java
How to implement Kalman filter in Java
Multilingual Locale in Java How to use Locale
How to compile Java with VsCode & Ant
[Rails] How to upload images using Carrierwave
[Java] How to add data to List (add, addAll)
[Java] How to use the HashMap class
How to switch thumbnail images with JavaScript
[Java] How to calculate age using LocalDate
How to do base conversion in Java