Docker 镜像优化:减小镜像尺寸

2022-12-02,,,

引 言
随着我们对Docker 应用的持续使用,如果不加注意,那么镜像尺寸就会变得越来越大。很多人在使用Docker 时会发现,团队定制化的Docker 镜像尺寸都至少有1GB 大。镜像越大就意味着编译和部署Docker 应用的时间会越长。因此,我们需要减小需要部署的镜像的尺寸。它会抵消使用Docker 带来的好处,失去快速迭代开发和部署应用的能力。
本文节将深入讨论Docker 镜像层的技术细节以及它们是如何影响最终镜像的大小的。
接下来,我们将在研究Docker 镜像工作原理的过程中,学习如何优化这些镜像层。

链 式 指 令
Docker 镜像尺寸变大的一个原因是很多对编译或运行无关的指令被引入到镜像中。一个常见的案例是打包元数据和缓存。在安装完编译和运行相关的依赖包之后,这些下载的文件就没有存在的必要了。类似clean 的指令可以在很多仓库(如Docker Hub)的Dockerfile 中发现,它们用于清理这类文件,例如:

但是,一个Docker 镜像的尺寸是每一个独立镜像层的尺寸之和,这也就是联合文件系统的工作机制。因此,clean 步骤并没有真正删掉相应的硬盘空间,可通过如下命令来查看:

记录显示,这里并不存在“负”的镜像层尺寸。于是,Dockerfile 中每一个指令要么保持镜像尺寸不变,要么增加它的尺寸。同时,每一步还会引入新的元数据信息,使得整体尺寸在增大。
为了降低整个镜像的尺寸,清除操作应该在同一镜像层中执行。于是,解决方案是将先前的多条指令合并成一条。当Docker 使用/bin/sh 来执行每一条指令时,我们可以使用Bourne shell 提供的&&操作符来实现链接,例如:

现在每一个独立层的尺寸已经足够小了。由于独立镜像层的尺寸被减小,于是整个镜像的尺寸也随之减小。让我们来确认一下它们的尺寸,操作如下:

分离编译镜像和部署镜像
Docker 镜像中另一类无用文件是编译过程中的依赖文件,例如在编译应用程序过程中所依赖的源代码库,如编译文件和头文件。一旦应用程序编译完毕,这些文件就不再有用,因为运行该应用仅需要相关的依赖库。
例如,编译下面这个应用程序,它已经开发完毕并准备部署到Docker 云主机上。这是一个简单的Web 应用程序,采用Go 语言开发,代码树如下图所示。

hello.go 的内容如下:

相应的Dockerfile 中记录了如何编译源代码和运行编译结果,内容如下:

接下来,我们将展示这个Docker 镜像的尺寸是如何变大的,操作如下所示。
1.首先,编译这个Docker 镜像并记录它的尺寸,操作如下:

2.然后,对比运行时实际应用程序的尺寸,操作如下:

用Go 语言编写应用程序以及编译代码的一个优势是,它可以生成一个单一可执行文件,这对部署非常方便。Docker 镜像中除去该可执行文件占据的空间,全都是Docker 基础镜像引入的无用文件。可以发现,来自基础镜像的文件使得整个镜像尺寸增加了将近100倍。
同样,我们可以优化这个最终的Docker 镜像并仅打包最后的hello 可执行文件和相关的依赖包,然后部署到生产环境。优化步骤如下所示。
1.首先,复制运行容器中的可执行文件到Docker 宿主机,操作如下:

2.如果前面的依赖库是一个静态库,那这一步就已经完成了,直接进入下一步。但是,Go 工具编译时默认采用共享库机制,为了让二进制文件直接运行,还需要这些共享库,操作如下:

3.接下来,保存全部共享库到Docker 宿主机,采用docker cp –L 命令,操作如下:

4.创建一个新的Dockerfile 用于编译这个只有二进制(binary-only)的镜像。注意,如何使用ADD 指令将共享库添加到Docker 镜像中,操作如下:

5.现在,所有必要的文件都在这个“binary-only”的镜像中,文件夹中的目录结构树如下图所示。

6.最后,采用build/Dockerfile 文件编译这个用于部署的二进制Docker 镜像,最终生成的镜像将比原来的小,操作如下:

同样的方法可以用于编译其他应用,例如通常采用./configure && make && makeinstall 方式安装的那些软件。同样,可以用于那些解释性编程语言的应用程序,如Python、Ruby 或者PHP。但是,创建一个“运行时”的Ruby 语言的Docker 镜像还需要进行一些额外处理。这种优化技术的最佳实践案例是在一个可持续开发流程中的应用程序的场景,并且它由于镜像太大导致传输时间太长。
相关图书

《高性能Docker》
DockOne社区倾情翻译
阅读本书将掌握Docker性能优化实践
更快、更高效地部署容器,改善开发工作流
【美】艾伦·埃斯皮诺萨 著
陈杰 杨峰 夏彬 译
2016年9月出版
◎ 帮助读者改善其Docker 工作流,并保证应用在生产环境中顺利进行
◎ 除了Docker 的基础知识外,还会学到如何优化Docker基础架构和大规模应用