下面是Docker官方的一张描述文件系统的图片,显示了一张联合文件系统在串联镜像层和容器层起到的作用
Docker支持多种联合文件系统,常见的有aufs,deviceMapper,overlay,overlay2,本文章中使用的系统版本为debian9.1,Docker版本为17.06.2-ce,默认使用是overlay2。
FROM hub.c.163.com/library/debian:stretch MAINTAINER nim #下载jdk ADD http://10.173.11.100/nim/jdk-8u202-linux-x64.tar.gz /usr/local/nim/ #解压jdk并删除 RUN tar -xzvf /usr/local/nim/jdk-8u202-linux-x64.tar.gz -C /usr/local/nim/ \ && rm /usr/local/nim/jdk-8u202-linux-x64.tar.gz #设置环境变量 ENV JAVA_HOME=/usr/local/nim/jdk1.8.0_202 ENV PATH=$JAVA_HOME/bin:$PATH CMD ["/bin/bash"] 根据构建镜像,查看构建结果,原基础镜像100M,构建后镜像体积697M。复制代码
现在开始看一下构建镜像工作在文件层存储情况。首先我们使用Docker history查看一下刚刚构建镜像情况,可以看到基础镜像占用100M,两个镜像分层占用194MB和403M。
接下来我们看查看一下文件系统中的存储情况,本环境使用overlay2,Docker镜像层存储默认路径为/var/lib/Docker/overlay2/,可以看到镜像存储目录下有4个目录,其中110M的对应是基础镜像,另外两个为ADD JDK(186M)和解压JDK压缩包的镜像分层(389M)。
其中的l目录包含了所有层的软连接,软链接使用短名称,避免mount时候参数达到页面大小限制。
下面我们了解一下,每个分层中的文件内容。基础镜像分层包含diff文件夹和link文件,diff文件夹中存放当前分层内容,link文件记录短名称。
接下来看一下COPY JDK生成的内容,diff文件夹保存了jdk压缩包,本层相比基础镜像层,多了lower,merged,work三个文件/文件夹,其中lower记录了此层的下层ID(基础镜像层),merged目录作为提供了统一视图,在容器层读写层被使用,work目录用于联合挂载指定的工作目录,使用过程对用户不可见。
解压JDK层的文件夹结构内容和上一层类似,主要关注jdk压缩包占用空间为0,表示已被删除。
现在来重点关注一个问题,镜像大小等于所有分层相加,在后续分层中被删除的jdk压缩包仍然要占用存储空间,这并不是我们原本意图,因此这里就出现了镜像文件进行优化的点。优化后的Dockerfile如下
FROM hub.c.163.com/library/debian:stretch MAINTAINER nim RUN curl -o /usr/local/nim/jdk-8u202-linux-x64.tar.gz http://10.173.11.100/nim/jdk-8u202-linux-x64.tar.gz \ && tar -xzvf /usr/local/nim/jdk-8u202-linux-x64.tar.gz -C /usr/local/nim/ \ && rm /usr/local/nim/jdk-8u202-linux-x64.tar.gz \ && export JAVA_HOME=/usr/local/nim/jdk1.8.0_202 \ && export PATH=$JAVA_HOME/bin:$PATH CMD ["/bin/bash"]复制代码
构建镜像真的是层数越少越好吗?当然不是这么绝对,尤其在早期镜像版本不是很稳定或是后续迭代比较频繁时,合理的镜像分层会减少编译时间,降低出错概率,也可以让Dockerfile更具有可读性。可以再稳定版本形成之后对镜像进行二次优化。
/var/lib/Docker/image/overlay2/imaged/ /var/lib/Docker/image/overlay2/layerdb/ /var/lib/Docker/overlay2/复制代码
Docker镜像的基本信息保存在/var/lib/Docker/image/overlay2/imaged/content/sha256/下面,可以根据Docker image ID在此目录下查找到对应ID开头文件。此文件中以json的形式保存了该镜像的分层文件系统、构建信息、相关容器等内容。
第二个目录/var/lib/Docker/image/overlay2/layerdb/sha256/保存分层元数据,每一个分层元数据目录下有cache-id,diff,size信息,其中cache-id对应分层存储层,diff关联镜像基础元数据信息。
创建容器完成之后,在镜像存储目录/var/lib/Docker/overlay2/会生成容器的初始层和读写层,两者使用相同标识,初始层后面多了-init。初始层中主要保存初始化容器环境时,与容器相关的环境信息,如容器主机名,主机host信息以及域名服务文件等;读写层用于容器的读写,Docker容器内的进程只对读写层拥有写权限,而对其他层文件内容只拥有读权限。
接下来我们进入容器操作进行一系列操作,再根据结果分析一下读写层对于文件的保存和处理,下面是操作和对应结果以及读写层实际文件存储情况。
读写层中的merged文件夹提供了统一视图,面向用户展示联合文件系统挂载完成的最终形态。
接下来我们再基于同一个镜像启动几个容器实例,然后来查询一下Docker容器使用空间,只有第一个容器由于上面修改文件只占用154k,新启动的容器并没有额外占用空间。可见基于同一个镜像创建容器时,所有的容器共享镜像层内容,有效节约了空间。读写层只保存修改内容,如果是操作镜像层文件,Docker采用的是修改时复制策略(copy-on-write)。这时回头再看一下第一节出现的两张图,会对Docker的文件系统有了更深的体会。
更多技术干货,欢迎关注vx公众号“网易智慧企业技术+”。系列课程提前看,精品礼物免费得,还可直接对话CTO。