阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

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

  • 2019-10-29
  • 本文字数:5284 字

    阅读完需:约 17 分钟

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

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


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

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 包包含三个部分:


  • 依赖项:它们占了很大的一部分比例,但很少发生改动。大多数时候我们只会修改我们的代码,很少会去修改依赖项。但依赖项每次都会被拷贝到发布版本中。

  • 资源文件:这个问题跟依赖项差不多。虽然资源文件(HTML、CSS、图像、配置文件,等等)比依赖项更经常发生改动,但比起代码还是相对少一些。它们也会被拷贝到发布版本中。

  • 代码:代码只占 JAR 包很小的一部分(通常 300KB 到 2MB),但会经常发生改动。


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


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


有哪些有用的工具可用来分析 Docker 镜像和可视化大型 JAR 对 Docker 镜像的影响?是dive和 docker history。



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


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


~ ❯❯❯ docker history registry.domain.com/neptune:latestIMAGE           CREATED          CREATED BY                     SIZE44e77fa110e5    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 的存储空间,因为我们可以重用依赖项层和资源层。它们在注册表中已经有了,不需要再次上传。

谷歌的 Jib 插件

实际上,我们不需要手动编写 Dockerfile,我们可以使用谷歌的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:latestIMAGE          CREATED         CREATED BY                SIZE     COMMENTa759771eb008   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 堆大小。


  • JVM 参数:我们使用环境变量 JAVA_TOOL_OPTIONS 来添加 JVM 参数,比如堆大小。

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


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
复制代码


原文链接:


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


2019-10-29 10:129769

评论

发布
暂无评论
发现更多内容

简单好上手!1分钟带你体验Apipost

叶小柒

ITSM | Atlassian被Gartner评为IT服务管理平台魔力象限的领导者

龙智—DevSecOps解决方案

Atlassian ITSM Gartner

云小课|GaussDB(DWS)数据存储尽在掌控,冷热数据切换自如

华为云开发者联盟

开发 华为云 数据存储 企业号 2 月 PK 榜 华为云开发者联盟

Elasticsearch dynamic_templates 实战 通用配置

alexgaoyh

elasticsearch dynamic_templates index template

从历代GC算法角度刨析ZGC

京东科技开发者

ZGC JVM GC算法 垃圾回收算法 企业号 2 月 PK 榜

【IntelliJ IDEA】idea中的插件之一:Free Mybatis plugin跳转插件的使用(方便在Dao接口和Mappper XML文件之间进行切换)

No8g攻城狮

插件 IntelliJ IDEA

前端leetcde算法面试套路之堆

js2030code

JavaScript LeetCode

量化Python交易系统开发技术,合约量化系统开发源码部署方案

I8O28578624

直呼牛逼!阿里最新SpringBoot进阶笔记涵盖了SpringBoot所有骚操作

程序知音

Java ssm springboot Java后端 Java进阶

【IntelliJ IDEA】idea常用快捷键汇总

No8g攻城狮

IDEA intellij IntelliJ IDEA

搞懂设计模式——代理模式 + 原理分析

京东科技开发者

jdk 代理 cglib 框架 企业号 2 月 PK 榜

同步计数器设计与建模

timerring

FPGA

A100 买不到了,只有小显卡怎么训大模型

MegEngineBot

深度学习 开源 大模型 显卡、gpu MegEngine

区块链DEFI质押挖矿系统开发流程丨土狗币智能合约系统开发源码方案

I8O28578624

react源码中的协调与调度

flyzz177

React

chatgpt背后的人工和智能

刘旭东

ChatGPT

架构作为6

梁山伯

“堆外内存”这玩意是真不错,我要写进简历了。

why技术

Java 程序员 面试

一文详解TensorFlow模型迁移及模型训练实操步骤

华为云开发者联盟

人工智能 华为云 昇腾AI 企业号 2 月 PK 榜 华为云开发者联盟

高性能存储SIG月度动态:ublk完成POC、dsms-storage在Anolis OS上成功适配

OpenAnolis小助手

技术 高性能存储 龙蜥社区 sig 月报

Flomesh Ingress 使用实践(三)多租户 Ingress

Flomesh

命名空间 多租户 ingress Ingress Controller

react源码分析:babel如何解析jsx

flyzz177

React

携程MySQL迁移OceanBase最佳实践|分享

OceanBase 数据库

数据库 oceanbase

Spring Data + DDD = 王炸!!

程序知音

前端leetcde算法面试套路之树

js2030code

JavaScript LeetCode

助力芯片产业蓬勃发展,诚翔滤器推出光刻机过滤器

电子信息发烧客

react源码中的生命周期和事件系统

flyzz177

React

Flink Table Store 典型应用场景

Apache Flink

大数据 flink 实时计算

用javascript分类刷leetcode21.树(图文视频讲解)

js2030code

JavaScript LeetCode

用 AI 取代人工?或许 LLMs 可以给你答案

鼎道智联

#人工智能

KCL 与其他 Kubernetes 配置管理工具的异同 - Helm 篇 - Helm 篇 [一个自研编程语言能做什么?(系列 3)]

Peefy

Kubernetes DevOps 编程语言 #开源

不要把大型JAR包放进Docker镜像_文化 & 方法_Philipp Hauer_InfoQ精选文章