前言
在之前的文章中有说过容器目录的隔离机制. 今天来分析一下镜像的文件系统.
Docker 已经用了很久了, 也知道镜像存储的时候是分层存储的(从docker pull
时分层下载就能看出), 但是具体是如何将多层进行聚合并生成最终展示的文件, 这个过程从未深究过. 既然不知道, 又难掩好奇, 就抽时间康康它具体是怎么做的吧
OverlayFS
OverlayFS
就是这样一个联合挂载的文件系统, 可以看看wiki上的介绍, 也可以看看内核文档中的介绍.
它的作用简单来说就是: 将多个目录的文件内容融合为一个目录.
在OverlayFS
中, 文件目录分为四类:
lowerdir
: 只读目录, 可以有多个, 相同文件上层会覆盖下层.upperdir
: 读写层. 只有一个, 对文件的增删改记录在这里mergedir
: 在此目录中展示合并后的文件内容workdir
: 文件系统的临时目录, 在创建后被清空. 这一层是文件系统使用的, 具体作用在后面说明.
实战
在官方文档中提到, 可以使用mount
命令来挂载OverlayFS
. 我们来测试一下
创建挂载相关的目录及文件:
mkdir lower1 lower2 upper merged work
# lower文件被上层覆盖
echo "lower1" > lower1/lower_change.txt
echo "lower2" > lower2/lower_change.txt
# lower文件在各层添加
echo "lower1" > lower1/lower1.txt
echo "lower2" > lower2/lower2.txt
# lower文件被upper覆盖
echo "lower2" > lower2/upper.txt
echo "upper" > upper/upper.txt
# 文件挂载
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
# 卸载文件系统. 卸载后 merged 目录下的所有文件均会消失
umount <dir>
此时进入merged
目录查看文件, lower2
中的同名文件会被lower1
覆盖. 现在, 让我们来对系统进行简单的测试, 以便于了解其原理(对merged
目录操作):
- 创建文件: 创建的文件会同步出现在
upperdir
目录下 - 修改文件: 修改的文件, 会将修改后的文件在
upperdir
下写一份 - 删除文件: 删除文件就比较有意思了, 将
lower1.txt
文件删除后, 会在upperdir
下创建一个whiteout
文件来标识下层文件被删除. 如下图:
当我们对挂载文件做了一系列操作之后, 可以尝试下这样的操作:
mkdir upper_new
umount merged
mount -t overlay overlay -o lowerdir=upper:lower1:lower2,upperdir=upper_new,workdir=work merged
# mount 命令可以看到当前所有的文件系统
mount
这样我们就将对文件系统的改动记录下来了. 熟悉不熟悉? 这不就是docker save
的操作么.
workdir 作用
至此, 对OverlayFS
系统也有一定了解了, 但是workdir
我们从来就没用到过, 它是做什么的呢? 在官方文件中有说明, 我的理解是: 这个目录是给文件系统使用的.
比如, 文件的删除操作不是一步完成的, 涉及到文件删除, upperdir
创建覆盖文件等操作, 为了防止断电等异常情况, 会接住workdir
来实现原子删除
不过具体的实现细节我没有深究.
镜像
好, 可以来看一下Docker
的镜像存储了.
镜像信息保存在/var/lib/docker/image/overlay2/imagedb/content/sha256/<image sha256>
文件中. 其中rootfs
字段记录了镜像的各层信息, 下方是nginx
镜像的信息. (或者用命令docker image inspect <image>
查看也行)
{
//...
"rootfs":
{
"type": "layers",
"diff_ids":
[
"sha256:8cbe4b54fa88d8fc0198ea0cc3a5432aea41573e6a0ee26eca8c79f9fbfa40e3",
"sha256:4b8862fe7056d8a3c2c0910eb38ebb8fc08785eaa1f9f53b2043bf7ca8adbafb",
"sha256:e60266289ce4a890aaf52b93228090998e28220aef04f128704141864992dd15",
"sha256:7daac92f43be84ad9675f94875c1a00357b975d6c58b11d17104e0a0e04da370",
"sha256:5e099cf3f3c83c449b8c062f944ac025c9bf2dd7ec255837c53430021f5a1517",
"sha256:4fd83434130318dede62defafcc5853d03dae8636eccfa1b9dcd385d92e3ff19"
]
}
}
layer
信息则保存在/var/lib/docker/image/overlay2/layerdb/sha256/<layer sha256>
目录下, 此目录下保存了以下几个文件:
cache-id
: 文件的索引. 当前层的具体数据保存在/var/lib/docker/overlay2/<cache-id>
目录下diff
: 就是上面的diff ids
size
: 当前层大小
好, 现在我们可以到/var/lib/docker/overlay2/<cache-id>/diff
目录下看到这一层的具体数据了.
/var/lib/docker/overlay2/l
目录下保存了diff
目录的软连接, 用于缩短路径的吧, 给文件系统传递的就是这个路径.
至此, 容器启动后是如何做的心里有数了吧. 将多个layer
层作为lowerdir
, 新建一个upperdir
来进行容器内的文件修改. 最终整合成完成的文件目录. 再通过目录挂载替换进程的文件系统.
当然了, 文件系统也不是只有Overlay
, 可以通过docker info
命令查看当前使用的文件系统. 其他的比如: unionfs-fuse
, mergerfs
等等.