关键词:Docker 联合文件系统 镜像 容器 云信私有化
在本文中,我们来实战构建一个 Docker 镜像,然后实例化容器,在 Docker 的生命周期中详细分析一下 Docker 的文件存储情况和 DockerFile 优化策略。
在开始实战之前,我们先介绍一个概念,联合文件系统(Union File System)。联合文件系统是实现 Docker 镜像的技术基础,支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。镜像的分层存储和继承就是基于此特性实现。
下面是 Docker 官方的一张描述文件系统的图片,显示了一张联合文件系统在串联镜像层和容器层起到的作用
Docker 支持多种联合文件系统,常见的有 aufs,deviceMapper,overlay,overlay2,本文章中使用的系统版本为 debian9.1,Docker 版本为 17.06.2-ce,默认使用是 overlay2。
看到这里如果你已经对 Docker 文件系统有了简单的概念,那么让我们开始实战,来对分层文件系统的存储方式进行更加深入的了解。
镜像层
这是一个云信私有化项目中基于 debian 系统镜像创建的 jdk8 基础镜像,为了方便阅读和分析,我们 Dockerfile 进行了一些精简,只保留核心部分内容
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"\]
借这个优化后的内容,我们再谈一下构建 Docker 镜像时在 时间和空间可优化的点:
- 组合运行语句:合并相同类型构建语句,可以有效减少镜像分层;
- 利用镜像构建缓存:时间同步,基础软件安装等固定内容在镜像前部分处理,镜像重新构建时会使用缓存,节省时间;
- 清理中间产物:注意安装过程中使用的软件和压缩包在 一定要同一层里清理,否则仍然会占用镜像空间;
- 构建语句优化:比如 ADD 在处理本地文件时可以直接解压缩,起到 COPY + RUN tar 的作用;
- 优化基础镜像源:国内高校和大型 IT 企业都有创建镜像站,选择一个稳定更新及时的镜像站可以有效缩短构建时间;
举例的镜像中优化策略涉及 1,3 条,用 curl 替代 add,与解压和删除合并为一层,Dockerfile 减少了层数,清理中间过程的 jdk 安装包,下图是优化后镜像体积变化:
构建镜像真的是层数越少越好吗?当然不是这么绝对,尤其在早期镜像版本不是很稳定或是后续迭代比较频繁时,合理的镜像分层会减少编译时间,降低出错概率,也可以让 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 关联镜像基础元数据信息。
容器层
首先我们来启动一个容器,挂载宿主机 /opt/yunxin 目录到容器 /usr/local/yunxin 目录
创建容器完成之后,在镜像存储目录 /var/lib/Docker/overlay2/ 会生成容器的初始层和读写层,两者使用相同标识,初始层后面多了 -init。初始层中主要保存初始化容器环境时,与容器相关的环境信息,如容器主机名,主机 host 信息以及域名服务文件等;读写层用于容器的读写,Docker容器内的进程只对读写层拥有写权限,而对其他层文件内容只拥有读权限。
接下来我们进入容器操作进行一系列操作,再根据结果分析一下读写层对于文件的保存和处理,下面是操作和对应结果以及读写层实际文件存储情况。
序号 | 类 型 | 操 作 | 表 现 |
---|---|---|---|
1 | 写入新文件 | 写入 /root/container_file.txt | 写入读写层 |
2 | 挂载目录写入新文件 | 写入 /usr/local/yunxin/mount_file.txt | 不写入读写层,仅保存在挂载目录 |
3 | 修改镜像原有文件 | 修改 /usr/local/nim/jdk1.8.0_202/THIRDPARTYLICENSEREADME.txt | 写入读写层 |
4 | 删除镜像原有文件 | 删除 /usr/local/nim/jdk1.8.0_202/README.html | 保存在读写层 |
读写层中的 merged 文件夹提供了统一视图,面向用户展示联合文件系统挂载完成的最终形态。
接下来我们再基于同一个镜像启动几个容器实例,然后来查询一下 Docker 容器使用空间,只有第一个容器由于上面修改文件只占用 154k,新启动的容器并没有额外占用空间。可见 基于同一个镜像创建容器时,所有的容器共享镜像层内容,有效节约了空间。读写层只保存修改内容,如果是操作镜像层文件,Docker 采用的是修改时复制策略(copy-on-write)。这时回头再看一下第一节出现的两张图,会对 Docker 的文件系统有了更深的体会。
结语
Docker 镜像和容器文件系统相关知识在云信私有化产品的镜像管理和运维存储管理方面作出理论支撑,但这只是深入了解 Docker 的开始。随着时间的积淀和云信旗下 IM、音视频、点播以及众多相关产品私有化工作的深入,更多的模块和镜像,更多的客户和需求,更复杂的网络和环境都逐渐呈现在我们面前。Docker 作为构建云信私有化服务的基础,只有更深入的去了解原理才能在使用中去更好的优化产品和开展运维。希望我们能为用户提供更可靠的云信私有化服务,也希望能在后续的文章中能与大家分享更多关于 Docker 的知识。
立即了解网易云信私有云 >>
更多技术干货,欢迎关注 vx 公众号 “ 网易智慧企业技术+”。系列课程提前看,精品礼物免费得,还可直接对话 CTO。
听网易 CTO 讲述前沿观察,看最有价值技术干货,学网易最新实践经验。网易智慧企业技术 +,陪你从思考者成长为技术专家。