Docker镜像存储

前言

之前的文章中有说过容器目录的隔离机制. 今天来分析一下镜像的文件系统.

Docker 已经用了很久了, 也知道镜像存储的时候是分层存储的(从docker pull时分层下载就能看出), 但是具体是如何将多层进行聚合并生成最终展示的文件, 这个过程从未深究过. 既然不知道, 又难掩好奇, 就抽时间康康它具体是怎么做的吧

OverlayFS

OverlayFS就是这样一个联合挂载的文件系统, 可以看看wiki上的介绍, 也可以看看内核文档中的介绍.

它的作用简单来说就是: 将多个目录的文件内容融合为一个目录.

OverlayFS中, 文件目录分为四类:

  1. lowerdir: 只读目录, 可以有多个, 相同文件上层会覆盖下层.
  2. upperdir: 读写层. 只有一个, 对文件的增删改记录在这里
  3. mergedir: 在此目录中展示合并后的文件内容
  4. 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>

image-20230604100836804

此时进入merged目录查看文件, lower2中的同名文件会被lower1覆盖. 现在, 让我们来对系统进行简单的测试, 以便于了解其原理(对merged目录操作):

  1. 创建文件: 创建的文件会同步出现在upperdir目录下
  2. 修改文件: 修改的文件, 会将修改后的文件在upperdir下写一份
  3. 删除文件: 删除文件就比较有意思了, 将lower1.txt文件删除后, 会在upperdir下创建一个whiteout文件来标识下层文件被删除. 如下图:

image-20230604101625571

当我们对挂载文件做了一系列操作之后, 可以尝试下这样的操作:

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等等.

image-20230604110208409

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x