Docker镜像详谈(2): 深入理解镜像大小

题图摄于三亚亚龙湾

编者注:在《Docker镜像的核心价值》一文中,曾给大家介绍过Docker镜像的重要性。应读者要求,将连载DaoCloud核心成员、《Docker源码分析》一书作者孙宏亮的系列文章,全面解析Docker镜像的技术要点。继上周《容器的文件系统》文章之后,本周介绍影响容器镜像大小的因素,包括为什么有大小为0的文件层等问题。

都说容器大法好,但是如果没有 Docker 镜像,Docker 该是多无趣啊。

是否还记得第一个接触 Docker 的时候,你从 Docker Hub 下拉的那个镜像呢?在那个处女镜像的基础上,你运行了容器生涯的处女容器。镜像的基石作用已经很明显,在 Docker 的世界里,可以说是「No Image, No Container」。

再进一步思考 Docker 镜像,大家可能很快就会联想到以下几类镜像:

  1. 系统级镜像:如 Ubuntu 镜像、CentOS 镜像以及 Debian 容器等;
  2. 工具栈镜像:如 Golang 镜像、Flask 镜像、Tomcat 镜像等;
  3. 服务级镜像:如 MySQL 镜像、MongoDB 镜像、RabbitMQ 镜像等;
  4. 应用级镜像:如 WordPress 镜像、Docker Registry 镜像等。

镜像林林总总,想要运行 Docker 容器,必须要有 Docker 镜像;想要有 Docker 镜像,必须要先下载 Docker 镜像;既然涉及到下载 Docker 镜像,自然会存在 Docker 镜像存储。谈到 Docker 镜像存储,那我们首先来聊聊 Docker 镜像大小方面的知识。

以下将从三个角度来分析 Docker 镜像的大小问题:「Dockerfile 与镜像」、「联合文件系统」以及「镜像共享关系」。

Dockerfile 与镜像

Dockerfile 由多条指令构成,随着深入研究 Dockerfile 与镜像的关系,很快大家就会发现,Dockerfile 中的每一条指令都会对应于 Docker 镜像中的一层。

继续以如下 Dockerfile 为例:

FROM ubuntu:14.04
 ADD run.sh /
 VOLUME /data
 CMD ["./run.sh"]

通过 docker build 以上 Dockerfile 的时候,会在 ubuntu:14.04 镜像基础上,添加三层独立的镜像,依次对应于三条不同的命令。镜像示意图如下:

有了 Dockerfile 与镜像关系的初步认识之后,我们再进一步联系到每一层镜像的大小。

不得不说,在层级化管理的 Docker 镜像中,有不少层大小都为 0。那些镜像层大小不为 0 的情况,归根结底的原因是,构建 Docker 镜像时,对当前的文件系统造成了修改更新。而修改更新的情况主要有两种:

  1. ADD 或 COPY 命令:ADD 或者 COPY 的作用是在 docker build 构建镜像时向容器中添加内容,只要内容添加成功,当前构建的那层镜像就是添加内容的大小,如以上命令 ADD run.sh /,新构建的那层镜像大小为文件 run.sh 的大小。
  2. RUN 命令:RUN 命令的作用是在当前空的镜像层内运行一条命令,倘若运行的命令需要更新磁盘文件,那么所有的更新内容都在存储在当前镜像层中。举例说明:RUN echo DaoCloud 命令不涉及文件系统内容的修改,故命令运行完之后当前镜像层的大小为 0;RUN wget http://abc.com/def.tar 命令会将压缩包下载至当前目录下,因此当前这一层镜像的大小为对文件系统内容的增量修改部分,即 def.tar 文件的大小(在成功执行的情况下)。

联合文件系统

Dockerfile 中命令与镜像层一一对应,那么是否意味着 docker build 完毕之后,镜像的总大小是否等于每一层镜像的大小总和呢?答案是肯定的。依然以上图为例:如果 ubuntu:14.04 镜像的大小为 200 MB,而 run.sh 的大小为 5 MB,那么以上三层镜像从上到下,每层大小依次为 0、0 以及 5 MB,那么最终构建出的镜像大小的确为 0 + 0 + 5 + 200 = 205 MB。

虽然最终镜像的大小是每层镜像的累加,但是需要额外注意的是,Docker 镜像的大小并不等于容器中文件系统内容的大小(不包括挂载文件,/proc、/sys 等虚拟文件)。个中缘由,就和联合文件系统有很大的关系了。

首先来看一下这个简单的 Dockerfile 例子(假设在 Dockerfile 当前目录下有一个 100 MB 的压缩文件 compressed.tar):

FROM ubuntu:14.04
 ADD compressed.tar /
 RUN rm /compressed.tar
 ADD compressed.tar /
  1. FROM ubuntu:14.04:镜像 ubuntu:14.04 的大小为 200 MB;
  2. ADD compressed.tar /: compressed.tar 文件为 100 MB,因此当前镜像层的大小为 100 MB,镜像总大小为 300 MB;
  3. RUN rm /compressed.tar:删除文件 compressed.tar,此时的删除并不会删除下一层的 compressed.tar 文件,只会在当前层产生一个 compressed.tar 的删除标记,确保通过该层将看不到 compressed.tar,因此当前镜像层的大小也为 0,镜像总大小为 300 MB;
  4. ADD compressed.tar /:compressed.tar 文件为 100 MB,因此当前镜像层的大小为 300 MB + 100 MB,镜像总大小为 400 MB;

分析完毕之后,我们发现镜像的总大小为 400 MB,但是如果运行该镜像的话,我们很快可以发现在容器根目录下执行 du -sh 之后,显示的数值并非 400 MB,而是 300 MB 左右。主要的原因还是,联合文件系统的性质保证了两个拥有 compressed.tar 文件的镜像层,容器仅能看到一个。同时这也说明了一个现状,当用户基于一个非常大,甚至好几个 GB 的镜像运行容器时,在容器内部查看根目录大小,发现竟然只有 500 MB 不到,甚至更小。

分析至此,有一点大家需要非常注意:镜像大小和容器大小有着本质的区别。

镜像共享关系

Docker 镜像说大不大,说小不小,但是一旦镜像的总数上来之后,岂不是对本地磁盘造成很大的存储压力?平均每个镜像 500 MB,岂不是 100 个镜像就需要准备 50 GB 的存储空间?

结果往往不是我们想象的那样,Docker 在镜像复用方面设计得非常出色,大大节省镜像占用的磁盘空间。Docker 镜像的复用主要体现在多个不同的 Docker 镜像可以共享相同的镜像层。

假设本地镜像存储中只有一个 ubuntu:14.04 的镜像,我们以两个 Dockerfile 来说明镜像复用:

FROM ubuntu:14.04
 RUN apt-get update
FROM ubuntu:14.04
 ADD compressed.tar /

假设最终 docker build 构建出来的镜像名分别为 image 1 和 image 2,由于两个 Dockerfile 均基于 ubuntu:14.04,因此,image 1 和 image 2 这两个镜像均复用了镜像 ubuntu:14.04。 假设 RUN apt-get update 修改的文件系统内容为 20 MB,最终本地三个镜像的大小关系应该如下:

ubuntu:14.04: 200 MB

image 1:200 MB(ubuntu:14.04 的大小)+ 20 MB = 220 MB

image 2:200 MB(ubuntu:14.04 的大小)+ 100 MB = 300 MB

如果仅仅是单纯的累加三个镜像的大小,那结果应该是:200 + 220 + 300 = 720 MB,但是由于镜像复用的存在,实际占用的磁盘空间大小是:200 + 20 + 100 + 320 MB,足足节省了 400 MB 的磁盘空间。在此,足以证明镜像复用的巨大好处。

总结

学习 Docker 的同时,往往有三部分内容是分不开的,那就是 Dockerfile、Docker 镜像与 Docker 容器,分析 Docker 镜像大小也是如此。Docker 镜像的大小,貌似平淡无奇,却是优化镜像、容器磁盘限额必须要涉及的内容。

欢迎通过留言交流。 本文已获授权发表,转载请与作者联系。

本文分享自微信公众号 - 亨利笔记(henglibiji)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-12-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏蓝天

LinuxThread VS NPTL

《原文出自http://blog.csdn.net/guosha, 转载请注明出处》

14030
来自专栏蓝天

Linux下的strerror是否线程安全?

man strerror即可看到相关说明,strerror_r是线程安全的,但不带_r的strerror是非线程安全的。

11030
来自专栏蓝天

Linux中的tty与pts

作者: coolentboy  发布日期: 2006-9-17    出自: http://www.linuxdiyf.com

22920
来自专栏大数据仓库建设

Mac OS X在终端中打开文件夹窗口

版权声明:如未注明转载,则均为公子原创。原创不

17420
来自专栏蓝天

Linux上获取CPU Core个数的实现

1) 调用系统提供的函数get_nprocs(),可以在头文件sys/sysinfo.h中发现它

18750
来自专栏蓝天

Bash 强大的History 命令

Bash的使用GUN readline库来处理用户输入,所以BASH也有emacs/vi 两种模式(主要是快捷键不同)(4DOS没有) 一般来说BASH缺省是e...

11020
来自专栏玄魂工作室

Kali Linux Web渗透测试手册(第二版) - 7.4 - Linux上的权限提升

thr0cyte,Gr33k,花花,MrTools,R1ght0us,7089bAt

19620
来自专栏蓝天

Linux对稀疏(Sparse)文件的支持

文件稀疏化(sparsify) 下面的方法都可以将一个文件稀疏化。 1. cp: $ cp --sparse=always file file.spa...

18530
来自专栏蓝天

linux下文件数、目录数、文件名长度的各种限制

以下测试都是在没有优化或修改内核的前提下测试的结果 1. 测试目的:ext3文件系统下filename最大字符长度   测试平台:RHEL5U3_x64  ...

66120
来自专栏蓝天

linux下测试磁盘的读写IO速度

有时候我们在做维护的时候,总会遇到类似于IO特别高,但不能判定是IO瓶颈还是软件参数设置不当导致热盘的问题.这时候通常希望能知道磁盘的读写速度,来进行下一步的决...

2.1K30

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励