Union File System

什么是Union File System

Union File System,简称UnionFS,是一种为Linux、FreeBSD和NetBSD操作系统设计的,把其他文件系统联合到一个联合挂载点的文件系统服务。它使用branch把不同文件系统的文件和目录“透明地”覆盖,形成一个单一一致的文件系统。这些branch或者是read-only的,或者是read-write的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为unionfs用到了一个重要的资源管理技术,叫写时复制。

写时复制(copy-on-write,下文简称CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何修改,这时并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改时增加小部分的开销。

用一个经典的例子来解释一下。Knoppix,一个用于Linux演示、光盘教学和商业产品演示的Linux发行版,就是将一个CD-ROM或DVD和一个存在于可读写设备(比如,U盘)上的叫作knoppix.img的文件系统联合起来的系统。这样,任何对CD/DVD上文件的改动都会被应用在U盘上,而不改变原来的CD/DVD上的内容。

AUFS(目前默认使用 overlay2,aufs 已废弃,可跳过

AUFS,英文全称是Advanced Multi-Layered Unification Filesystem,曾经也叫Acronym Multi-Layered Unification Filesystem、Another Multi-Layered Unification Filesystem。AUFS完全重写了早期的UnionFS 1.x,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。AUFS的一些实现已经被纳入UnionFS 2.x版本。

Docker是如何使用AUFS的

AUFS是Docker选用的第一种存储驱动。AUFS具有快速启动容器、高效利用存储和内存的优点。直到现在,AUFS仍然是Docker支持的一种存储驱动类型。接下来,介绍一下Docker是如何利用AUFS存储image和container的。

image layer和AUFS

每一个Docker image都是由一系列read-only layer组成的。image layer的内容都存储在Docker hosts filesystem的/var/lib/docker/aufs/diff目录下。而/var/lib/docker/aufs/layers目录,则存储着image layer如何堆栈这些layer的metadata。

准备一台安装了Docker的机器。在没有拉取任何镜像、启动任何容器的情况下,执行如下命令。

ls /var/lib/docker/aufs/diff

可以发现,目录没有存储任何内容。

拉取镜像后,可以看到在docker pull中的结果显示ubuntu:15.04镜像一共有4个layer,在执行命令的结果中也有4个对应的存储文件目录。

Union File System

拉取镜像后,可以看到在docker pull中的结果显示ubuntu:15.04镜像一共有4个layer,在执行命令的结果中也有4个对应的存储文件目录。

接下来,以ubuntu:15.04镜像为基础镜像,创建一个名为changed-ubuntu的镜像。这个镜像只是在镜像的/tmp文件夹中添加一个写了“Hello world”的文件。可以使用下面的Dockerfile来实现。

vim Dockerfile

FROM ubuntu:15.04
RUN echo "Hello world" > /tmp/newfile
Union File System

然后,执行docker images查看现在的镜像,可以看到新生成的changed-ubuntu镜像。

Union File System

使用如下命令,可以清楚地查看到changed-ubuntu镜像使用了哪些image layer。

docker history changed-ubuntu

Union File System

从输出中可以看到a9d31126f50e image layer位于最上层,只有12B的大小,由如下命令创建。

/bin/sh-c echo "Hello world" >/tmp/newfile

也就是说,changed-ubuntu镜像只占用了12B的磁盘空间,这也证明了AUFS是如何高效使用磁盘空间的。而下面的四层image layer,则是共享地构成ubuntu:15.04镜像的4个image layer。“missing”标记的layer,是自Docker 1.10之后,一个镜像的image layer的image history数据都存储在一个文件中导致的,这是Docker官方认为的正常行为。

自己动手写AUFS

首先,在实验目录下创建一个aufs的文件夹,然后在aufs目录下创建一个mnt的文件夹作挂载点。接着,在aufs目录下创建一个名为container-layer的文件夹,里面有一个名为container-layer.txt的文件,文件内容为“I am container layer”。同样地,继续在aufs目录下创建4个名为image-layern的文件夹(n取值分别为1和4),里面有一个名为image-layer{n}.txt的文件,文件内容为“I am image layer${n}”。

Union File System
Union File System

要联合的文件目录都已经准备好了。接下来,把container-layer和4个名为image-layer${n}的文件夹用AUFS的方式挂载到刚刚创建的mnt目录下。在mount aufs的命令中,没有指定待挂载的5个文件夹的权限,默认的行为是,dirs指定的左边起第一个目录是read-write权限,后续的都是read-only权限。

sudo mount -t aufs -o dirs=./container-layer:./image-layer4:./image-layer3:./image-layer2:./image-layer1 none ./mnt

Union File System

还记得上一小节曾经在系统aufs目录下,查看文件读写权限的做法吗?这里依然使用如下命令来确认新mount的文件系统中每个目录的权限。(注意,si_c771f69583deeeb6应该是系统为这个mnt挂载点新创建的,而非在介绍Docker和AUFS里面提到的那个文件夹。)

cat /sys/fs/aufs/si_c771f69583deeeb6/*

Union File System

根据输出,可以清楚地看到,只有container-layer文件夹是read-write的,其余的都是read-only权限。

接下来,执行一个有意思的操作,往mnt/image-layer4.txt文件末尾添加一行文字“write to mnt's image-layer4.txt”。根据上面介绍的CoW技术,大家猜想一下会产生什么样的行为。

echo -e "write to mnt's image-layer4.txt" >>./mnt/image-layer4.txt

用cat命令去查看mnt/image-layer4.txt文件的内容,发现内容确实从“I am image layer 4”变成了如下的样子。

Union File System

此处,mnt只是一个虚拟挂载点,因此,接下来还需要继续去寻找文件修改到底在什么位置。

查看image-layer4/image-layer4.txt文件的内容,发现其并未改变。

Union File System

而在检查container-layer文件夹的时候,发现多了一个名为image-layer4.txt的文件,文件的内容如下。

Union File System

也就是说,当尝试向mnt/image-layer4.txt文件进行写操作的时候,系统首先在mnt目录下查找名为image-layer4.txt的文件,将其拷贝到read-write层的container-layer目录中,接着对container-layer目录中的image-layer4.txt文件进行写操作。

小结

我们列举了其中的几个具体实现,并且讲解了Docker是如何使用分层文件系统来实现镜像不同分层的重复利用的。最后,以AUFS为例子介绍了如何构建一个简单的分层文件系统。后面在开发自己的容器镜像的过程中就会使用这项技术。

overlay2

overlayFS是被称为联合文件系统的其中一个解决方案。在2014年,发布了第一个版本并且合并到了Linux的内核3.18版本中,此时,在docker被称为是overlay文件驱动。后来在Linux 内核4.0 版本中进行了改进,称为overlay2。(overlay存在诸多性能和不稳定的问题,不推荐使用overlay,直接使用默认的overlay2即可)

Union File System

overlayfs 通过三个目录:lower 目录、upper 目录、以及 work 目录实现,其中 lower 目录可以是多个,work 目录为工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见,最后联合挂载完成给用户呈现的统一视图称为为 merged 目录。

  • lowerdir对应底层文件系统,是能被上层文件系统upperdir所共享的只读层
  • workdir则可以理解为overlay2运作的一个工作目录,用于完成copy-on-write等操作
  • overlay2运作时(也就是容器启动时),会将lowerdir、upperdir和workdir联合挂载到merged目录,为使用者提供一个“统一视图

查看 /var/lib/docker/overlay2/容器ID目录结构,使用mount | grep overlay查看overlay的挂载情况。如图,确实此目录下的link、lower、work、diff等目录是通过挂载多个目录后,合并显示在一起的。

Union File System

overlay2 是如何存储文件的?

镜像怎么存储的?

1、为了更好的演示,使用一个纯净的环境:没有任何镜像和容器,/var/lib/docker/overlay2目录也是空的

Union File System

2、拉取一个nginx镜像,观察拉取过程:可以看到镜像一共被分为7层拉取。

Union File System

3、/var/lib/docker/overlay2/ 目录下也多了7个文件夹

4、首先来查看一下l目录,可以看到l目录是一堆软连接,把一些较短的随机串软连到镜像层的 diff 文件夹下,这样做是为了避免达到mount命令参数的长度限制

Union File System

docker image inspect nginx 查看nginx镜像的信息,每个镜像都会有一个GraphDriver.Data信息,这个信息指示了镜像是怎么存的

Union File System

6、将这7个文件夹全部展开,可以看到目录结构几乎都是一致的,需要重点关注的是diff文件夹和lower文件。
可以看到/var/lib/docker/overlay2/9baa6dd5f513a41f720ac968d33acb11398d355e143200ab14d284c20abb0946/文件夹中不存在lower文件,说明它是最底层的,等于是根镜像,即docker pull时下载的第一层。
同时,diff文件夹下的文件,正是Linux文件目录结构。说明在nginx的dockerfile中,肯定有FROM centos的操作。

Union File System
Union File System

实例说明:Dockerfile的每一个命令都可能引起了系统的变化,它的每一个变化都会记录一层diff文件。

容器怎么存储的?

1、当前环境有一个nginx镜像,/var/lib/docker/overlay2/ 目录下只有镜像层的存储目录。

Union File System

2、docker run 启动一个容器

3、查看/var/lib/docker/overlay2/ 目录,发现新增了两个目录:其中带-init的目录是只读的;没有init的容器目录才是容器的读写目录

Union File System

4、linklower文件与镜像层的功能一致,link文件内容为该容器层的短 ID,lower文件为该层的所有父层镜像的短 ID。diff目录为容器的读写层,容器内修改的文件都会在diff中出现,merged目录为分层文件联合挂载后的结果,也是容器内的工作目录。

Union File System
Union File System

5、根据 docker inspect nginx-test获取到的GraphDriver.Data数据显示,merged目录,是lowerDir各个目录合并UpperDir各个目录后的结果。

Union File System

6、当我们进入容器创建文件时,文件也会出现在这里。

Union File System

结论 overlay2将镜像层和容器层都放在单独的目录,并且有唯一 ID,每一层仅存储发生变化的文件,最终使用联合挂载技术将容器层和镜像层的所有文件统一挂载到容器中,使得容器中看到完整的系统文件。

overylay实战

lower文件系统是readonly,对merged中所有的修改都只对upper操作,记住这点很重要。下面我们在linux上创建一个overlay文件系统,用以说明overlay文件系统挂载,文件读写,文件新增和删除。

创建overlay文件系统

创建lower upper merged work目录,把lower和upper挂载到mergedwork是空目录,必须和merged的文件系统类型一样。lower目录下REDAME.txt内容为“lowerfile”,upper目录下REDAME.txt内容为“upperfile”

Union File System
Union File System

挂载: sudo mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work merged/,挂载后merged目录结构如下:

Union File System

可以看到lowerupper中的文件合并到了merged中,当lowerupper有相同路径的文件时,merged中只显示upper中的。也就是说upper会遮住lower中同名的文件(同路径下)。

overlay文件系统读写

新建文件:在upper中新建文件,lower只读

删除文件:

  1. 如果文件在upper中存在,则删除,并新建一个whiteout文件
  2. 如果文件在upper中不存在,在lower中存在,则在upper新建一个同名的whiteout文件

修改文件:

  1. 文件在upper中存在,则只会修改upper中的文件
  2. 文件在upper中不存在,在lower中存在,则从lower中复制文件到upper,再修改upper中的复制品

读取文件:

  1. upper中有该文件则读取upper的,否则读取lower中的
  2. whiteout是个字符设备文件,主次设备号为0,用来屏蔽对lower的访问。

补充

  • linux4.0以后,overlay文件系统支持多层lower挂载,挂载方式如下:
    sudo mount -t overlay overlay -o lowerdir=./dir1:./dir2:./dir3,upperdir=./upper,workdir=./work merged/
    dir1在lower的顶层,dir3在lower的底层,debian上docker就是采用这种方式
  • overlay提供了对只读文件系统的读写功能,适合用在需要维持一个只读镜像,又需要提供读写功能的系统中,比如openwrt和docker,下面我们介绍docker中overlay的应用。

docker

docker的基础镜像其实就是个readonly的根文件系统,从基础镜像构建的镜像其实都只是把修改部分和基础镜像合并重新打包,我们从ubuntu镜像构建一个具有golang环境的镜像,用来说明overlay在docker中的应用。

Dockerfile

## golang dockerfile
## version 1.0
## 以ubuntu为基础镜像构建新镜像
FROM ubuntu:latest

## 维护者
MAINTAINER "howell"

## 新增用户go
RUN ["useradd", "-m", "-s", "/bin/bash", "go"]

## 指定工作目录
WORKDIR "/home/go"

## 把golang源码包打包到镜像,并解压到/opt路径
ADD go1.16.7.linux-amd64.tar.gz /opt

## 设置GOROOT/GOPATH/GOPROXY环境变量,并把对应的bin目录加到系统PATH环境变量
ENV GOROOT="/opt/go" GOPATH="/home/go/golang" GOPROXY="https://goproxy.cn,direct" GO111MODULE="on"
ENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH

## 以下命令以用户go执行
USER go

## docker run执行的命令
ENTRYPOINT ["/bin/bash"]

## 挂载/home/go到一个匿名路径,后续能看到具体是哪个路径,可以通过-v覆盖匿名路径
VOLUME ["/home/go"]

## 添加一些元数据
LABEL author="howell" email="1234567@163.com"

构建镜像:docker build -t ubuntu:golang .

Union File System

启动容器:docker run -itd --name golang -u go -v /var/golang:/home/go ubuntu:golang

查看容器:docker inspect golang

查看GraphDriver

Union File System

从上面可以看到docker采用的是overlay2文件系统,LowerDir有多层。

/var/lib/docker/overlay2/799c6450ffe088538cc9d2c5740a2553cc1df9a8761366f9b5e86f2c68461873-init/diff

/var/lib/docker/overlay2/pkyqxzsh2n2e6niseg3k1wriq/diff

/var/lib/docker/overlay2/q7uty725bkgyhgwd72albu1db/diff

/var/lib/docker/overlay2/v8x7z5f3wcf65zgendaxvot4z/diff

/var/lib/docker/overlay2/fdd69c843ec4885c9fc2358e3c1c1372bbeeed15d9808abeb15ff5b3ceddff91/diff

查看每一层都有什么:

Union File System

为什么会有这么多层,其实很好理解,查看Dockerfile中写了哪些规则:

  • 基于ubuntu基础镜像,所以最底层必然是ubuntu的镜像文件,待会可以查证
  • 创建一个用户,必然会修改/etc,新用户在/home下创建了工作目录,导致LowerDir增加一层
  • 上传了golang源码包并解压到/opt,并然导致/opt修改,导致LowerDir增加一层
  • docker把/etc自动添加一个init层,docker官方认为/etc下的修改一般会影响kernel,从而影响所有使用该镜像的用户,其他用户可能不希望有这些修改。
  • UpperDir初始化是empty的,用户有修改就会在UpperDir中创建,比如进入容器添加一个文件,UpperDir也会更新

拉取一个 ubuntu 镜像,现在我们核对下最底层镜像是否是ubuntu的镜像文件:

docker image inspect ubuntu

Union File System

ubuntu是基础镜像,没有LowerDir, UpperDir(/var/lib/docker/overlay2/fdd69c843ec4885c9fc2358e3c1c1372bbeeed15d9808abeb15ff5b3ceddff91/diff)就是新建ubuntu:golang镜像LowerDir的最底层文件系统

发布者:LJH,转发请注明出处:https://www.ljh.cool/39389.html

(0)
上一篇 2024年1月3日 上午1:40
下一篇 2024年1月6日 下午8:55

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注