在Docker容器里放大型JAR包是对存储空间、网络带宽和时间的一种浪费。所幸的是,我们可以利用Docker镜像的分层机制和注册表缓存来创建增量式的小工件,甚至可以将一个工件的大小从75MB减到1MB。值得一提的是,现在有一个Maven和Gradle插件可以处理这些事情。
Docker镜像的分层机制非常强大。如果你所有的应用程序都使用了相同的基础镜像(比如openjdk:11.0.4-jre-slim),Docker重用了OS和JRE的一些层,这样就可以节省Docker注册表的存储空间,上传和下载镜像的速度也更快了,因为只需要传输更少的文件量(Docker只会将新的层传输到注册表中)。
可惜的是,很多应用程序并没有很好地利用这个强大的机制,因为他们把大型的JAR包塞进了Docker镜像里。
每个新版本都会创建一个72MB的新层
我们假设将一个Spring Boot应用程序打成一个大型的JAR包。这个JAR包有72MB,并在Dockerfile的最后一行将它加到镜像里。也就是说,每个新版本都需要72MB的存储空间,而且要上传到注册表中,或则从注册表中下载下来。
现在来看一下这个72MB的镜像:
一个大型的JAR包,大部分东西很少发生改动,但每次都会被拷贝到新工件中
一个大型的JAR包包含三个部分:
经常发生改动的代码只有几MB,但每次都需要拷贝所有的依赖项和资源文件,这是对存储空间、带宽和时间的一种浪费。
如果需要为每个git提交创建一个可部署的镜像,那浪费的空间就更多了。持续交付可能需要这么做,但这样做浪费了大量的空间,因为每一次提交都会占用额外的72MB空间。
有哪些有用的工具可用来分析Docker镜像和可视化大型JAR对Docker镜像的影响?是dive(https://github.com/wagoodman/dive)和docker history。
交互式命令行工具dive可用来显示JAR包层
docker history命令也可以用来显示JAR包层:
~ ❯❯❯ docker history registry.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 -c set -ex; if [ … 217MB
...
<missing> 8 months ago /bin/sh -c #(nop) ADD file:… 55.3MB
所幸的是,我们可以利用Docker镜像的分层机制,就像已经分好的OS和JRE层那样。我们更进一步,引入了依赖项层、资源文件层和代码层。我们还按照改动的频繁程度来安排这些层的次序。
将应用程序分到依赖项、资源和代码三个层。常规发布版本现在只需要拷贝2MB的文件,而不是72MB
现在,如果新版本只包含代码层的改动,就只需要2MB的存储空间,因为我们可以重用依赖项层和资源层。它们在注册表中已经有了,不需要再次上传。
实际上,我们不需要手动编写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显示层结构。
使用Jib创建的镜像,镜像中包含了依赖项、资源和代码层
~ ❯❯❯ docker history registry.domain.com/neptune:latest
IMAGE CREATED CREATED BY SIZE COMMENT
a759771eb008 49 years ago jib-maven-plugin:1.6.1 1.22MB classes
<missing> 49 years ago jib-maven-plugin:1.6.1 297kB resources
<missing> 49 years ago jib-maven-plugin:1.6.1 64.6MB dependencies
...
<missing> 8 months ago /bin/sh -c set -ex; ... 217MB
...
<missing> 8 months 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堆大小。
docker run -p 1309: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
原文链接:
领取专属 10元无门槛券
私享最新 技术干货