Linux-容器技术基石3-overlay文件系统

前言

docker 中, 我们可以使用同一个镜像创建不同的容器, 这些容器中的数据是完全隔离的, 容器中的数据的更改, 不会在镜像中的生效, 并且在容器间, 数据都是隔离的, 互不影响的, 这是怎么做到的呢? 底层使用的就是 linux overlay 文件系统,

下面, 我们便通过一个简单的例子, 来探索 overlay 的使用

什么是 overlay 文件系统

overlay 一词便可以猜测出, 这里边有 覆盖 的概念, 其中的 上层下层 的概念, 每一个 看起来都是一个独立的文件系统, 有着各自的目录和文件,将这些层进行堆叠覆盖最终合并成一个面向用户文件系统, 这个文件系统 就叫作 overlay文件系统, 对于用户来说, 最终看到并且操作的就是这个合并后文件系统, 所有的增删改查 操作, 就跟操作平常文件系统无异.

由于这个文件系统是由多层合并的, 合并规则是怎么样的? 并且在合并层 进行 增删改查操作时, 相应的在下面各个层是怎么反应的? 以及这个系统最终是怎么在docker 中应用的? 这便是本文需要重点探索的问题.

overlay 文件系统的结构

overlay 文件系统3 部分 组成

  1. 合并层: 最终面向用户的层
  2. 上层: 堆叠层中的最上面一层
  3. 若干的下层: 堆叠层中除了最上一层的所有下层

overlay 文件系统例子

我们尝试创建多个文件夹, 分别表示overlay 文件系统 的各个层, 然后使用 mount 命令, 创建一个 overlay 类型文件系统, 创建完毕, 我们查看每个文件夹的内容以及变化

  • 创建例子文件夹

    我们创建一个测试目录 test-overlayfs, 在这个目录下, 我们创建4 个目录, 分别为 lower, upper, merge, work

    mkdir lower upper merge work
    

    3 个文件夹的含义表示 overlay 文件系统中的 下层, 上层合并层, 至于最后一个文件夹 work, 这是为 overlay 文件系统储存 元数据 准备的, 用来维持 overlay 文件系统, 我们不用管

  • 创建例子文件

    lower 文件夹下创建 3 个文件, lower.txt, delete-me.txt, duplicate.txt, 并在 duplicate.txt 文件中加入特有内容

    touch lower.txt delete-me.txt duplicate.txt
    echo "this is duplicate file in lower" > duplicate.txt
    

    类似的, 我们在 upper 文件夹下创建 2 个文件 upper.txt, duplicate.txt, 同样在 duplicate.txt 文件中加入特有内容

    touch upper.txt duplicate.txt
    echo "this is duplicate file in upper" > duplicate.txt
    

    我们能观察到, 两个文件夹分别对映着两层, 两层 中的文件有些许差异, 那么如果进行 堆叠merge, 最终 在 merge 层, 会怎么反映呢? 我们想探索以下几个问题:

    1. 两层有一个相同文件 duplicate.txt, 那么如果进行 堆叠merge, 是用哪一个 duplicate.txt 呢?
      • 既然是堆叠, 我猜测上层的覆盖下层的 (-> _ ->)
    2. 两层 都存在独有的文件, 如 lowerlower.txt, delete-me.txt; 还有 upperupper.txt, 最终在 merge 层, 应该是进行简单合并吧, 都存在 ?
    3. 在合并后, 如果我删除 lower 中的文件, 如 delete-me.txt, 会同时删除 merge 中的文件吗? 同样的, 如果删除 upper 中的文件呢?

为了探索上述问题, 我们赶快来尝试一下吧.

  • 使用 mount 挂载一个 overlay 类型文件系统

    在测试目录 test-overlayfs 下执行

    sudo mount -t overlay -o lowerdir=lower/,upperdir=upper/,workdir=work none merge/
    

    我们来解释一下这个命令, 首先我们指定了要挂载一个 overlay 类型的文件系统, 并且指定了下层, 下层 文件夹分别为 lower, upper, 不仅如此, 还指定了工作目录, 以及合并后的目录, 至于其中的 none, 可以忽略, 据说这是用来指定设备的, 我们这个例子没有额外设备, 我们这里不需要深究, 设置成 none 就行

  • 观察结果
    • 第 2 个问题探索

      两层 都存在独有的文件, 如 lowerlower.txt, delete-me.txt; 还有 upperupper.txt, 最终在 merge 层, 应该是进行简单合并吧, 都存在 ?

      我们使用命名 tree 观察

      root@walkerjun:/home/walkerjun/test-overlayfs# tree
      .
      ├── lower
      │   ├── delete-me.txt
      │   ├── duplicate.txt
      │   └── lower.txt
      ├── merge
      │   ├── delete-me.txt
      │   ├── duplicate.txt
      │   ├── lower.txt
      │   └── upper.txt
      ├── upper
      │   ├── duplicate.txt
      │   └── upper.txt
      └── work
          └── work
      
      

      我们可以看到在merge文件夹中, 两层 中的独有 文件都存在, 这可以很好解释我们上面的第2 个问题: 对于各层的独有文件, 最终都可以合并到 merge 层的

    • 第 1 个问题探索

      两层有一个相同文件 duplicate.txt, 那么如果进行 堆叠merge, 是用哪一个 duplicate.txt 呢?

      我们再来观察 merge 文件夹中的 duplicate.txt 文件

      root@walkerjun:/home/walkerjun/test-overlayfs# cat merge/duplicate.txt 
      this is duplicate file in upper
      

      我们可以看到, merge 中的 duplicate.txt 用的是 upper 中的, 这符合我们对第 1 个问题的猜想: 对于各层中的 同名文件, 是使用 覆盖策略, 会优先使用 上层 的文件

    • 第 3 个问题探索

      在合并后, 如果我删除 lower 中的文件, 如 delete-me.txt, 会同时删除 merge 中的文件吗? 同样的, 如果删除 upper 中的文件呢?

      我们尝试删除 lower 中的 delete-me.txt

      root@walkerjun:/home/walkerjun/test-overlayfs# rm lower/delete-me.txt 
      
      root@walkerjun:/home/walkerjun/test-overlayfs# ls merge/*
      merge/duplicate.txt  merge/lower.txt  merge/upper.txt
      

      merge 中的文件也消失掉了诶, 那么如果在 lower 中添加新文件呢? 会在 merge 中直接出现吗?

      root@walkerjun:/home/walkerjun/test-overlayfs# touch lower/test-new-file.txt
      root@walkerjun:/home/walkerjun/test-overlayfs# ls merge/*
      merge/duplicate.txt  merge/lower.txt  merge/test-new-file.txt  merge/upper.txt
      

      果然的确是这样的, 并且相同的操作在 upper 中操作也是一样的, 但是加入逆向操作呢? 我在 merge 中删除刚刚在 lower 中新建的文件 test-new-file.txt

    • 新问题: merge 中的操作会同步到 下层去吗?
      root@walkerjun:/home/walkerjun/test-overlayfs# rm merge/test-new-file.txt 
      
      root@walkerjun:/home/walkerjun/test-overlayfs# ls lower/*
      lower/duplicate.txt  lower/lower.txt  lower/test-new-file.txt
      
      root@walkerjun:/home/walkerjun/test-overlayfs# ls merge/*
      merge/duplicate.txt  merge/lower.txt
      
      root@walkerjun:/home/walkerjun/test-overlayfs# ls upper/*
      upper/duplicate.txt  upper/test-new-file.txt
      
      root@walkerjun:/home/walkerjun/test-overlayfs# ls -l upper/*
      -rw-rw-r-- 1 walkerjun walkerjun   32  7月  6 19:12 upper/duplicate.txt
      c--------- 2 root      root      0, 0  7月  6 20:12 upper/test-new-file.txt
      

      这个操作发生了有意思的事情, 在 merge 中的 删除 操作并不会 同步下层去, lower 中的对应文件仍然存在, 这就能解释为什么在docker 中, 容器的数据更改, 不会影响镜像中的数据了, 那就是因为, 容器 的文件系统是 merge 层, 然而 镜像 的文件系统中只是 下层 而已. 除此之外, 还有个小现象, 被删除的下层 文件在 上层 upper 中有一个标记文件, 这个似乎只是告诉用户这个文件在 merge 层删掉了

      上面尝试了在 merge删除数据, 其实是不完全是更改数据, 再尝试下在 merge 层更改 下层 文件内容, 我猜想为了让下层能通用, merge 层的文件内容更改, 是不会同步到 下层

      # 在 lower 增加新文件
      root@walkerjun:/home/walkerjun/test-overlayfs# touch lower/test-new-file2.txt
      root@walkerjun:/home/walkerjun/test-overlayfs# echo "this is test content in test-new-file2.txt" > lower/test-new-file2.txt
      
      root@walkerjun:/home/walkerjun/test-overlayfs# cat merge/test-new-file2.txt 
      this is test content in test-new-file2.txt
      
      # 在 merge 修改对应文件
      root@walkerjun:/home/walkerjun/test-overlayfs# echo "this is extra content that added from merge" >> merge/test-new-file2.txt 
      
      root@walkerjun:/home/walkerjun/test-overlayfs# cat merge/test-new-file2.txt 
      this is test content in test-new-file2.txt
      this is extra content that added from merge
      
      # 查看是否同步到 lower
      root@walkerjun:/home/walkerjun/test-overlayfs# 
      root@walkerjun:/home/walkerjun/test-overlayfs# cat lower/test-new-file2.txt 
      this is test content in test-new-file2.txt
      

      如上, 在 merge 层修改的内容并不会同步到 下层

    • 新问题: merge 层修改的内容会不会同步到 上层 upper?

    这个问题, 我似乎没那么在乎了, 因为我已经知道了多个容器能用同一个镜像的原理了, 这个upper 是哪一层, 我好像不 care

参考

Linux overlayfs文件系统介绍 | CSDN
Exploring the Power of Overlay File Systems in Linux Containers | Medium