深刻理解Docker镜像大小
都说容器大法好,但是如果没有Docker镜像,Docker该是多无趣啊。是否还记得第一个接触Docker的时候,你从Docker Hub下拉的那个镜像呢?在那个处女镜像的基础上,你运行了容器生涯的处女容器。镜像的基石作用已经很明显,在Docker的世界里,可以说是:No Image,No Container。再进一步思考Docker镜像,大家可能很快就会联想到以下几类镜像:1.系统级镜
都说容器大法好,但是如果没有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镜像,DockerRegistry镜像等。
镜像林林总总,想要运行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镜像的大小为200MB,而run.sh的大小为5MB,那么以上三层镜像从上到下,每层大小依次为0、0以及5MB,那么最终构建出的镜像大小的确为0+0+5+200=205MB。
虽然最终镜像的大小是每层镜像的累加,但是需要额外注意的是:Docker镜像的大小并不等于
容器中文件系统内容的大小(不包括挂载文件,/proc、/sys等虚拟文件)。个中缘由,就和联合文件系统有很大的关系了。
首先来看一下这个简单的Dockerfile例子(假如在Dockerfile当前目录下有一个100MB的压缩文件compressed.tar):
FROM ubuntu:14.04
ADD compressed.tar /
RUN rm /compressed.tar
ADD compressed.tar /
1.FROM ubuntu:镜像ubuntu:14.04的大小为200MB;
2.ADD compressed.tar /: compressed.tar文件为100MB,因此当前镜像层的大小为100MB,镜像总大小为300MB;
3.RUN rm /compressed.tar:删除文件compressed.tar,此时的删除并不会删除下一层的compressed.tar文件,只会在当前层产生一个compressed.tar的删除标记,确保通过该层将看不到compressed.tar,因此当前镜像层的大小也为0,镜像总大小为300MB;
4.ADD compressed.tar /:compressed.tar文件为100MB,因此当前镜像层的大小为300MB+100MB,镜像总大小为400MB;
分析完毕之后,我们发现镜像的总大小为400MB,但是如果运行该镜像的话,我们很快可以发现在容器根目录下执行du -sh
之后,显示的数值并非400MB,而是300MB左右。主要的原因还是:联合文件系统的性质保证了两个拥有compressed.tar
文件的镜像层,仅仅会容器看到一个。同时这也说明了一个现状,当用户基于一个非常大,甚至好几个GB的镜像运行容器时,在容器内部查看根目录大小,发现竟然只有500MB不到,设置更小。
分析至此,有一点大家需要非常注意:镜像大小和容器大小有着本质的区别。
镜像共享关系
Docker镜像说大不大,说小不小,但是一旦镜像的总数上来之后,岂不是对本地磁盘造成很大的存储压力?平均每个镜像500MB,岂不是100个镜像就需要准备50GB的存储空间?
结果往往不是我们想象的那样,Docker在镜像复用方面设计得非常出色,大大节省镜像占用的磁盘空间。Docker镜像的复用主要体现在:多个不同的Docker镜像可以共享相同的镜像层。
假设本地镜像存储中只有一个ubuntu:14.04的镜像,我们以两个Dockerfile来说明镜像复用:
FROM ubuntu:14.04
RUN apt-get update
FROM ubuntu:14.04
ADD compressed.tar /
假设最终docker build构建出来的镜像名分别为image1和image2,由于两个Dockerfile均基于ubuntu:14.04,因此,image1和image2这两个镜像均复用了镜像ubuntu:14.04。 假设RUN apt-get update
修改的文件系统内容为20MB,最终本地三个镜像的大小关系应该如下:
ubuntu:14.04: 200MB
image1:200MB(ubuntu:14.04)+20MB=220MB
image2:200MB(ubuntu:14.04)+100MB=300MB
如果仅仅是单纯的累加三个镜像的大小,那结果应该是:200+220+300=720MB,但是由于镜像复用的存在,实际占用的磁盘空间大小是:200+20+100=320MB,足足节省了400MB的磁盘空间。在此,足以证明镜像复用的巨大好处。
总结
学习Docker的同时,往往有三部分内容是分不开的,那就是Dockerfile,Docker镜像与Docker容器,分析Docker镜像大小也是如此。Docker镜像的大小,貌似平淡无奇,却是优化镜像,容器磁盘限额必须要涉及的内容。
本系列将通过以下多篇文章来分析Docker镜像:
1.深刻理解 Docker 镜像大小
2.其实 docker commit 很简单
3.不得不说的 docker save 与 docker export 区别
4.为什么有些容器文件动不得
5.打破 MNT Namespace 的容器 VOLUME
欢迎关注[Docker源码分析]微信公众号,更多精彩即将呈现。
更多推荐
所有评论(0)