关于Dockerfile的优化

news2025/3/15 13:22:40

如今各个公有镜像仓库中已经包含了成千上万的镜像文件,但并不是所有的镜像都是精简高效的。很多初学者刚开始都习惯使用FROM centos然后RUN 一堆yum install,这样还停留在虚拟机层面的使用,这样创建出来的镜像往往体积比较大。其实我们可以参考官方制作的dockerfile文件,或者以alpine版本的镜像为基础镜像进行定制,但是alpine初期的时候问题有点多,而且缺少很多软件包,不易进行排错,但现在很多常用的镜像都有alpine版本了,我们可以直接使用。本文将分享一些在工作中的具体使用经验,尽量以此来帮助大家编写出更精简更优雅的Dockerfile。

本文使用一个基于 Maven 的 Java 项目作为示例,然后不断优化 Dockerfile 的写法,直到最后写出一个比较合适的Dockerfile。中间的每一个步骤都是为了说明从某一方面的最佳实践。

一、减少构建时间

一个开发周期大致包括代码开发,构建 Docker 镜像,代码测试,代码修改,然后重新构建 Docker 镜像。在构建镜像的过程中,如果能够利用缓存,可以减少不必要的重复构建步骤,大大加快制作镜像的速度。

1、构建顺序影响缓存的利用率

[root@k8s-m1 docker1]# cat Dockerfile 
FROM openjdk:7u131-jdk-alpine
RUN yum install vim ssh -y    
ADD wg-kms-pm.jar /opt/wvmp/
### yum install vim ssh -y  放在add的前面更好
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

镜像的构建顺序很重要,当向 Dockerfile 中添加文件,或者修改其中的某一行时,那一部分的缓存就会失效,并且该缓存的后续所有操作都需要重新构建。所以优化缓存的最佳方法是把不需要经常更改的行放到最前面(如安装一些基础包),更改最频繁的行放到最后面(如从外部添加的jar业务包)。

2、只拷贝需要的文件,防止缓存溢出

[root@k8s-m1 docker1]# cat Dockerfile 
FROM openjdk:7u131-jdk-alpine
RUN yum install vim ssh -y    
###ADD . /opt/wvmp/    由于文件夹中可能包含其他不需要的文件
ADD wg-kms-pm.jar /opt/wvmp/
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

当拷贝文件到镜像中时,尽量只拷贝需要的文件,最好不要使用COPY . 指令拷贝整个目录。因为如果被拷贝的文件夹中任意一个文件内容发生了更改,缓存就会被破坏。在上面的示例中,镜像中只需要构建好的 jar 包,因此只需要拷贝这个文件就行,这样即使其他不相关的文件发生了更改也不会影响缓存。

3、尽量减少可缓存的层数

FROM openjdk:7u131-jdk-alpine

### RUN yum update
### RUN yum install vim ssh -y    
RUN yum update && yum install vim ssh -y   #将上面两行合并
### ADD adb_server-V1.2.0.12.tar.gz /opt/wvmp/
### ADD bootstrap.yml /opt/wvmp/
ADD bootstrap.yml wg-kms-pm.jar /opt/wvmp/ #将上面两个add合并,因为都是拷贝到同一文件夹下
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

通过前面的了解知道,docker镜像是分层的,每一个命令在制作镜像时都会生产一个新的镜像层。太多的命令行指令会增加镜像的层数,增大镜像体积,而如果能通过适当优化将某些命令放到同一行就能减少层数;但是如果将所有都放入同一行指令中,某一部分发生改变又会破坏缓存,影响构建速度。如当使用包管理器安装软件时,一般都会先更新软件索引信息,然后再安装软件。推荐将更新索引和安装软件放在同一个 RUN 指令中,这样可以形成一个可缓存的执行单元,否则你可能会安装旧的软件包。

二、减小镜像体积

镜像的体积很重要,因为镜像越小,部署的速度更快,攻击范围也越小。

1、选择镜像体积小的作为基础镜像

在能满足使用,功能正常的情况下,尽量选择镜像大小比较小的作为基础镜像。如openjdk和jdk-alpine版本的大小从下图可以看出体积相差还是挺大的。
在这里插入图片描述

2、删除不必要依赖

[root@k8s-m1 docker1]# cat Dockerfile 
FROM openjdk:7u131-jdk-alpine
### RUN yum install vim ssh -y    
##vim和ssh都不是运行java包的必须依赖包,可以不安装
ADD wg-kms-pm.jar /opt/wvmp/
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

删除不必要的依赖,不要安装调试工具。如果实在需要调试工具,可以在容器运行之后再安装或者只在测试镜像安装,调试完成后从新构建镜像。某些包管理工具(如 apt)除了安装用户指定的包之外,还会安装推荐的包,这会无缘无故增加镜像的体积。apt可以通过添加参数 -–no-install-recommends 来确保不会安装不需要的依赖项。如果确实需要某些依赖项,请在后面手动添加。

3、删除包管理工具的缓存

[root@k8s-m1 docker1]# cat Dockerfile 
FROM openjdk:7u131-jdk-alpine
RUN yum install vim ssh -y    && rm -rf /var/lib/yum/repos/
##增加删除缓存。但是yum默认没有缓存yum包,如果修改了配置需要删除
ADD wg-kms-pm.jar /opt/wvmp/
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

有时候,我们设置了包管理工具缓存安装的yum包,这些缓存会保留在镜像文件中,推荐的处理方法是在每一个 RUN 指令的末尾删除缓存(可以根据实际配置添加)。如果你在下一条指令中删除缓存,不会减小镜像的体积。

当然了,还有其他更高级的方法可以用来减小镜像体积,如下文将会介绍的多阶段构建。接下来我们将探讨如何优化 Dockerfile 的可维护性、安全性和可重复性。

三、可维护性

一、尽量使用官方镜像

官方的hubdocker仓库地址已经变成了https://hub-stage.docker.com/

使用官方镜像可以节省大量的维护时间,因为官方镜像的所有安装步骤都使用了最佳实践。如果你有多个项目,可以共享这些镜像层,因为他们都可以使用相同的基础镜像。

2、指定具体的标签

在这里插入图片描述

如openjdk,基础镜像尽量不要使用 latest 标签。虽然这很方便,但是latest 镜像随时可能发生更新而变得不适用,而且latest镜像比其他的alpine镜像体积大很多。因此在 Dockerfile 中最好指定基础镜像的具体标签。具体某个标签请参考官网提供的。

3、使用体积最小的基础镜像

在功能满足的情况下,使用体积小的基础镜像。基础镜像的标签风格不同,镜像体积就会不同。slim 风格的镜像是基于 Debian 发行版制作的,而 alpine 风格的镜像是基于体积更小的 Alpine Linux 发行版制作的。其中一个明显的区别是:Debian 使用的是 GNU 项目所实现的 C 语言标准库,而 Alpine 使用的是 Musl C 标准库,它被设计用来替代 GNU C 标准库(glibc)的替代品,用于嵌入式操作系统和移动设备。因此使用 Alpine 在某些情况下会遇到兼容性问题。 以 openjdk 为例,jre 风格的镜像只包含 Java 运行时,不包含 SDK,这么做也可以大大减少镜像体积。

四、重复利用

到目前为止,我们一直都在假设你的 jar 包是在某个服务器上提前编译好,但还不是最理想方案,因为没有充分利用容器提供的一致性环境。例如,如果Java 应用依赖于某一个特定的操作系统的库,有可能只在这一台服务器上正常编译(服务器配置了特殊环境),在其他环境编译运行就可能会出现问题,因为环境不一致(具体取决于构建 jar 包的机器)。

1、在一致的环境中从源代码构建

源代码是你构建 Docker 镜像的最终来源,Dockerfile 里面只提供了构建步骤,通过镜像容器提供一个一致的编译环境,就不用在本地编译好jar包。

[root@k8s-m1 docker1]# cat Dockerfile 
##基础镜像为官方提供的,如果不适用,可以自行制作一个基础镜像
FROM maven:alpine
WORKDIR /opt/wvmp/
COPY pom.xml
COPY scr ./src
RUN mvn -e -B package
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

首先应该确定构建应用所需的所有依赖,本文的示例 Java 应用很简单,只需要 Maven 和 JDK,所以基础镜像应该选择官方的体积最小的 maven 镜像,该镜像也包含了 JDK。如果你需要安装更多依赖,可以通过RUN就在该镜像基础上安装好或者自行先制作好基础镜像。pom.xml文件和 src 文件夹需要被复制到镜像中,因为最后执行 mvn package 命令(-e 参数用来显示错误,-B 参数表示以非交互式的“批处理”模式运行)打包的时候会用到这些依赖文件。

通过使用docker直接提供编译环境我们解决了环境不一致的问题,但还有另外一个问题:每次代码更改之后,都要重新获取一遍 pom.xml 中描述的所有依赖项,这样就比较耗费时间。下面我们来解决这个问题。

2、在单独的步骤中获取依赖项

[root@k8s-m1 docker1]# cat Dockerfile 
##基础镜像为官方提供的,如果不适用,可以自行制作一个基础镜像
FROM maven:alpine
WORKDIR /opt/wvmp/
COPY pom.xml
RUN mvn -e -B package dependency:resolve
COPY scr ./src
RUN mvn -e -B package
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

结合前面提到的分层缓存机制,我们可以让获取依赖项这一步变成可缓存单元,只要 pom.xml 文件的内容没有变化,无论代码如何更改,都不会破坏这一层的缓存。上图中两个 COPY 指令中间的 RUN 指令用来告诉 Maven 只获取依赖项。
现在又出现一个新问题:跟之前直接拷贝 jar 包相比,镜像体积变得更大了,因为它包含了很多运行应用时不需要的构建依赖项。

3、使用多阶段构建来删除构建时的依赖项

[root@k8s-m1 docker1]# cat Dockerfile 
##基础镜像为官方提供的,如果不适用,可以自行制作一个基础镜像
FROM maven:alpine AS mid-builder
WORKDIR /opt/wvmp/
COPY pom.xml
RUN mvn -e -B package dependency:resolve
COPY scr ./src
RUN mvn -e -B package

FROM openjdk:7u131-jdk-alpine
COPY --from=mid-builder /opt/wvmp/wg-kms-pm.jar /opt/wvmp/
ENTRYPOINT ["java" "-Djava.security.egd=file:/dev/./urandom " "-jar" "/opt/wvmp/wg-kms-pm.jar"]                                                       

多阶段构建可以由多个 FROM 指令识别,每一个 FROM 语句表示一个新的构建阶段,阶段名称可以用 AS 参数指定。本例中指定第一阶段的名称为 mid-builder,它可以被第二阶段直接引用。两个阶段环境一致,并且第一阶段包含所有构建依赖项。

第二阶段是构建最终镜像的最后阶段,它将包括应用运行时的所有必要条件,本例是基于 Alpine 的最小 JRE 镜像。上一个构建阶段虽然会有大量的缓存,但不会出现在第二阶段中。为了将构建好的 jar 包添加到最终的镜像中,可以使用 COPY --from=STAGE_NAME 指令,其中 STAGE_NAME 是上一构建阶段的名字。

多阶段构建是删除构建依赖的首选方案。

本文从多方面对镜像的制作过程进行优化,目的就是为了创建一个精简的镜像,提高效率,使用率。

更多关于docker容器和运维相关的知识,请前往博客主页。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vmware 设置固定ip地址--桥接模式

前言: 若虚拟机没有设置固定ip地址,每次关机重启后都会更新ip地址。导致连接工具得跟着一起修改,每次修改很烦。 之前使用NAT模式,因为使用此模式后,每次打开网页都会转几秒钟后才会显示网页。所以才使用桥接模式&…

DP学习第一篇之爬楼梯

DP学习之爬楼梯 剑指 Offer II 088. 爬楼梯的最少成本 - 力扣(LeetCode) 1. 题目分析 可以从第0或者第1作为起始台阶、每次可以选择跳1或2步、到楼顶结束 2. 解题 a.解法一 状态表示 tips: 经验题目要求。以i位置为结尾,。。。 dp[i] :…

Frida技术:App逆向开发屠龙刀

Frida是一种基于JavaScript的动态分析工具,可以用于逆向开发、应用程序的安全测试、反欺诈技术等领域。Frida主要用于在已安装的应用程序上运行自己的JavaScript代码,从而进行动态分析、调试、修改等操作,能够绕过应用程序的安全措施,可以助力于对应用程序进行逆向分析。 …

OpenShift Virtualization - 从集群外部访问集群中的 VM(附视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 说明:本文已经在 OpenShift 4.12 的环境中验证 文章目录 方法1:通过 Service 的 NodePort 访问 VM方法2:通过外部 IP 访问 VM确认 OpenShift 集群环境为 Worker 节点添加 Linux Bridge创建使用 Li…

大文件上传功能在标签服务的简单应用和代码实现

各位看官大家好,今天给大家分享的又是一篇实战文章,希望大家能够喜欢。 目前「袋鼠云客户数据洞察平台」标签服务的群组按种类划分,可以分为三大类,分别是实时群组、动态群组以及静态群组。如果按创建方式划分则有两种&#xff0…

6.11 有名管道和无名管道

目录 进程间通讯介绍 System V IPC 无名管道 无名管道特点 无名管道创建-pipe 无名管道通信 无名管道-示例 有名管道特点 有名管道创建-mkfifo 有名管道读写-示例 进程间通讯介绍 无名管道(pipe) 有名管道 (fifo) 信号…

制造业供应商合作该如何协调?SRM供应商管理系统的出现改变一切

制造业是使用SRM系统频率最高的行业了,因为该行业需要与大量供应商合作和协调,以便及时获得所需的原材料和零件。同时,该行业生产周期长,需求通常较为稳定,需要稳定的供应链管理来确保生产效率和质量。因此&#xff0c…

写一个vscode支持vue文件跳转到定义的插件,又可以愉快地摸鱼了

1. 背景 vscode自身是支持vue文件组件跳转到定义的,但是支持的力度是非常弱的。我们在vue-cli的配置的下,可以写很多灵活的用法,这样可以提升我们的生产效率。但是正是这些灵活的写法,导致了vscode自身提供的功能无法支持跳转到文…

MySQL数据库语言二:DML、DQL

😘作者简介:正在努力的99年打工人。 👊宣言:人生就是B(birth)和D(death)之间的C(choise),做好每一个选择。 🙏创作不易,动…

Linux---网络传输命令(ping、wget、curl)

1. ping命令 执行 ping 指令会使用 ICMP 传输协议,发出要求回应的信息,若远端主机的网络功能没有问题, 就会回应该信息,因而得知该主机运作正常。 语法:ping [参数] IP名或主机名 参数包括: 注意&#…

Jmeter实现Dubbo接口测试

目录 前言: 一、准备 二、编写我们的测试工程 三、Jmeter来测试这个工程 前言: JMeter可以用来测试Dubbo接口的性能和负载。Dubbo是阿里巴巴的高性能RPC框架,常用于分布式服务的调用。为了测试Dubbo接口,需要使用JMeter提供的…

Android Chrome Custom Tabs

参考文档 API文档 链接 Chrome Custom Tabs 参考文档 https://developer.chrome.com/docs/android/custom-tabs/ Chrome Custom Tabs最佳实践_customtabs_Just_Sanpark的博客-CSDN博客 Chrome Custom Tabs最佳实践_chrome custom tabs集成_wxx614817的博客-CSDN博客 Chrome…

红黑树(RBTree)c++实现

目录 红黑树介绍 红黑树的性质: 红黑树的结点类 搜索(红黑)树的旋转 旋转分为4种(左旋,右旋,左右双旋,右左双旋): 左旋(RotateL) 右旋(RotateR) 左右双旋(RotateLR) 右左双旋(RotateRL) 红黑树的插入 插入结…

计算机中丢失MSVCP140.dll无法启动此程序怎么办,可以使用这个方法修复

计算机中丢失MSVCP140.dll会导致很多软件跟游戏无法启动运行,这个问题相信困扰着不少小伙伴,遇到这个问题其实不用慌,也无需重装系统。需要先了解清楚MSVCP140.dll文件是什么,已经在我们电脑系统中的作用,了解清楚以后…

【技术干货】数字电路电平标准

信号的逻辑电平经历了从单端信号到差分信号、从低速信号到高速信号的发展过程。最基本的单端信号逻辑电平为CMOS、TTL,在此基础上随着电压摆幅的降低,出现LVCMOS、LVTTL等逻辑电平,随着信号速率的提升又出现ECL、PECL、LVPECL、LVDS、CML等差…

2.7V至25V宽输入电压15A 峰值电流

HT7179是一款高功率异步升压转换器,集成 20mΩ功率开关管,为便携式系统提供高效的 小尺寸解决方案。 HT7179具有2.7V至25V宽输入电压范围,可为 采用单节或两节锂电池,或12V铅酸电池的应 用提供支持。该器件具备15A开关电流能力&a…

【Java基础学习打卡05】命令提示符

目录 引言一、命令提示符是什么二、命令提示符常用命令1.打开命令提示符2.命令演示3.常用命令 总结 引言 知道命令提示符是什么,熟练打开命令提示符,熟练使用常用命令,并自行尝试其他命令。本文只是对命令提示符进行简单介绍和使用。 一、命…

绝版功能回归,Win11开始向Win7进化了

如果将 Win11 上架到 Steam 提供评论打分,那么 Win11 会和很多有争议的游戏一样:褒贬不一。 许多功能是吸引人的,但微软总会在什么时候突然给你一击。 前有 VBS 影响性能、谜之卡顿,后有各种广告、自动安装微软电脑管家。 以及我…

运营活动类项目测试方案设计

1、背景 随着业务的不断发展成熟,商业业务逐渐向重运营、重策略的模式发展,提出的需求中运营活动类需求数量也不断增多。而通过项目的积累、与其他业务的讨论共创,我们也积累了一批对运营活动类项目的测试点和对应的测试方案。下面我将从设计…

List容器(Java)

文章目录 1.容器介绍1.1 容器接口结构1.2 简单解析 2. List容器创建3. 访问操作(get, iterator, for)4. 修改操作(add, remove, set)5. 容量操作(clear, contains, isEmpty, size)6. 其他操作(toArray, )7. 泛型操作 1.容器介绍 1.1 容器接口结构 a. Java存储结构划分   Java…