1. 前言
(Union filesystem)联合文件系统允许我们把多个文件系统逻辑上合并成一个文件系统,组成Union filesystem的文件系统不必相同(它们可以是ext2/3/4,vfat,ntfs,jffs...)。overlay是联合文件系统的一种(aufs...),overlay文件系统构建于其他文件系统之上,overlay其实更像是个挂载系统(mount system),功能是把不同的文件系统挂载到统一的路径。
2. overlay文件系统
overlay是个分层的文件系统,底层文件系统通常叫lower
,顶层文件系统系统通常叫upper
,两者通常合并挂载到merged
目录,最终用户看到的就是merged
中的文件。
lower
文件系统是readonly,对merged
中所有的修改都只对upper
操作,记住这点很重要。下面我们在linux上创建一个overlay文件系统,用以说明overlay文件系统挂载,文件读写,文件新增和删除。
3. 创建overlay文件系统
创建lower upper merged work
目录,把lower和upper
挂载到merged
,work
是空目录,必须和merged
的文件系统类型一样。
lower/
├── books
│ ├── c
│ └── java
├── computers
│ ├── huawei
│ ├── lenove
│ └── macpro
├── README.txt
└── sports
├── fishing
├── football
└── running
upper/
├── books
│ ├── c
│ └── golang
├── cities
│ ├── beijing
│ ├── shanghai
│ └── shenzhen
├── README.txt
└── sports
├── basketball
├── football
└── running
挂载:
sudo mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work merged/
挂载后merged
目录结构如下:
merged/
├── books
│ ├── c
│ ├── golang
│ └── java
├── cities
│ ├── beijing
│ ├── shanghai
│ └── shenzhen
├── computers
│ ├── huawei
│ ├── lenove
│ └── macpro
├── README.txt
└── sports
├── basketball
├── fishing
├── football
└── running
可以看到lower
和upper
中的文件合并到了merged
中,当lower
和upper
有相同路径的文件时,merged
中只显示upper
中的。也就是说upper
会遮住lower
中同名的文件(同路径下)。
4. overlay文件系统读写
dir1在lower
的顶层,dir3在lower
的底层,debian上docker就是采用这种方式。
overlay提供了对只读文件系统的读写功能,适合用在需要维持一个只读镜像,又需要提供读写功能的系统中,比如openwrt和docker,下面我们介绍docker中overlay的应用。
- 新建文件
在upper
中新建文件,lower
只读 - 删除文件
- 如果文件在
upper
中存在,则删除,并新建一个whiteout
文件 - 如果文件在
upper
中不存在,在lower
中存在,则在upper
新建一个同名的whiteout
文件
- 如果文件在
- 修改文件
- 文件在
upper
中存在,则只会修改upper
中的文件 - 文件在
upper
中不存在,在lower
中存在,则从lower
中复制文件到upper
,再修改upper
中的复制品
- 文件在
- 读取文件
upper
中有该文件则读取upper
的,否则读取lower
中的whiteout
是个字符设备文件,主次设备号为0,用来屏蔽对lower
的访问。 - 补充
linux4.0以后,overlay文件系统支持多层lower
挂载,挂载方式如下:sudo mount -t overlay overlay -o lowerdir=./dir1:./dir2:./dir3,upperdir=./upper,workdir=./work merged/
5. docker
docker的基础镜像其实就是个readonly的根文件系统,从基础镜像构建的镜像其实都只是把修改部分和基础镜像合并重新打包,我们从ubuntu镜像构建一个具有golang环境的镜像,用来说明overlay在docker中的应用。
Dockerfile
如下:
## golang dockerfile
## version 1.0
## 以ubuntu为基础镜像构建新镜像
FROM ubuntu:latest
## 维护者
MAINTAINER "joker"
## 新增用户go
RUN ["useradd", "-m", "-s", "/bin/bash", "go"]
## 指定工作目录
WORKDIR "/home/go"
## 把golang源码包打包到镜像,并解压到/opt路径
ADD go1.16.6.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="joker" email="cyp_fly@126.com"
构建镜像
$$ docker build -t ubuntu:golang .
Sending build context to Docker daemon 129.1MB
Step 1/11 : FROM ubuntu:latest
---> ba6acccedd29
Step 2/11 : MAINTAINER "joker"
---> Running in 1906ecf1e073
Removing intermediate container 1906ecf1e073
---> b10d3bffdec9
Step 3/11 : RUN ["useradd", "-m", "-s", "/bin/bash", "go"]
---> Running in f5fc6f4d31c2
Removing intermediate container f5fc6f4d31c2
---> 8985b91b0827
Step 4/11 : WORKDIR "/home/go"
---> Running in f4f4b66d61fe
Removing intermediate container f4f4b66d61fe
---> a6e334f8073f
Step 5/11 : ADD go1.16.6.linux-amd64.tar.gz /opt
---> 142a1460d16c
Step 6/11 : ENV GOROOT="/opt/go" GOPATH="/home/go/golang" GOPROXY="https://goproxy.cn,direct" GO111MODULE="on"
---> Running in a0b88f65dc77
Removing intermediate container a0b88f65dc77
---> 331d4c34ae5f
Step 7/11 : ENV PATH=$GOROOT/bin:$GOPATH/bin:$PATH
---> Running in 3909af77862a
Removing intermediate container 3909af77862a
---> 55035165f7c3
Step 8/11 : USER go
---> Running in 0840158531d4
Removing intermediate container 0840158531d4
---> 68459adca191
Step 9/11 : ENTRYPOINT ["/bin/bash"]
---> Running in fe9897b8d155
Removing intermediate container fe9897b8d155
---> d17e20160cfd
Step 10/11 : VOLUME ["/home/go"]
---> Running in dce2482115c0
Removing intermediate container dce2482115c0
---> 7252f0757d57
Step 11/11 : LABEL author="joker" email="cyp_fly@126.com"
---> Running in c0a3975854ba
Removing intermediate container c0a3975854ba
---> d4e4da229727
Successfully built d4e4da229727
Successfully tagged ubuntu:golang
启动容器
$$ docker run -itd --name golang -u go -v /var/golang:/home/go ubuntu:golang
查看容器
$$ docker inspect golang
查看GraphDriver
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/144b8379bf7b144ab9ec4d8de712b8ec56d87fd0f5d74b2c938c2b215e1b9ebd-init/diff:/var/li
b / docker / overlay2 / 1 d65e86e54373a5b01afe28d0878a953fa2e9eb7cd14552a17d4a8628b476978 / diff: /var/lib / docker / overlay2 / b56e1dd54abdf8300b2f972
6e3 e92d3b32b78c8bf5b5cd807e27be78c671af40 / diff: /var/lib / docker / overlay2 / 44 c045232b8f3510d28965fbebcaad458263855785cc38e1a784a6731df7433d /
diff ",
"MergedDir": "/var/lib/docker/overlay2/144b8379bf7b144ab9ec4d8de712b8ec56d87fd0f5d74b2c938c2b215e1b9ebd/merged",
"UpperDir": "/var/lib/docker/overlay2/144b8379bf7b144ab9ec4d8de712b8ec56d87fd0f5d74b2c938c2b215e1b9ebd/diff",
"WorkDir": "/var/lib/docker/overlay2/144b8379bf7b144ab9ec4d8de712b8ec56d87fd0f5d74b2c938c2b215e1b9ebd/work"
},
"Name": "overlay2"
},
从上面可以看到docker采用的是overlay2文件系统,LowerDir有多层。
查看每一层都有什么:
/var/lib/docker/overlay2/144b8379bf7b144ab9ec4d8de712b8ec56d87fd0f5d74b2c938c2b215e1b9ebd-init/diff
dev etc
/var/lib/docker/overlay2/1d65e86e54373a5b01afe28d0878a953fa2e9eb7cd14552a17d4a8628b476978/diff
opt
/var/lib/docker/overlay2/b56e1dd54abdf8300b2f9726e3e92d3b32b78c8bf5b5cd807e27be78c671af40/diff
etc home var
/var/lib/docker/overlay2/44c045232b8f3510d28965fbebcaad458263855785cc38e1a784a6731df7433d/diff
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
为什么会有这么多层,其实很好理解,查看Dockerfile中写了哪些规则:
- 基于ubuntu基础镜像,所以最底层必然是ubuntu的镜像文件,待会可以查证
- 创建一个用户,必然会修改/etc,新用户在/home下创建了工作目录,导致LowerDir增加一层
- 上传了golang源码包并解压到/opt,并然导致/opt修改,导致LowerDir增加一层
- docker把/etc自动添加一个init层,docker官方认为/etc下的修改一般会影响kernel,从而影响所有使用该镜像的用户,其他用户可能不希望有这些修改。
- UpperDir初始化是empty的,用户有修改就会在UpperDir中创建
现在我们核对下最地城镜像是否是ubuntu的镜像文件:
$$ docker inspect ubuntu
"GraphDriver": {
"Data": {
"MergedDir": "/var/lib/docker/overlay2/44c045232b8f3510d28965fbebcaad458263855785cc38e1a784a6731df7433d/merged",
"UpperDir": "/var/lib/docker/overlay2/44c045232b8f3510d28965fbebcaad458263855785cc38e1a784a6731df7433d/diff",
"WorkDir": "/var/lib/docker/overlay2/44c045232b8f3510d28965fbebcaad458263855785cc38e1a784a6731df7433d/work"
},
"Name": "overlay2"
},
ubuntu是基础镜像,没有LowerDir, UpperDir就是我新建ubuntu:golang镜像LowerDir的最底层文件系统。