MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

减少JVM Docker镜像大小的方法

2025-01-16 19:42 huorong 精选文章 3 ℃ 0 评论

这篇博客聚焦于优化JVM Docker镜像的大小。它探讨了多阶段构建、jlink、jdeps以及尝试不同基础镜像等各种技术。通过实施这些优化,部署可以更快,资源使用也可以得到优化。

问题

自Java 11起,不再提供预打包的JRE。因此,基本的Dockerfile如果没有任何优化,可能导致镜像大小较大。在没有提供JRE的情况下,有必要探索技术和优化方法来减小JVM Docker镜像的大小。

现在,让我们看一下我们应用的最简单版本的Dockerfile,并看看其中存在的问题。我们将在所有示例中使用的项目是Spring Petclinic。

我们项目的最简单Dockerfile如下:

注意:不要忘记构建你的JAR文件。

FROM eclipse-temurin:17
VOLUME /tmp
COPY target/spring-petclinic-3.1.0-SNAPSHOT.jar app.jar

在构建了我们项目的JAR文件之后,让我们构建我们的Dockerfile镜像,并比较我们的JAR文件和创建的Docker镜像的大小。

docker build -t spring-pet-clinic/jdk -f Dockerfile .
docker image ls spring-pet-clinic/jdk

# REPOSITORY              TAG       IMAGE ID       CREATED          SIZE
# spring-pet-clinic/jdk   latest    3dcd0ab89c3d   23 minutes ago   465MB

如果我们看一下SIZE列,我们会发现我们的Docker镜像大小为465MB!你可能会觉得这很大,但也许是因为我们的JAR文件很大?

为了验证这一点,让我们使用以下命令查看我们的JAR文件的大小:

ls -lh target/spring-petclinic-3.1.0-SNAPSHOT.jar | awk '{print $9, $5}'

# target/spring-petclinic-3.1.0-SNAPSHOT.jar 55M

根据我们命令的输出,你可以看到我们的JAR文件大小只有55MB。如果我们将其与构建的Docker镜像大小进行比较,我们的JAR文件几乎小了九倍!接下来,让我们继续分析原因以及如何使其更小。

为什么Docker镜像会很大,以及如何减小它们的大小?

在我们继续优化我们的Docker镜像之前,我们需要找出到底是什么导致它相对较大。为此,我们将使用一个名为Dive的工具,该工具用于探索Docker镜像、层内容,并发现缩小Docker/OCI镜像大小的方法。

要安装Dive,请按照他们README中的指南进行操作:

现在,让我们通过使用以下命令来探索层,找出为什么我们的Docker镜像如此大:dive spring-pet-clinic/jdk(在spring-pet-clinic/jdk处使用你的Docker镜像名称)。

它的输出可能会让人有些不知所措,但不用担心,我们将一起探索它的输出。对于我们的目的,我们主要感兴趣的是左上角的部分,即我们Docker镜像的层。我们可以使用“箭头”按钮在层之间进行导航。现在,让我们找出我们的Docker镜像由哪些层组成。

请记住,这些是从我们基本的Dockerfile构建的Docker镜像的层。

  1. 第一层是我们的操作系统,默认情况下是Ubuntu。
  2. 在接下来的一层中,它安装了tzdata、curl、wget、locales以及一些其他不同的工具,占用了50MB!
  3. 第三层,正如你从上面的截图中看到的那样,是我们整个Eclipse Temurin 17 JDK,占用了279MB,相当大。
  4. 最后一层是我们构建的JAR文件,占用了58MB。

现在我们了解了我们的Docker镜像由什么组成,我们可以看到我们的Docker镜像的一个很大的部分包括整个JDK以及诸如时区、区域设置和不同的实用工具等不必要的内容。

我们Docker镜像的第一个优化是使用Java 9中包含的jlink工具以及模块化。使用jlink,我们可以创建一个包含只有必要组件的自定义Java运行时,从而得到更小的最终镜像。

现在,让我们看一下我们新的Dockerfile,它包含了jlink工具,理论上应该比之前的更小。

# Example of custom Java runtime using jlink in a multi-stage container build
FROM eclipse-temurin:17 as jre-build

# Create a custom Java runtime
RUN $JAVA_HOME/bin/jlink \
         --add-modules ALL-MODULE-PATH \
         --strip-debug \
         --no-man-pages \
         --no-header-files \
         --compress=2 \
         --output /javaruntime

# Define your base image
FROM debian:buster-slim
ENV JAVA_HOME=/opt/java/openjdk
ENV PATH "${JAVA_HOME}/bin:${PATH}"
COPY --from=jre-build /javaruntime $JAVA_HOME

# Continue with your application deployment
RUN mkdir /opt/app
COPY target/spring-petclinic-3.1.0-SNAPSHOT.jar /opt/app/app.jar
CMD ["java", "-jar", "/opt/app/app.jar"]

为了理解我们新的Dockerfile是如何工作的,让我们逐步来看:

  • 我们在这个Dockerfile中使用了多阶段Docker构建,它由2个阶段组成。
  • 对于第一个阶段,我们使用了与之前Dockerfile中相同的基础镜像。
  • 同样,我们使用jlink工具来创建一个自定义的JRE,包括所有Java模块,使用—add-modules ALL-MODULE-PATH
  • 第二阶段使用了debian:buster-slim基础镜像,并设置了JAVA_HOME和PATH的环境变量。它将第一阶段创建的自定义JRE复制到镜像中。
  • 然后,Dockerfile创建了一个应用程序目录,将应用程序JAR文件复制到其中,并在容器启动时指定了运行Java应用程序的命令。

现在,让我们构建我们的容器镜像,看看它变得多小了。

docker build -t spring-pet-clinic/jlink -f Dockerfile_jlink .
docker image ls spring-pet-clinic/jlink

# REPOSITORY                TAG       IMAGE ID       CREATED       SIZE
# spring-pet-clinic/jlink   latest    e7728584dea5   1 hours ago   217MB

我们新的容器镜像大小为217MB,比之前的小了一半。

使用Java依赖分析工具(Jdeps)进一步减小容器镜像大小

如果我告诉你,我们的容器镜像的大小可以进一步减小呢?当与jlink配对使用时,你还可以使用Java依赖分析工具(jdeps),该工具首次在Java 8中引入,用于了解应用程序和库的静态依赖关系。

在我们之前的示例中,对于jlink的—add-modules参数,我们设置了ALL-MODULE-PATH,它会在我们自定义的JRE中添加所有现有的Java模块,显然,我们不需要包含每个模块。这样,我们可以使用jdeps来分析项目的依赖关系,并删除任何未使用的依赖项,进一步减小镜像大小。让我们看看如何在我们的Dockerfile中使用jdeps:

# Example of custom Java runtime using jlink in a multi-stage container build
FROM eclipse-temurin:17 as jre-build

COPY target/spring-petclinic-3.1.0-SNAPSHOT.jar /app/app.jar
WORKDIR /app

# List jar modules
RUN jar xf app.jar
RUN jdeps \
    --ignore-missing-deps \
    --print-module-deps \
    --multi-release 17 \
    --recursive \
    --class-path 'BOOT-INF/lib/*' \
    app.jar > modules.txt

# Create a custom Java runtime
RUN $JAVA_HOME/bin/jlink \
         --add-modules $(cat modules.txt) \
         --strip-debug \
         --no-man-pages \
         --no-header-files \
         --compress=2 \
         --output /javaruntime

# Define your base image
FROM debian:buster-slim
ENV JAVA_HOME=/opt/java/openjdk
ENV PATH "${JAVA_HOME}/bin:${PATH}"
COPY --from=jre-build /javaruntime $JAVA_HOME

# Continue with your application deployment
RUN mkdir /opt/server
COPY --from=jre-build /app/app.jar /opt/server/
CMD ["java", "-jar", "/opt/server/app.jar"]

即使不深入细节,你也可以看到我们的Dockerfile变得更大了。现在让我们分析每个部分以及它的职责:

  • 我们仍然使用多阶段Docker构建。
  • 复制我们构建的Java应用程序,并将WORKDIR设置为/app。
  • 解压JAR文件,使其内容对jdeps工具可访问。
  • 第二个RUN指令在提取的JAR文件上运行jdeps工具,分析其依赖关系并创建所需Java模块的列表。以下是每个选项的作用:--ignore-missing-deps :忽略任何缺失的依赖项,允许分析继续进行。--print-module-deps :指定分析应该打印模块依赖关系。--multi-release 17 :表示应用程序JAR与多个Java版本兼容,在我们的情况下是Java 17。--recursive :执行递归分析,以识别所有级别的依赖关系。--class-path 'BOOT-INF/lib/*' :为分析定义类路径,指示“jdeps”在JAR文件的“BOOT-INF/lib”目录中查找。app.jar > modules.txt :将“jdeps”命令的输出重定向到名为“modules.txt”的文件,其中将包含应用程序所需的Java模块列表。
  • 然后,我们将—add-modules jlink参数的ALL-MODULE-PATH值替换为$(cat modules.txt),以仅包含必要的模块
  • # Define your base image 部分与之前的Dockerfile中的相同。
  • # Continue with your application deployment 被修改为从上一个阶段复制出JAR文件。

唯一剩下的事情就是看看我们最新的Dockerfile使容器镜像缩小了多少:

docker build -t spring-pet-clinic/jlink_jdeps -f Dockerfile_jdeps .
docker image ls spring-pet-clinic/jlink_jdeps

# REPOSITORY                      TAG       IMAGE ID       CREATED        SIZE
# spring-pet-clinic/jlink_jdeps   latest    d24240594f1e   3 hours ago   184MB

因此,通过仅使用我们需要运行应用程序的模块,我们将容器镜像的大小减小了33MB,虽然不多,但仍然不错。

结论

让我们再次使用Dive来看看我们的Docker镜像在优化后变得多小。

在这种情况下,我们没有使用整个JDK,而是使用jlink工具构建了我们的自定义JRE,并使用了debian-slim基础镜像。这显著减小了我们的镜像大小。正如你所看到的,我们没有不必要的东西,比如时区、区域设置、大型操作系统和整个JDK。我们只包含我们使用和需要的内容。

Dockerfile_jlink

在这里,我们甚至进一步传递了我们的JRE所使用的Java模块,使构建的JRE更小,从而减小了整个最终镜像的大小。

Dockerfile_jdeps

总之,减小JVM Docker镜像的大小可以显著优化资源使用和加快部署速度。采用多阶段构建、jlink、jdeps以及尝试不同基础镜像等技术可以产生实质性的差异。尽管在某些情况下,大小的减小可能看起来微不足道,但累积效应可能是显著的,特别是在运行多个容器的环境中。因此,在任何应用程序开发和部署过程中,优化Docker镜像应该是一个关键的考虑因素。

Tags:docker 强制删除镜像

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言