博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最小化 Java 镜像的常用技巧
阅读量:6930 次
发布时间:2019-06-27

本文共 4180 字,大约阅读时间需要 13 分钟。

背景

随着容器技术的普及,越来越多的应用被容器化。人们使用容器的频率越来越高,但常常忽略一个基本但又非常重要的问题 - 容器镜像的体积。本文将介绍精简容器镜像的必要性并以基于 spring boot 的 java 应用为例描述最小化容器镜像的常用技巧。

精简容器镜像的必要性

精简容器镜像是非常必要的,下面分别从安全性和敏捷性两个角度进行阐释。

安全性

基于安全方面的考虑,将不必要的组件从镜像中移除可以减少攻击面、降低安全风险。虽然 docker 支持用户通过 限制容器内可以执行操作或者使用 为容器配置安全策略,但它们的使用门槛较高,要求用户具备安全领域的专业素养。

敏捷性

精简的容器镜像能提高容器的部署速度。假设某一时刻访问流量激增,您需要通过增加容器副本数以应对突发压力。如果某些宿主机不包含目标镜像,需要先拉取镜像,然后启动容器,这时使用体积较小的镜像能加速这一过程、缩短扩容时间。另外,镜像体积越小,其构建速度也越快,同时还能减少存储和传输的成本。

常用技巧

将一个 java 应用容器化所需的步骤可归纳如下:

  1. 编译 java 源码并生成 jar 包。
  2. 将应用 jar 包和依赖的第三方 jar 包移动到合适的位置。

本章所用的样例是一个基于 spring boot 的 java 应用 ,所用的未经优化的 如下:

FROM maven:3.5-jdk-8COPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageENTRYPOINT ["java","-jar","/usr/src/app/target/spring-boot-docker-1.0.0.jar"]

由于应用使用 maven 构建,dockerfile 中指定maven:3.5-jdk-8作为基础镜像,该镜像的大小为 635MB。通过这种方式最终构建出的镜像非常大,达到了 719MB,这是因为一方面基础镜像本身就很大,另一方面 maven 在构建过程中会下载许多用于执行构建任务的 jar 包。

多阶段构建

Java 程序的运行只依赖 JRE,并不需要 maven 或者 JDK 中众多用于编译、调试、运行的工具,因此一个明显的优化方法是将用于编译构建 java 源码的镜像和用于运行 java 应用的镜像分开。为了达到这一目的,在 docker 17.05 版本之前需要用户维护 2 个 dockerfile 文件,这无疑增加了构建的复杂性。好在自 17.05 开始,docker 引入了的概念,它允许用户在一个 dockerfile 中使用多个 From 语句。每个 From 语句可以指定不同的基础镜像并将开启一个全新的构建流程。您可以选择性地将前一阶段的构建产物复制到另一个阶段,从而只将必要的内容保留在最终的镜像里。优化后的 如下:

FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM openjdk:8-jreARG DEPENDENCY=/usr/src/app/target/dependencyCOPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY --from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY --from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

该 dockerfile 选用maven:3.5-jdk-8作为第一阶段的构建镜像,选用openjdk:8-jre作为运行 java 应用的基础镜像并且只拷贝了第一阶段编译好的.claass文件和依赖的第三方 jar 包到最终的镜像里。通过这种方式优化后的镜像大小为 459MB。

使用 distroless 作为基础镜像

虽然通过多阶段构建能减小最终生成的镜像的大小,但 459MB 的体积仍相对过大。经调查发现,这是因为使用的基础镜像openjdk:8-jre体积过大,到达了 443MB,因此下一步的优化方向是减小基础镜像的体积。

Google 开源的项目 正是为了解决基础镜像体积过大这一问题。Distroless 镜像只包含应用程序及其运行时依赖项,不包含包管理器、shell 以及在标准 Linux 发行版中可以找到的任何其他程序。目前,distroless 为依赖 、、、 等环境的应用提供了基础镜像。

使用 distroless 的 如下:

FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM gcr.io/distroless/javaARG DEPENDENCY=/usr/src/app/target/dependencyCOPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY --from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY --from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

该 dockerfile 和上一版的唯一区别在于将运行阶段依赖的基础镜像由openjdk:8-jre(443 MB)替换成了gcr.io/distroless/java(119 MB)。经过这一优化,最终镜像的大小为 135MB。

使用 distroless 的唯一不便是您无法 attach 到一个正在运行的容器上排查问题,因为镜像中不包含 shell。虽然 distroless 的 提供 busybox shell,但需要用户重新打包镜像、部署容器,对于那些已经基于非 debug 镜像部署的容器无济于事。 但从安全角度来看,无法 attach 容器并不完全是坏事,因为攻击者无法通过 shell 进行攻击。

使用 alpine 作为基础镜像

如果您确实有 attach 容器的需求,又希望最小化镜像的大小,可以选用 作为基础镜像。Alpine 镜像的特点是体积非常下,基础款镜像的体积仅 4 MB 左右。

使用 alpine 后的 如下:

FROM maven:3.5-jdk-8 AS buildCOPY src /usr/src/app/srcCOPY pom.xml /usr/src/appRUN mvn -f /usr/src/app/pom.xml clean packageFROM openjdk:8-jre-alpineARG DEPENDENCY=/usr/src/app/target/dependencyCOPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY --from=build ${DEPENDENCY}/META-INF /app/META-INFCOPY --from=build ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

这里并未直接继承基础款 alpine,而是选用从 alpine 构建出的包含 java 运行时的openjdk:8-jre-alpine(83MB)作为基础镜像。使用该 dockerfile 构建出的镜像体积为 99.2MB,比基于 distroless 的还要小。

执行命令docker exec -ti <container_id> sh可以成功 attach 到运行的容器中。

distroless vs alpine

既然 distroless 和 alpine 都能提供非常小的基础镜像,那么在生产环境中到底应该选择哪一种呢?如果安全性是您的首要考虑因素,建议选用 distroless,因为它唯一可运行的二进制文件就是您打包的应用;如果您更关注镜像的体积,可以选用 alpine。

其他技巧

除了可以通过上述技巧精简镜像外,还有以下方式:

  1. 将 dockerfile 中的多条指令合并成一条,通过减少镜像层数的方式达到精简镜像体积的目的。
  2. 将稳定且体积较大的内容置于镜像下层,将变动频繁且体积较小的内容置于镜像上层。虽然该方式无法直接精简镜像体积,但充分利用了镜像的缓存机制,同样可以达到加快镜像构建和容器部署的目的。

想了解更多优化 dockerfile 的小窍门可参考教程 。

总结

  1. 本文通过一系列的优化,将 java 应用的镜像体积由最初的 719MB 缩小到 100MB 左右。如果您的应用依赖其他环境,也可以用类似的原则进行优化。
  2. 针对 java 镜像,google 提供的另一款工具 能为您屏蔽镜像构建过程中的复杂细节,自动构建出精简的 java 镜像。使用它您无须编写 dockerfile,甚至不需要安装 docker。
  3. 对于类似 distroless 这样无法 attach 或者不方便 attach 的容器,建议您将它们的日志中心化存储,以便问题的追踪和排查。具体方法可参考文章。

扩展阅读

阿里云日志服务针对容器场景提供了一站式日志解决方案,想了解相关内容可参考下列文章:

转载地址:http://hqmjl.baihongyu.com/

你可能感兴趣的文章
linux kickstart无人值守安装实现多版本的引导
查看>>
docker 容器静态IP配置
查看>>
探索MySQL高可用架构之MHA(4)
查看>>
先来个基础的windos内部机制与ipc$
查看>>
win8安装11gR2[INS-13001] 环境不满足最低要求
查看>>
Aspose.Words 新版本发布【附下载】
查看>>
宫崎骏首次因为自己的新作流泪
查看>>
SAP sybase16 安装的一些细节问题
查看>>
linux服务器同步时间
查看>>
命令行方式管理MySQL数据库实例
查看>>
MySQL索引背后的数据结构及算法原理
查看>>
《大话数据结构》学习之(一)
查看>>
JS常用正则表达式【持续更新】
查看>>
Java中HashMap,LinkedHashMap,TreeMap的区别
查看>>
JAVA中重写equals()方法为什么要重写hashcode()方法说明
查看>>
j2me学习四_LCDui类学习(3)
查看>>
Android开发常见问题及解决方法
查看>>
PostgreSQL监控之pgwatch2
查看>>
遍历MAP最有效率的方式
查看>>
Linux 基础 - 磁盘管理 - 01
查看>>