This article uses a ** Spring Boot ** based ** Java ** application as an example to give you some common tips for minimizing a ** Java ** image.
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.
Simplification of the container image is very necessary. We'll talk about this in terms of both security and agility.
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.
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.
To containerize your Java application, complete the following steps:
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.
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.
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) to
gcr.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.
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.
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.
In addition to the above tips, you can further simplify the image size by performing the following operations.
Recommended Posts