转载

不要把大型 JAR 包放进 Docker 镜像

在 Docker 容器里放大型 JAR 包是对存储空间、网络带宽和时间的一种浪费。所幸的是,我们可以利用 Docker 镜像的分层机制和注册表缓存来创建增量式的小工件,甚至可以将一个工件的大小从 75MB 减到 1MB。值得一提的是,现在有一个 Maven 和 Gradle 插件可以处理这些事情。

不要把大型 JAR 包放进 Docker 镜像

问题:大型 JAR 包里的依赖项

Docker 镜像的分层机制非常强大。如果你所有的应用程序都使用了相同的基础镜像(比如 openjdk:11.0.4-jre-slim),Docker 重用了 OS 和 JRE 的一些层,这样就可以节省 Docker 注册表的存储空间,上传和下载镜像的速度也更快了,因为只需要传输更少的文件量(Docker 只会将新的层传输到注册表中)。

可惜的是,很多应用程序并没有很好地利用这个强大的机制,因为他们把大型的 JAR 包塞进了 Docker 镜像里。

不要把大型 JAR 包放进 Docker 镜像

每个新版本都会创建一个 72MB 的新层

我们假设将一个 Spring Boot 应用程序打成一个大型的 JAR 包。这个 JAR 包有 72MB,并在 Dockerfile 的最后一行将它加到镜像里。也就是说,每个新版本都需要 72MB 的存储空间,而且要上传到注册表中,或则从注册表中下载下来。

现在来看一下这个 72MB 的镜像:

不要把大型 JAR 包放进 Docker 镜像

一个大型的 JAR 包,大部分东西很少发生改动,但每次都会被拷贝到新工件中

一个大型的 JAR 包包含三个部分:

  • 依赖项:它们占了很大的一部分比例,但很少发生改动。大多数时候我们只会修改我们的代码,很少会去修改依赖项。但依赖项每次都会被拷贝到发布版本中。
  • 资源文件:这个问题跟依赖项差不多。虽然资源文件(HTML、CSS、图像、配置文件,等等)比依赖项更经常发生改动,但比起代码还是相对少一些。它们也会被拷贝到发布版本中。
  • 代码:代码只占 JAR 包很小的一部分(通常 300KB 到 2MB),但会经常发生改动。

经常发生改动的代码只有几 MB,但每次都需要拷贝所有的依赖项和资源文件,这是对存储空间、带宽和时间的一种浪费。

如果需要为每个 git 提交创建一个可部署的镜像,那浪费的空间就更多了。持续交付可能需要这么做,但这样做浪费了大量的空间,因为每一次提交都会占用额外的 72MB 空间。

有哪些有用的工具可用来分析 Docker 镜像和可视化大型 JAR 对 Docker 镜像的影响?是 dive( https://github.com/wagoodman/dive )和 docker history。

不要把大型 JAR 包放进 Docker 镜像

交互式命令行工具 dive 可用来显示 JAR 包层

docker history 命令也可以用来显示 JAR 包层:

复制代码

~ ❯❯❯ dockerhistoryregistry.domain.com/neptune:latest
IMAGE           CREATED          CREATED BY                     SIZE
44e77fa110e5    2 minutes ago   /bin/sh-c#(nop) COPY dir:…   65.5MB 
...
<missing>       8 months ago     /bin/sh-cset-ex;   if[ …   217MB
...
<missing>       8 months ago     /bin/sh-c#(nop) ADD file:…   55.3MB

解决方案:依赖项、资源文件和代码放在不同的层

所幸的是,我们可以利用 Docker 镜像的分层机制,就像已经分好的 OS 和 JRE 层那样。我们更进一步,引入了依赖项层、资源文件层和代码层。我们还按照改动的频繁程度来安排这些层的次序。

不要把大型 JAR 包放进 Docker 镜像

将应用程序分到依赖项、资源和代码三个层。常规发布版本现在只需要拷贝 2MB 的文件,而不是 72MB

现在,如果新版本只包含代码层的改动,就只需要 2MB 的存储空间,因为我们可以重用依赖项层和资源层。它们在注册表中已经有了,不需要再次上传。

谷歌的 Jib 插件

实际上,我们不需要手动编写 Dockerfile,我们可以使用谷歌的 Jib 插件( https://github.com/GoogleContainerTools/jib )。Jib 是一个 Maven 或 Gradle 插件,用于简化 Java 应用程序镜像的打包过程。对于我们来说,Jib 最重要的一个特性是,它会扫描我们的 Java 项目,并为依赖项、资源文件和代码创建不同的层。

使用步骤:

1)在 pom.xml 中添加插件配置:

复制代码

<plugin>
   <groupId>com.google.cloud.tools</groupId>
   <artifactId>jib-maven-plugin</artifactId>
   <version>1.6.1</version>
   <configuration>
       <from>
           <image>openjdk:11.0.4-jre-slim</image>
       </from>
       <to>
           <image>domain.com/${project.artifactId}:latest</image>
           <!-- 可选项: 基于 git 提交创建标签 (通过 git-commit-id 插件): -->
           <tags>
               <tag>${git.commit.id}</tag>
           </tags>
       </to>
       <container>
           <jvmFlags>
               <jvmFlag>-server</jvmFlag>
           </jvmFlags>
       </container>
   </configuration>
   <executions>
       <execution>
           <id>build-and-push-docker-image</id>
           <phase>package</phase>
           <goals>
               <goal>build</goal>
           </goals>
       </execution>
   </executions>
</plugin>

2)使用

复制代码

# 执行整个 build 生命周期,并将镜像推送到注册表中
mvn package

# 只是创建和推送镜像
mvn jib:build
# 注意,`jib:build`运行在前台,而且不会在本地创建镜像
# 直接与注册表交互。可以使用`docker pull`拉取创建好的镜像

# 通过 Docker 后台创建和推送镜像
mvn jib:dockerBuild

(3)使用 dive 和 docker history 显示层结构。

不要把大型 JAR 包放进 Docker 镜像

使用 Jib 创建的镜像,镜像中包含了依赖项、资源和代码层

复制代码

~ ❯❯❯ docker history registry.domain.com/neptune:latest
IMAGE          CREATED         CREATED BY                SIZE     COMMENT
a759771eb008   49years ago    jib-maven-plugin:1.6.1   1.22MB   classes
<missing>     49years ago    jib-maven-plugin:1.6.1   297kB    resources
<missing>     49years ago    jib-maven-plugin:1.6.1   64.6MB   dependencies
...                         
<missing>     8months ago    /bin/sh -cset-ex; ...   217MB               
...
<missing>     8months ago    /bin/sh -c #(nop) ADD... 55.3MB

清理(可选)

清理 1)禁用 maven-deploy-plugin、maven-install-plugin 和 maven-jar-plugin。这些步骤不需要了,即使程序员敲入 mvn deploy 也不会执行这些步骤。

复制代码

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-deploy-plugin</artifactId>
   <configuration>
       <skip>true</skip>
   </configuration>
</plugin>
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-install-plugin</artifactId>
   <configuration>
       <skip>true</skip>
   </configuration>
</plugin>
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-jar-plugin</artifactId>
   <!-- 可惜的是,这里不支持开关。
    临时解决方案: 将 default-jar 绑定到一个不存在的 phase -->
   <executions>
       <execution>
           <id>default-jar</id>
           <phase>none</phase>
       </execution>
   </executions>
</plugin>

清理 2)如果使用了 Spring Boot,可以移除 spring-boot-maven-plugin,现在不再需要创建大型 JAR 包了。

使用部署配置

我们可以在 pom.xml 中配置 Jib 的 JVM 和程序参数,但通常不会这么做。相反,我们会根据部署环境(比如本地环境、QA 环境、生产环境)来配置这些参数。我们通过这种方式来配置 Spring 参数和 JVM 堆大小。

  • JVM 参数:我们使用环境变量 JAVA_TOOL_OPTIONS 来添加 JVM 参数,比如堆大小。
  • Spring 配置:我们将外部配置文件挂载到 Docker 容器,并将它的路径作为一个程序参数传递给命令行。当然,你也可以使用环境变量。

复制代码

docker run -p1309:1309--net=host /
-e JAVA_TOOL_OPTIONS='-Xms1000M -Xmx1000M'/
-v /home/phauer/dev/app/app-config.yml:/app-config.yml/
registry.domain.com/app:latest /
--spring.config.additional-location=/app-config.yml

原文链接:

https://phauer.com/2019/no-fat-jar-in-docker-image/

原文  https://www.infoq.cn/article/eULlQ4A3RcaLLQeImQy9
正文到此结束
Loading...