overlayfs与overlayfs2下编译镜像的区别

近期编译镜像时, 出现了镜像体积骤增的情况, 排查后, 发现本质原因居然是由于升级了存储引擎导致的问题, overlayfs 与 overlayfs2 存储引擎实现多层联合文件系统的原理不同, 最终导致了我们遇到的这个问题

昨日重现

镜像引用关系图

1
2
3
4
5
6
7
centos:7.5

base-os (创建了一系列目录, 安装了部分命令工具)

open-jdk (下载并部署 jdk 至 /opt/soft 下, 335MB)

application (应用部署, 且执行了 chown user:user /opt)

引发镜像体积增大的原因: 在 open-jdk dockerfile 中, 将 jdk 文件拷贝至 /opt/soft 下, 该文件夹体积为335MB; 在 application dockerfile 中, 执行了一行 shell 命令 chown -R /opt

从编译镜像最佳实践来说, chown -R /opt 这样的匹配命令是不应该出现的, 其实体积增大的道理也很简单, 由于更改了整个/opt目录的属性, 导致/opt目录下的所有文件被重新拷贝到 application 镜像层中, 也就造成了 335MB 空间的浪费, 体积至少也无端增大了 335MB. 但问题是, 之前在 overlayfs 存储引擎下编译镜像, 却一直相安无事, 所有镜像版本都迭代了很多次, 没有出现上面预期的335MB 的空间浪费. 造成这样的问题, 是由于 overlayfs 和 overlayfs2 在实现多层联合文件系统的原理不同.

overlayfs vs overlayfs2

overlayfs将单个Linux主机上的两个目录分层并将它们呈现为单个目录. 这些目录称为图层, 统一过程称为联合装载. overlayfs将下部目录称为lowerdir上部目录upperdir. 统一视图通过自己的目录公开merged.
虽然overlay驱动程序只能使用一个较低的overlayfs层, 因此需要硬链接来实现多层图像, 但该overlay2驱动程序本身最多支持128个较低的overlayfs层. 此功能为和层相关的Docker命令(如docker build)提供更好的性能docker commit,并且在后备文件系统上占用更少的inode —-引用自https://cloud.tencent.com/developer/section/1091813

问题原因

简单来说, 之前在 application 层执行 chown 一个335MB文件夹之所有没有引发空间浪费问题, 完全是由于使用 overlayfs 的硬链接特性造成的.

之后改为 overlayfs2 存储引擎后, 层与层之间不再使用硬链接形式引用, 所以根据标准情况下镜像层之间的增删改原理推断, application 镜像层至少浪费了 335MB 的空间.(由于 chown 操作导致整个/opt目录文件被拷贝至 application 层)

chown 是个很神奇的命令, 它只修改文件的 metadata 属性, 这完美的切中了 overlayfs 与 overlayfs2 最本质的区别.

科普硬链接

简单来说, 硬链接是有着相同 inode 号仅文件名不同的文件, 因此硬链接存在以下几点特性:

  • 文件有相同的 inode 及 data block
  • 只能对已存在的文件进行创建
  • 不能交叉文件系统进行硬链接的创建
  • 不能对目录进行创建,只可对文件创建
  • 删除一个硬链接文件并不影响其他有相同 inode 号的文件

由于 overlayfs 的硬链接引用的原理, chown 只更改 metadata 数据, 而 metadata 是作用在 inode 节点上的, 一个硬链接文件的属性被修改, 同步的, 所有指向这个 inode 节点的文件的属性都会变化. 也就是说, overlayfs 通过硬链接将文件挂载到新的镜像层之后, 对里面已存在的文件做 chown 操作, 意味着底层(只读层)的文件属性也会被修改, docker 就会认为这个操作没有引发任何文件的变化, 所以就不会再拷贝那335MB 的文件. 而 overlayfs2 的每层都是独立的, 即使文件属性的变化, 也会导致整个文件被拷贝, 所以在 overlayfs2 下, 会产生335MB 的空间浪费


参考文档: