前言
随着Linux Kernel从3.18开始,将Overlay文件系统纳入主线开发维护,到目前为止,经过不断的进行完善和开发,Overlay文件系统的地位变得越来越重要,并且从各方面来说,其作用和优点也越来越显著。
对于Overlay的原理,网上有很多文章进行了详细介绍,此处不再赘述其原理,下面仅做大致描述,以方便读者对文章后续的理解。
Overlay基本原理
Overlay文件系统类似于堆叠的文件系统,它本身不像其他传统文件系统(jffs2、ext4、fat…)那样,直接参与磁盘空间及节点存储的划分。它更像是一个皮包公司,依赖并构建于其他传统文件系统之上,“合并”底层传统文件系统中文件内容的差异并进行“合并”,然后再向用户呈现。因此用户看到的文件系统根目录,其实是overlay“合并”的文件系统目录的“合集”。
文件系统的理解,可以参考一本书的结构:
一本书有厚有很多内容(类比为磁盘空间),而每本书前面的目录(类比为文件系统),可以帮我们准确的定位到你要查找的内容在书正文的那一页(类比为文件系统inode节点)。
为了便于理解,来一张图进行简单说明:
Overlay应用场景很多,下面仅从两个接触到的应用领域进行较详细的说明:
- Overlay在嵌入式系统中的应用
- Overlay在Docker中的应用
1. Overlay在嵌入式系统中的应用
众所周知,在嵌入式通信行业,尤其是需要跑Linux操作系统的嵌入式通信产品,如交换机、WiFi路由器、GPON ONU等设备中,由于系统硬件资源及成本限制,存储通常使用SPI接口的Flash,容量基本8/16/32MByte居多(Nand Flash一般存储容量大,但是运行里面的Kernel及文件系统需要先将此分区读取出来加载在内存运行,这种方式不在讨论范围),如果使用在工业级设备上的话,为了系统稳定及容灾的考虑,都会做双image备份,这就导致存储资源更加紧缺。
而制作根文件系统使用的主要有jffs2和squashfs,两者的区别在于,jffs2 的文件系统可读写,但是压缩比率较低,squash的文件系统为只读,压缩率高,所以比较普遍的做法有两种,各有优缺点,根据自已也无需求选择使用,分别如下:
- 传统方式
- Overlay方式
这样实现的方式,如果要实现类似恢复出厂设置的功能时,只需要将jffs2的用户数据分区内容直接删除即可。
1.1 传统方式
根文件系统制作为squash fs,而单独划分一个jffs2的数据分区,用来存储用户对设备的配置信息,在系统启动之后,自动挂载此数据分区到squash根文件系统预先创建的目录下,从而实现用户配置信息的存储。
实现脚本:
# 只需在系统家电启动的时候,启动脚本中mount此block即可,制作只读文件系统时,预先创建好/user/data路径,以mtdblock7为例进行挂载
mount -n -t jffs2 /dev/mtdblock7 -o rw,noatime,mode=0755 /user/data
1.2 Overlay方式
同样是划分两个分区,一个只读squash,一个可读写jffs2,使用overlay fs,将squash作为lower layer,jffs2作为upper layer进行挂载,然后通过pivot_root将/overlay转移为根文件系统使用。
实现脚本:
#!/bin/sh
echo "------------------------------------------- start preinit -----------------------------------------"
mount -a
echo -e "If you want to erase overlay data, please press [y] key in 3 seconds!"
read -t 3 input
if [ "$input" = "y" ]; then
echo -e "Remove all files in /overlay/"
rm /overlay/* -rf
fi
...
#先挂载jffs2分区
mount -n -t jffs2 /dev/mtdblock7 -o rw,noatime,mode=0755 /overlay
#将/目录作为lowerdir,/overlay作为upperdir,使用overlay方式,挂载到/mnt目录下
mount -n -t overlayfs overlayfs:/overlay -o rw,noatime,lowerdir=/,upperdir=/overlay /mnt
mount -n /proc -o noatime,--move /mnt/proc
#将根文件系统切换为/mnt目录
pivot_root /mnt /mnt/rom
mount -n /rom/dev -o noatime,--move /dev
mount -n /rom/tmp -o noatime,--move /tmp
mount -n /rom/sys -o noatime,--move /sys
mount -n /rom/overlay -o noatime,--move /overlay
...
echo "----------------------- preinit end, and then start normal init ------------------------------------"
目前,在嵌入式系统的应用中,比较常用并且稳定运行的Linux内核版本都是2.6.x版本,而Overlay是在Linux 3.18之后内核才开始支持,因此,要想在2.6.x版本使用,就需要我们手动打补丁,网上有OpenWRT对Linux 2.6.38版本的补丁,但是目前好像无法访问了,以下为在项目中使用的参考Linux 2.6.38版本的补丁思路:
- 内核空间支持overlay
- 应用空间
详细补丁文件请参考:
已经打进kernel,后续有空研究一下单独列出来
2. Overlay在Docker中的应用
Docker的存储驱动有很多种,常用的比如AUFS、DeviceMapper、BtrFS、ZFS等,当然也包括OverlayFS,本文以下部分仅描述Overlay在docker中作为存储驱动的应用。
Docker的安装及原理请参考网上其他文章,在此不再赘述。
2.1 Docker镜像和容器
镜像(image)
是一个或几个只读层的堆叠“合并”,通过overlay文件系统“合并”成一个统一文件系统,从用户角度来看,隐藏了多个只读层的细节,只存在一个统一文件系统。
容器(Container)
跟镜像一样,也是一个或多个只读层的堆叠“合并”,只不过不同的是,最上层比镜像多了一个可读写的统一文件系统层。即:容器 = 镜像 + 可读写层;
下面我们通过docker overlay存储驱动测试,来验证上面的结论。
2.2 Docker overlay存储驱动测试
在Linux(本文以CentOS7为例)下安装docker之后,docker镜像的默认安装目录是/var/lib/docker,我们安装docker之后,进入这个目录,未安装任何镜像时,先查看一下这个目录结构以方便验证,如下图:
简单介绍一下较重要或者我们测试关注的几个目录:
containers:docker的容器信息存储目录;
image:docker的镜像信息存储目录;
- image/overlay2/imagedb:镜像信息及镜像layer各个层信息;
- image/overlay2/layerdb:镜像各个layer层的信息(imagedb sha256sum);
overlay2:docker镜像内,镜像各个layer中具体的文件内容及数据;
这时,我们随便使用docker pull ubuntu:15.04
拉取一个镜像,如下:
其实从这个过程也可以看到,Pull complete一共有4个,说明这个ubuntu:15.04镜像包含了4个只读层,但是这一点我还没有仔细验证。
docker images
查看IMAGE ID开头为d1b55fd07600,再查看image/overlay2/imagedb/content/sha256/下已存在此ID的文件,如下图:
我们查看一下这个文件内容,发现是一个json文件,文件末尾部分,有一个diff_ids数组,关注一下,其实这个数组正是组成ubuntu:15.04镜像的4个layer id,从数组的第一个到第四个,分别对应image从最底层到最顶层,也就是说,"sha256:3cbe18655eb6…"是最底层。如下图红色框部分:
既然在image/overlay2/imagedb得到了各个layer的image id,那就再进入image/overlay2/layerdb/目录去对应查找,可以发现,只找到最底层的sha256:3cbe18655eb6…这个image id,其他的3个没有,那是因为docker使用了sha256sum方式去保存这些layer及image id,则sha256:3cbe18655eb6…这个image id的上一层id是echo -n "sha256:3cbe18655eb6...[diff_ids json数组最底层id] sha256:84cc[diff_ids json数组倒数第二层id]" | sha256sum-
,那么上上一层id是上一步得出的结果和diff_ids json数组倒数第三层id一起计算sha256sum得出,以此类推,如下图的方式,可以看出各个layer已经全部对应上了。
查看一下layerdb中的内容,发现除了最底层之外,其他每一层都有个parent文件,指向
同时,在每一层都有一个cache-id,cat一下得到与对应到overlay2/目录下,而overlay2目录,则是存储镜像每层实际文件与数据的地方,真正的image对应layer镜像的内容,就包含在这个目录下,里面保存的是本层与下层的diff,而在最上层,则是这些各个层的合并,这也是overlay实现的对于分层layer的“合并”,即同一文件系统部分。
接下来,我们运行这个容器,并在容器里面添加一个文件,进行验证,先通过docker run -i -t ubuntu:15.04 /bin/bash
进入容器,创建一个文件,然后退出。如下图操作:
查看容器运行状态,生成了7a0b843060f9的CONTAINER ID,在container目录下可以找到此容器ID,如下:
查看layerdb目录,还是之前的4个image layer,此时通过docker commit -m="add overlayfs test file" -a="name" 7a0b843060f9 name/ubuntu:15.04-dev
命令,将创建测试文件之后的容器,commit一下,再查看layerdb,发现已经多了一条,如下图的操作:
进行验证,确实在在diff中包含我们创建的文件,如下:
本人水平一般,能力有限,欢迎大家指正留言交流!