我是如何将 Java 基础 docker 镜像大小从 674Mb 优化到 58Mb的

我是如何将 Java 基础 docker 镜像大小从 674Mb 优化到 58Mb的

如果您是 Java 开发人员,并且正在使用 Docker 打包应用程序,您可能已经注意到,即使是“hello world”类型的项目,最终镜像的大小也可能非常大。在本文中,我们将介绍一些优化 Java 应用程序的 Docker 镜像大小的技巧。

我们将使用上一篇文章《使用 RFC-9457 规范在 Spring web 中处理错误》中构建的相同 Spring web 应用程序来演示这些技巧。我们的应用程序仅包含 2 个端点:

  • GET /users/:id :通过 id 获取用户
  • POST /users:创建新用户

Controller.java

@RestController 
@RequestMapping("/api/users") 
@RequiredArgsConstructor 
public  class  UserController { private  final UserService userService; @GetMapping("{id}") public User getUser ( @PathVariable Long id) { return userService.getUserById(id) .orElseThrow(() -> new  UserNotFoundException (id, "/api/users" )); } @PostMapping public User createUser ( @Valid  @RequestBody User user) { return userService.createUser(user); } 

不是太多吧?但正如你所看到的,最简单的docker镜像(没有应用一些优化)的大小可能相当大。

不是太多吧?但正如你所看到的,最简单的docker镜像(没有应用一些优化)的大小可能相当大。

本文的源代码可以在github上找到

为什么我们应该关心镜像大小?

镜像大小会对您的绩效产生重大影响,无论是作为开发人员还是组织。特别是当您在处理包含多项服务的大型项目时,镜像的大小可能会非常大,这可能会花费您大量的金钱和时间。

应避免使用大镜像的原因如下:

  • 磁盘空间:您正在浪费 Docker 注册表和生产服务器中的磁盘空间。
  • 构建速度较慢:镜像越大,构建和推送镜像所需的时间越长。
  • 安全性:镜像越大,依赖关系越大,攻击面就越大。
  • 带宽:镜像越大,从注册表拉取和推送镜像时消耗的带宽就越大。

使用简单的 Dockerfile

基础镜像很重要 ✌🏽 :选择正确的基础镜像

在开始考虑优化之前,你应该始终小心用于打包应用程序的基础镜像。你选择的基础镜像会对最终镜像的大小产生重大影响(如下所示)。

您可以使用多个基础镜像来打包 Java 应用程序,其中一些是:

  • JDK Alpine 基础镜像 :这些镜像体积相当小,但并不适合所有应用程序,因此您可能会遇到与某些库的兼容性问题。
  • JDK Slim 基础镜像 :这些镜像基于 Debian 或 Ubuntu,与完整的 JDK 镜像相比,它们的尺寸非常小,但仍然相当大。
  • JDK 完整基础镜像 :这些镜像尺寸相当大,它们包含运行应用程序所需的所有模块和依赖项。

openjdk:17-jdk-slim为了让您对基础镜像的大小有一个概念,这里是(slim) 和 eclipse-temurin:17-jdk-alpine(alpine) 镜像大小的比较:

知道应用程序工件(jar)的大小为:~20MB

openjdk:17-jdk-slim为了让您对基础镜像的大小有一个概念,这里是(slim) 和 eclipse-temurin:17-jdk-alpine(alpine) 镜像大小的比较:

知道应用程序工件(jar)的大小为:~20MB

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将我们的工件打包到docker镜像中,我们需要 Dockefile在应用程序根目录中定义如下:

使用 openjdk:17-jdk-slim 作为基础镜像。

Dockerfile.base-openjdk

FROM openjdk: 17 -jdk-slim # 设置容器中的工作目录
WORKDIR /app # 创建用户
RUN addgroup -- system spring && adduser -- system spring --ingroup spring # 更改为用户
USER spring:spring COPY target/*.jar app.jar EXPOSE 8080CMD [ "java" , "-jar" , "app.jar" ]

定义Dockerfile后,我们可以使用以下命令构建镜像:

docker build-t 用户服务。

此后,你应该有一个名为的 docker 镜像 user-service,你可以看到,与应用程序工件的大小相比,镜像的大小相当大,大约为 674MB

在这里插入图片描述

等什么🤯!!然而,这只是一个具有 2 个端点且没有依赖关系的小项目,那么具有十几个依赖项和文件的应用程序怎么办呢?

使用 eclipse-temurin:17-jdk-alpine 作为基础镜像。

Dockerfile.base-temurin

FROM eclipse-temurin:17-jdk-alpine ARG APPLICATION_USER=spring 
# 创建用户来运行应用程序,不要以 root 身份运行
RUN addgroup --system $APPLICATION_USER && adduser --system $APPLICATION_USER --ingroup $APPLICATION_USER # 创建应用程序目录
RUN mkdir /app && chown -R $APPLICATION_USER /app # 设置用户来运行应用程序
USER $APPLICATION_USER # 将 jar 文件复制到容器
COPY -- chown = $APPLICATION_USER : $APPLICATION_USER target/*.jar /app/app.jar # 设置工作目录
WORKDIR /app # 公开端口
EXPOSE 8080 # 运行应用程序
ENTRYPOINT [ "java" , "-jar" , "/app/app.jar" ]

使用以下命令构建镜像后:

docker build -t 用户服务:alpine -f Dockerfile.base-alpine . --platform=linux/amd64

🚨 附注

重要提示:如果您在 Apple Silicon 上使用 MAC,则在构建映像时可能会遇到以下问题:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

[internal] 加载 docker.io/library/eclipse-temurin:17-jdk-alpine 的元数据:
— — —
Dockerfile:2
— — — — — — — — — — —
1 | # 第一阶段,构建自定义 JRE
2 | >>> FROM eclipse-temurin:17-jdk-alpine AS jre-builder
3 |
4 | # 安装 jlink 所需的 binutils
— — — — — — — — — — —
错误:无法解决:eclipse-temurin:17-jdk-alpine:清单中的平台不匹配:未找到
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

要解决此问题,您可以将其添加到 docker build命令中:

–平台=linux/amd64

linux/amd64或者您可以通过运行以下命令将默认平台设置为:

导出 DOCKER_DEFAULT_PLATFORM=linux/amd64

linux/amd64或者您可以通过运行以下命令将默认平台设置为:
export DOCKER_DEFAULT_PLATFORM=linux/amd64

eclipse-temurin:17-jdk-alpine使用该镜像作为基础镜像构建镜像后,我们得到了以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

查看两个镜像的大小,即使不进行任何调整,用作基础镜像的镜像大小也为 180MB,比用作基础镜像的 eclipse-temurin:17-jdk-alpine镜像(674MB)小 73%openjdk:17-jdk-slim

动手优化

等一下,为什么我们不能使用 JREimage 来代替 JDKimage ?

好问题!这是因为从 Java 11 开始,JRE不再可用

其中需要考虑的最重要的一点是这部分“用户可以使用 jlink 创建更小的自定义运行时。”

JRE使用构建您自己的镜像 jlink

jlink是一种可用于创建自定义运行时映像的工具,该映像仅包含运行应用程序所需的模块;

👉 如果您的应用程序不与数据库交互,则无需 java.sql在映像中包含该模块。 如果您不与桌面 GUI 交互,则无需 java.desktop在映像中包含该模块。 等等。

它有点像 JRE镜像的替代品,但对您想要在镜像中使用的模块有更多的控制权。

因此 jlink我们的 Dockerfile 应该是这样的:

# 第一阶段,构建自定义 JREFROM eclipse-temurin:17-jdk-alpine AS jre-builder # 安装 jlink 所需的 binutilsRUN apk update && \ apk add binutils # 构建小型 JRE 映像
RUN $JAVA_HOME /bin/jlink \ --verbose \ --add-modules ALL-MODULE-PATH \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /optimized-jdk-17 # 第二阶段,使用自定义 JRE 并构建应用程序映像
FROM alpine:latest 
ENV JAVA_HOME=/opt/jdk/jdk-17 
ENV PATH= " ${JAVA_HOME} /bin: ${PATH} " # 从基础映像复制 JRECOPY --from=jre-builder /optimized-jdk-17 $JAVA_HOME # 添加应用程序用户
ARG APPLICATION_USER=spring # 创建用户来运行应用程序,不要以 root 身份运行
RUN addgroup --system $APPLICATION_USER && adduser --system $APPLICATION_USER --ingroup $APPLICATION_USER # 创建应用程序目录
RUN mkdir /app && chown -R $APPLICATION_USER /app COPY -- chown = $APPLICATION_USER : $APPLICATION_USER target/*.jar /app/app.jar WORKDIR /app USER $APPLICATION_USEREXPOSE 8080 
ENTRYPOINT [ "java" , "-jar" , "/app/app.jar" ]

那么让我们解释一下我们在这里所做的事情:

  • 我们有两个阶段,第一阶段用于构建自定义 JRE 映像 jlink,第二阶段用于将应用程序打包到 slim alpine 映像中。
  • 在第一阶段,我们使用 eclipse-temurin:17-jdk-alpine来构建自定义 JRE 映像 jlink。然后我们安装 binutils所需的 jlink,然后我们运行 jlink构建一个小型 JRE 映像,该映像包含 --add-modules ALL-MODULE-PATH运行应用程序所需的所有模块(目前)。
  • 在第二阶段,我们使用 alpine镜像(相当小的 3Mb)来打包我们的应用程序)作为基础镜像,然后我们 JRE从第一阶段获取自定义并将其用作我们的 JAVA_HOME
  • Dockerfile 的其余部分与前一个相同,只是复制工件并使用自定义用户(非 root)设置入口点。

然后我们可以使用以下命令构建镜像:

docker build -t 用户服务:jlink-all-modules-temurin -f Dockerfile.jlink-all-modules.temurin 。

如果运行以下命令:

docker 镜像用户服务

您将看到该镜像的新 Docker 镜像大小现在为 85.3MB,比使用 eclipse-temurin 基础镜像的基础镜像小约 95MB 🎉🥳

为了确保镜像按预期工作,您可以运行以下命令:

docker run -p 8080:8080 用户服务:jlink-all-modules-temurin

您应该会看到应用程序按预期运行。

这还不够

作为一名优秀的开发人员,我们总是希望改进我们的工作,所以让我们看看如何进一步改善镜像尺寸。

--add-modules ALL-MODULE-PATH镜像大小仍然很大,这是因为在命令中使用时 jlink,我们包含了运行应用程序所需的所有模块,但我们肯定不需要所有模块。因此,让我们看看如何通过仅包含运行应用程序所需的模块来获得较小的镜像大小。

如何知道运行应用程序需要哪些模块?

我们可以使用 jdepsJDK自带的工具。jdeps该工具可以用来分析jar文件的依赖关系并生成运行应用程序所需的模块列表。

为此,我们可以在项目根目录运行以下命令:

jdeps --ignore-missing-deps -q \ --recursive \ --multi-release 17 \ --print-module-deps \ --class-path BOOT-INF/lib/* \ target/spring-error-handling-rfc-9457-0.0.1-SNAPSHOT.jar

这将打印出运行应用程序所需的模块列表,在我们的例子中是:

java.base、java.compiler、java.desktop、java.instrument、java.management、java.naming、java.net.http、java.prefs、java.rmi、java.scripting、java.security.jgss、java.sql、jdk.jfr、jdk.unsupported

我们可以简单地将其放入命令 ALL-MODULE-PATHjlink

Dockerfile.jlink-已知模块.temurin


# 第一阶段,构建自定义 JREFROM openjdk:17-jdk-slim AS jre-builder # 安装 jlink 所需的 binutilsRUN apt-get update -y && \ apt-get install -y binutils # 构建小型 JRE 映像
RUN $JAVA_HOME/bin/jlink \ --verbose \ --add-modules java.base,java.compiler,java.desktop,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.sql,jdk.jfr,jdk.unsupported \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress = 2 \ --output /optimized-jdk-17 # 第二阶段,使用自定义 JRE 并构建应用程序映像
FROM alpine:latest 
ENV JAVA_HOME =/opt/jdk/jdk- 17ENV PATH = "${JAVA_HOME}/bin:${PATH}" # 从基础镜像复制 JRECOPY --from =jre-builder /optimized-jdk- 17  $JAVA_HOME # 添加应用程序用户
ARG APPLICATION_USER =spring # 创建用户来运行应用程序,不要以 root 身份运行
RUN addgroup --system $APPLICATION_USER && adduser --system $APPLICATION_USER --ingroup $APPLICATION_USER # 创建应用程序目录
RUN mkdir /app && chown -R $APPLICATION_USER /app COPY --chown = $APPLICATION_USER : $APPLICATION_USER target/*.jar /app/app.jar WORKDIR /app USER $APPLICATION_USER EXPOSE 8080 
ENTRYPOINT [ "java", "-jar", “/app/app.jar” ]

然后我们可以使用以下命令构建镜像:

docker build - t用户-服务:jlink -已知-模块- temurin - f Dockerfile.jlink -已知-模块.temurin 。

构建后镜像的尺寸如下:

我们得到的镜像尺寸较小,为 57.8MB,而不是 85.3MB。

这很好,但是我们不能自动化这个过程,而是 jdeps手动运行命令,然后将模块复制到 jlink命令中?

dockerfile 内部流程自动化

Dockerfile.jlink-带有-jdeps.temurin

# 第一阶段,构建自定义 JREFROM eclipse-temurin:17-jdk-alpine AS jre-builder RUN mkdir /opt/app 
COPY 。 /opt/app WORKDIR /opt/app环境 MAVEN_VERSION 3.5.4
环境 MAVEN_HOME /usr/lib/mvn
环境 PATH $MAVEN_HOME/bin:$PATH运行 apk 更新 && \ apk 添加 --no-cache tar binutils运行 wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \ tar -zxvf apache-maven-$MAVEN_VERSION-bin.tar.gz && \ rm apache-maven-$MAVEN_VERSION-bin.tar.gz && \ mv apache-maven-$MAVEN_VERSION /usr/lib/mvn运行 mvn package -DskipTests
运行 jar xvf target/spring-error-handling-rfc-9457-0.0.1-SNAPSHOT.jar 
RUN jdeps --ignore-missing-deps -q \ --recursive \ --multi-release 17 \ --print-module-deps \ --class-path 'BOOT-INF/lib/*' \ target/spring-error-handling-rfc-9457-0.0.1-SNAPSHOT.jar > modules.txt # 构建小型 JRE 映像
RUN $JAVA_HOME/bin/jlink \ --verbose \ --add-modules $(cat modules.txt) \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress = 2 \ --output /optimized-jdk-17 # 第二阶段,使用自定义 JRE 并构建应用程序映像
FROM alpine:latest 
ENV JAVA_HOME =/opt/jdk/jdk- 17ENV PATH = "${JAVA_HOME}/bin:${PATH}" # 从基础镜像复制 JRECOPY --from =jre-builder /optimized-jdk- 17  $JAVA_HOME # 添加应用程序用户
ARG APPLICATION_USER =spring # 创建用户来运行应用程序,不要以 root 身份运行
RUN addgroup --system $APPLICATION_USER && adduser --system $APPLICATION_USER --ingroup $APPLICATION_USER # 创建应用程序目录
RUN mkdir /app && chown -R $APPLICATION_USER /app COPY --chown = $APPLICATION_USER : $APPLICATION_USER target/*.jar /app/app.jar WORKDIR /app USER $APPLICATION_USER EXPOSE 8080 
ENTRYPOINT [ "java", "-jar", "/app/app.jar" ]

然后我们可以使用以下命令构建镜像:

docker build -t 用户服务:jlink-with-jdeps.temurin -f Dockerfile.jlink-with-jdeps.temurin . --platform=linux/amd64

奖金

在我们完成之前,请注意,您可以使用一个 .dockerignore文件来排除一些文件和目录被复制到镜像中,这有助于减少中间阶段镜像的大小。

您还应该知道,选择小型的基础镜像是好的,但要确保它具有良好的安全策略并且与您的应用程序兼容。

结论

希望这篇文章对您有所帮助。如果您有任何问题或意见,请随时通过Twitter或LinkedIn 与我联系。请务必访问我的网站https://www.abdelrani.com查看新文章。

参考

  • https://docs.docker.com/develop/develop-images/multistage-build/
  • https://docs.oracle.com/en/java/javase/11/tools/jlink.html
  • https://docs.oracle.com/en/java/javase/11/tools/jdeps.html
  • https://www.oracle.com/java/technologies/javase/11-relnote-issues.html

以上文章翻译自medium,原文链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/436351.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

D18【python接口自动化学习】-python基础之内置数据类型

day18 综合练习:实现手机通讯录(下) 学习日期:20240925 学习目标:内置数据类型--27 小试牛刀:如何使用类型转换实现手机通讯录(下) 学习笔记: 实现手机通讯录 案例文…

【C语言】字符和字符串函数(2)

文章目录 一、strncpy函数的使用二、strncat函数的使用三、strncmp函数的使用四、strstr的使用和模拟实现五、strtok函数的使用六、strerr函数的使用 一、strncpy函数的使用 我们之前学习的strcpy的作用是把源字符串拷贝到目标空间内,而且经过我们的模拟实现&#x…

【Linux:线程概念】

目录 概念: 创建线程的函数:​编辑 ​编辑 有多进程为什么还需要有多线程? 线程调度的成本为什么低? 进程与线程的区别: 概念: 线程是CPU的基本调度单位,在进程内部运行。在内核中&#xff…

数据库系统

数据库管理系统 DBMS Database Management System分为三类: 关系数据库系统(Relation Database System) 面向对象数据库系统 (Object-Oriented Database System) 对象关系数据库系统 (Object-Oriented Relation Database System) 数据库设…

Stable Diffusion绘画 | SDXL模型使用注意事项

注意事项 SDXL模型的使用,对电脑配置要求更高,需要 8GB 以上显存的显卡SDXL模型兼容性不太好,容易出现错误,对 Mac 电脑不友好只能选择 SDXL模型 训练的 LoRA 使用不能使用旧的 VAE文件 SDXL 专用 VAE 文件:sdxl_vae.…

在矩池云使用 Llama-3.2-11B-Vision 详细指南

Llama 3.2-Vision是Meta开发的一系列多模态大型语言模型(LLMs),包含11B和90B两种规模的预训练和指令调整模型。 这些模型专门优化用于视觉识别、图像推理、字幕生成和回答有关图像的一般问题。Llama 3.2-Vision模型在常见行业基准测试中的表…

【网络安全】内部应用中的多重漏洞利用

未经许可,不得转载。 文章目录 初步发现:帐户枚举利用帐户枚举发现 IDOR 导致帐户接管拦截请求洪水攻击:注册拒绝服务目标网站:https://redacted.com 初步发现:帐户枚举 在最近的一次渗透测试中,我对一个仅供员工使用的内部应用程序进行了评估,重点关注身份验证和帐户…

HR告诉你:HCIE证书到底是职场神话还是锦上添花?真相大解析

在职场内卷的赛道上,每个人都在寻找能让自己脱颖而出的光环。而HCIE证书,作为IT领域的一项高含金量认证,莫过于优势最高最让人垂涎的光环,许多人相信它能开启通往理想职位的大门。 但在这个快速变化的时代,HCIE证书真的…

基于Hive和Hadoop的电商消费分析系统

本项目是一个基于大数据技术的电商消费分析系统,旨在为用户提供全面的电商消费信息和深入的消费行为分析。系统采用 Hadoop 平台进行大规模数据存储和处理,利用 MapReduce 进行数据分析和处理,通过 Sqoop 实现数据的导入导出,以 S…

望繁信科技CTO李进峰受邀在上海外国语大学开展流程挖掘专题讲座

2023年,望繁信科技联合创始人兼CTO李进峰博士受邀在上海外国语大学国际工商管理学院(以下简称“上外管院”)开展专题讲座,畅谈流程挖掘的发展及对企业数字化转型的价值。演讲吸引了上外教授和来自各行各业的领军企业学员百余人。 …

easyExcel使用模版填充excel,合并单元格

一、最终效果 二、制作模版 1、制作填充模版 模版在代码中保存的位置 2、Controller /*** 下载模板*/ RequestMapping(value "exportData") public void exportData(KqKqb kqKqb,HttpServletResponse response, HttpServletRequest request) throws IOExceptio…

高级算法设计与分析 学习笔记10 平摊分析

动态表,可以变长。 一溢出就另起一个两倍大小的表。 可以轻易证明把n个数字放进去的时间复杂度是O(n),n n/2 n/4……也就2n,插入数字本身也就是n,加起来最多不超过3n. 这种复杂度究竟是怎么算的?毕竟每次插入复杂度…

Vulhub zico 2靶机详解

项目地址 https://download.vulnhub.com/zico/zico2.ova实验过程 将下载好的靶机导入到VMware中,设置网络模式为NAT模式,然后开启靶机虚拟机 使用nmap进行主机发现,获取靶机IP地址 nmap 192.168.47.1-254根据对比可知Zico 2的一个ip地址为…

阿里云ACP认证考试题库

最近有好些同学,考完阿里云ACP了,再来跟我反馈:自己花700买的阿里云ACP题库,结果答案是错的! 或者考完后发现,买的阿里云ACP题库覆盖率只有50%! 为避免大家继续踩坑,给大家分享一个阿…

短视频去水印解析api接口使用文档

短视频去水印解析api接口,支持各大平台短视频和图集。 请求示例:https://www.dspqsy.vip/spapi?key密钥&url短视频链接 返回数据格式:JSON 请求方式:GET/POST 请求参数:url (短视频分享的URL) PHP 源码&…

从存储到人工智能洞察: 利用 MinIO 和 Polars 简化数据管道

将 MinIO 的高性能、可扩展企业对象存储的强大功能与 Polars(闪电般快速的 DataFrame 库)的快速内存数据处理功能相结合,可以显著提高数据管道的性能。在 AI 工作流中尤其如此,其中预处理大型数据集和执行特征选择是关键步骤。在这…

Linux操作系统中dubbo

1、简介 dubbo框架是做微服务通信的,是由阿里巴巴开发,后捐赠给阿帕奇基金会。 2、与OpenFeign的区别 dubbo是采用RPC协议实现微服务通信,OpenFeign是采用Http请求的方式实现的。 OpenFeign 最简单的,就是Spring公司开发的&am…

RabbitMQ 队列之战:Classic 和 Quorum 的性能洞察

RabbitMQ 是一个功能强大且广泛使用的消息代理,它通过处理消息的传输、存储和交付来促进分布式应用程序之间的通信。作为消息代理,RabbitMQ 充当生产者(发送消息的应用程序)和使用者(接收消息的应用程序)之…

2024年软考网络工程师中级题库

1【考生回忆版】以下不属于5G网络优点的是(A) A.传输过程中消耗的资源少,对设备的电池更友好 B.支持大规模物联网,能够连接大量低功耗设备,提供更高效的管理 C.引入了网络切片技术,允许将物理网络划分为多个虚拟网络…

Elasticsearch7.7.1集群不能相互发现的问题解决以及Elasticsearch7.7.1安装analysis-ik中文分词插件的应用

一、Elasticsearch7.7.1集群不能相互发现的问题解决 在使用elasticsearch7.7.1搭建集群,使用了3台服务器作为节点,但在搭建的过程中发现每台服务器的elasticsearch服务都正常,但是不能相互发现,期间进行了一些配置的修改偶尔出现了…