解决我在学习 docker 中提出的一个问题: 镜像到底存在哪了?什么格式的?(初步探究一下)
在上传的过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。(相同的摘要可以共享,即只保留一份)
每一层 ID 的前12位,以及结束后 sha256 摘要确保一致。
以前看 docker info 的时候提到了 Docker root dir
是 /var/lib/docker
,如果你没有镜像,这个目录不存在,如果有了镜像,那么这个目录?
目录依旧不存在,那么到底 image 存储在哪了?
btw: 我什么没事儿要关心它放在哪儿了?吃饱了没事儿?因为我的 iMac 容量太小了,先把它挂在到外置磁盘上。
image 是什么?
从容器的角度来说, image 就是一个可执行单元,用于产生一个 container 实例。
《Docker 入门与实践》
开头有一段就说:
镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像是分层构建的:
因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。
所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。
比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
也就是一个增量的多层文件系统,或者称为 UnionFS (联合文件系统)。
联合文件系统,可以将几层目录挂载到一起,形成一个虚拟文件系统(目录和文件系统的关系不清楚的就不解释了)。该文件系统的目录结构和宿舍一致(即容器运行时的那个目录结构),然后加上 linux支持就形成了一个 linux 环境。UnionFS 可以为每一层设置得写权限:
- readonly 只读
- readwrite 读写
- writeout-able 写出
但是 docker 镜像做了规定,它的多层文件系统,每一层都只读,所以构建的时候,但凡修改就相当于增加一层文件系统。一层层往上叠加,上层的修改会覆盖底层该位置的可见性。当你使用的时候,你只会看到一个完全的整体,你不知道里面有几层,也不清楚每一层所做的修改是什么。结构类似下图:
从基本的看起,一个典型的 Linux 文件系统由 bootfs 和 rootfs 两部分组成,bootfs(boot file system) 主要包含bootloader 和 kernel,bootloader 主要用于引导加载 kernel,当 kernel 被加载到内存中后 bootfs 会被 umount 掉。rootfs (root file system) 包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc 等标准目录和文件。下图就是 docker image 中最基础的两层结构,不同的 linux 发行版(如 ubuntu 和 CentOS ) 在 rootfs 这一层会有所区别,体现发行版本的差异性。
bootfs 和 rootfs 见下图: (不同的发型版本 rootfs 可能会有区别;所以可以看到挂载出来的目录结构有一定的差异)
传统的 Linux 加载 bootfs 时会先将 rootfs 设为 read-only,然后在系统自检之后将 rootfs 从 read-only 改为 read-write,然后我们就可以在 rootfs 上进行读写操作了。但 Docker 在 bootfs 自检完毕之后并不会把 rootfs 的 read-only 改为 read-write,而是利用 union mount(UnionFS 的一种挂载机制)将 image 中的其他的 layer 加载到之前的 read-only 的 rootfs 层之上,每一层 layer 都是 rootfs 的结构,并且是read-only 的。所以,我们是无法修改一个已有镜像里面的 layer 的!
只有当我们创建一个
容器
,也就是将 Docker 镜像进行实例化,系统会分配一层空的 read-write 的 rootfs ,用于保存我们做的修改。
每层 layer 所保存的修改是增量式的,就像 git 一样。大概就像下图一样(从两个侧面看,容器状态才能修改):
最终总结: iamge 是一个增量的特殊的多层文件系统,本身不允许修改每层;除非加载为容器,然后重新构建新的层。
但是仅有文件系统可不能模拟一个 linux 环境,所以启动 docker container 时共享底层内核及系统feature或者相关依赖,然后才构建出一个虚拟的linux环境。
还嫌不够的话,这里有一篇通俗易懂的讲解 联合文件系统的文章 Overlay Filesystem
也可以用 docker commit
, docker diff
结合 docker history
对比看看,修改了哪些内容,是否是原来的镜像多了一层。
AUFS 的好处
换句话说 Docker 镜像是怎么实现增量的修改和维护的
?
每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。
通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
Docker 在 AUFS 上构建的容器也是利用了类似的原理。
image 存储在哪里?
macOS 可能有点儿特殊吧? 你看它的存储位置,根本看不出来有什么名堂:(分层存储和具体的驱动有关)
貌似和我看到的资料都不太一样,(至少我看到以前的资料说 ubuntu用的是 aufs,但实际上已经改成了 overlay2)。
说明: Docker目前支持五种镜像层次的存储driver:aufs、device mapper、btrfs、vfs、overlay。
其他参考: (参考《Docker入门与实践》)
centOS 的情况是这样的: 《docker 镜像与容器存储目录结构精讲》
ubuntu 下的情况:
但是 overlay2 目录下还是保留着 repositories.json,以及layerdb/mounts等,只是改换了目录:
没有运行的时候,image 目录下的分层是这样的:
但是和原来不同的是,这里的 images 的中间件信息只是简单的记录了一下,数据并不存储在这里。(上一层级也是保留着 repositories.json)
跑起来容器之后,目录会有变化么?
1 | ## 跑起来容器,然后再去查看镜像的变化 |
没发现哪里有差别,包括 mnt 目录。。。。(完全不知道设计人员是怎么设计的,忧桑)
- 分块 hash 存储
- 容器存储技术
参考一下官网手册,但是关于镜像的存储设计方面,它也没有在 doc 中给出来;先实际用起来,后续再来研究。
Merlin 对于镜像的存储设计不甚了解,第二轮迭代再来深挖