docker文件系统&数据卷
docker
文件系统UnionFS
和容器数据卷
docker镜像
之前的文章中,我们一直在使用docker
命令,第一步就是通过docker pull
,那么问题来了,到底什么是镜像呢?
镜像是什么
镜像是一种轻量级、可执行
的独立软件包,用来打包软件。运行环境开发的软件,他包含运行某个软件所需要的的所有内容,包括代码、数据库、环境变量以及配置文件等
docker
特点:
-
轻量级 【
docker
的镜像相比之下都比较小】 -
可执行【拿到
docker
的镜像只需要简单运行命令即可】 -
弱安全:【
docker
能够对多种OS
资源进行隔离,但是它本质上依托于内核,因此所有的内核漏洞都是Docker
的致命伤】
镜像加载原理
UnionFs
【联合文件系统】
UnionFs
(联合文件系统):Union
文件系统(UnionFs
)是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,因此相同的文件层不需要再次拉取,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。Union文件系统是 Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
理解:
- 是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改,作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(
unite directories into a single virtual filesystem
)。 UnionFS
是Docker
镜像的基础,镜像可以通过分层来继承,基于基础镜像(没有父镜像的镜像),可以制作各种具体的应用镜像- 特性一次同时加载多个文件系统,但从外面看起来只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
例子:比如说 mysql
和 tomcat
都需要 centos
环境,先安装了 mysql
,就有了 centos
的环境,那再安装 tomcat
,就可以共用这一层 centos
,不需要再下载 centos
。
Docker镜像加载原理
docker
的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS
。
boots(boot file system)
主要包含 bootloader
和 Kernel, bootloader
主要是引导加 kernel
, Linux
刚启动时会加bootfs
文件系统,在 Docker
镜像的最底层是 boots
。这一层与我们典型的Linux/Unix
系统是一样的,包含boot
加載器和内核。当boot
加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs
转交给内核,此时系统也会卸载bootfs
。
rootfs(root file system)
在 bootfs
之上。包含的就是典型 Linux
系统中的/dev,/proc,/bin,/etc
等标准目录和文件。 rootfs
就是各种不同的操作系统发行版,比如 Ubuntu, Centos
等等。
理解:
Docker
的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS
。BootFS(Boot file system)
主要包含bootloader
和kernel
,bootloader
主要是引导加载kernel
,Linux
刚启动时会加载BootFS
文件系统,在Docker
镜像的最底层是BootFS
。这一层与我们典型的Linux/Unix
系统是一样的,包含boot
加载器 和 内核。当boot
加载完成之后整个内核就都在内存中了,此时内存的使用权已由BootFS
转交给内核,此时系统也会卸载BootFS
。RootFS(Root File System)
,在BootFS
之上,包含的就是典型Linux
系统中的/dev,/proc,/bin,/etc
等标准目录和文件。RootFS
就是各种不同的操作系统发行版,比如Ubuntu CentOS
等等。
我们正常安装一个CentOS
都是大几个G,但是在docker
中我们使用镜像拉取一下ContenOs
仅仅有200多MB
这就符合docker
的轻小的特点,对于精简的OS,rootfs
可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接使用Host
的kernel
,自己只需要提供rootfs
就可以了。由此可见对于不用的Linux
发行版,boots
会有差别,因此不同的发型可以公用bootfs
【VM虚拟机是分钟级别,容器就是秒级的】
镜像分成
当我们使用docker pull 容器名
命令拉取一个新的镜像时,我们会发现输出的日志,是一层一层在下载的,每一层都会有自己的唯一ID
我们再次拉取一个版本的nginx,进行对比
对比发现,文件的第一层是已经存在的并没有再次拉取下载。
那么问题来了?docker
为什么要采用这用分层的结构呢?(换句话说,这种分层思想做法有什么优点?)
- 资源共享,当我们使用
docekr
拉取nginx
的镜像时,由于nginx
版本不同,由于版本不同,看能只有部分层中的文件有变化,那么我们就可以只拉起有变化的层,公用部分层级文件,达到资源共享 - 充分利用系统存储资源
docker image inspect
查看镜像详情
docker imgae inspect 镜像名:tag
docker 联合文件系统UnionFS
上面我们简单知道了什么是分层文件系统,docker
镜像是由分层系统构成的。下面有必要深刻认识一下docker
的联合文件系统UnionFS
docker支持的联合文件系统有很多种,不过原理都一样,包括:AUFS、overlay、overlay2、DeviceMapper、VSF等
Linux
中各发行版实现的 UnionFS
各不相同,所以docker
在不同 linux
发行版中使用的也不同。通过docker info
命令可以查看当前系统所使用哪种 UnionFS
,常见的几种发行版使用如下:
CentOS, Storage Driver: overlay2、overlay
debain, Storage Driver: aufs
RedHat, Storage Driver: devicemapper
docker info
分层文件系统剖析
我们常用的就是CentOS
发行版的overlay2
,我们就依overlay2
分析:
根据上图我们可以大致分析出,联合文件系统分为三层:lowerdir
、upperdir
、merged
docker
环境中,我们通过 docker inspect
容器ID 就可以看到每一层所在的实际文件目录
docker inspect 容器ID
lowerdir层
-
在
docker
镜像中这一层称为init
,构成容器文件系统的文件路径集合,通过 冒号 : 分割,越是后面的表示越底层。 由于组成LowerDir
的镜像层是不可修改的,因此他们可以被一个宿主机上所有容器共用的一层。 -
lowerdir
是只读的镜像层(image layer
),其中就包含bootfs/rootfs
层,bootfs(boot file system)
主要包含bootloader
和kernel
,bootloader
主要是引导加载kernel
,当boot
成功kernel
被加载到内存中,bootfs
就被umount
了,rootfs(root file system)
包含的就是典型Linux
系统中的/dev、/proc、/bin、/etc
等标准目录。 -
lowerdir
是可以分很多层的,除了bootfs/rootfs
层以外,还可以通过Dockerfile
建立很多image
层,构建过程如下:
Dockerfile
中每一个指令都会生成一个新的image
层,如上图所示。
当FROM
时就已经生成了bootfs/rootfs
层,也就是kernel
和base
层。
upperdir层
-
upperdir
层是lowerdir
的上一层,只有这一层可读可写的,其实就是Container
层,在启动一个容器的时候会在最后的image
层的上一层自动创建,所有对容器数据的更改都会发生在这一层。 -
docker
将一个镜像作为lowerdir
层,并且建立一个upperdir
层,upperdir
层也称为容器读写层(联想我们在docker
容器里编辑或者新增文件)。最后将这两层挂载到一个特定的路径下,最终形成docker
容器的rootfs
。 -
由于联合文件系统采用了
copy-on-wirte
的方式来读写文件:当对一个文件进行编辑的时候,会先从上至下逐层查找,如果找到了就copy
到upper
层,然后进行在upperdir
层对文件进行修改。
merged层
-
merged
是容器层和镜像层的联合视图 -
merged
层就是联合挂载层,也就是给用户暴露的统一视觉,将image
层和container
层结合,就如最上边的图中描述一致,同一文件,在此层会展示离它最近的层级里的文件内容,或者可以理解为,只要container
层中有此文件,便展示container
层中的文件内容,若container
层中没有,则展示image层中的。 -
在
merge
路径下 编辑/写入一个文件,其首先会从upper
层中寻找看是否存在目标文件,如果存在,则直接修改,如果不存在,则跳入到lower
层查找,如果找到了,会把lower
层的目标文件拷本一份到upper
层,然后对拷贝到upper
层的目标文件进行编辑,如果在lower
层也查不到,则会将编辑的文件保存一份到upper
层。这样,最后用户在merge
层看到的视图就是 编辑/写入 的文件。
联合挂载系统的工作原理
读文件
如果文件在upperdir
(容器)层,直接读取文件;
如果文件不在upperdir
(容器)层,则从镜像层(lowerdir
)读取;
写文件
首次写入:如果upperdir
中不存在,overlay
和overlay2
执行copy_up
操作,把文件从lowdir
拷贝到upperdir
中,由于overlayfs
是文件级别的(即使只有很少的一点修改,也会产生copy_up
的动作),后续对同一文件的再次写入操作将对已经复制到容器层的文件副本进行修改,这也就是尝尝说的写时复制(copy-on-write
)。
删除文件
删除文件或目录:当文件被删除时,在容器层(upperdir
)创建whiteout文件,镜像层(lowerdir
)的文件是不会被删除的,因为它们是只读的,但without文件会阻止它们显示,当目录被删除时,在容器层(upperdir
)一个不透明的目录,这个和上边的whiteout的原理一样,组织用户继续访问,image
层不会发生改变
注意事项
copy_up
操作只发生在文件首次写入,以后都是只修改副本overlayfs
只适用两层目录,,相比于比AUFS
,查找搜索都更快- 容器层的文件删除只是一个“障眼法”,是靠
whiteout
文件将其遮挡,image
层并没有删除,这也就是为什么使用docker commit
提交保存的镜像会越来越大,无论在容器层怎么删除数据,image
层都不会改变
容器整体构成图
docker容器数据卷
Docker
中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker
中称为数据卷(Data Volume
)
镜像产生问题
我们将应用和环境打包成一个镜像,那么数据呢,如何进行数据的持久化?
问题:容器的数据持久化和数据同步共享问题
数据卷是什么
卷就是目录或文件,存在于一个或多个容器中,由Docker挂载到容器,但卷不属于联合文件系统(Union FileSystem),因此能够绕过联合文件系统提供一些用于持续存储或共享数据的特性:。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
如何使用数据卷
-
创建数据卷,只要在
docker run
命令后面跟上-v
参数即可创建一个数据卷,当然也可以跟多个-v参数来创建多个数据卷,当创建好带有数据卷的容器后,就可以在其他容器中通过--volumes-froms
参数来挂载该数据卷了,而不管该容器是否运行。也可以在Dockerfile中通过VOLUME指令来增加一个或者多个数据卷 -
数据卷的备份:不能使用
docker export、save、cp
等命令来备份数据卷的内容,因为数据卷是存在于镜像之外的。备份方法: 创建一个新容器,挂载数据卷容器,同时挂载一个本地目录,然后把远程数据卷容器的数据卷通过备份命令备份到映射的本地目录里面。如下:
docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data
Docker Volume数据卷可以实现
绕过“拷贝写”系统,以达到本地磁盘IO的性能,(比如运行一个容器,在容器中对数据卷修改内容,会直接改变宿主机上的数据卷中的内容,所以是本地磁盘IO的性能,而不是先在容器中写一份,最后还要将容器中的修改的内容拷贝出来进行同步。)
绕过“拷贝写”系统,有些文件不需要在docker commit打包进镜像文件。
数据卷可以在容器间共享和重用数据、在宿主和容器间共享数据
数据卷数据改变是直接修改的
数据卷是持续性的,直到没有容器使用它们。即便是初始的数据卷容器或中间层的数据卷容器删除了,只要还有其他的容器使用数据卷,那么里面的数据都不会丢失。
小结
- 数据卷可在容器之间共享或重用数据
- 卷中的更改可以直接生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
docker环境安装mysql
通过docker容器的数据卷,解决mysql数据持久化问题
- 拉取
mysql 5.7
镜像
docker pull mysql:5.7
- 创建myslq需要挂载的配置文件和目录
mkdir -p /data/mysql/conf && mkdir -p /data/mysql/data && mkdir -p /data/mysql/log
#进入conf目录创建my.cnf文件
[mysqld]
user=mysql
character-set-server=utf8
default_authentication_plugin=mysql_native_password
secure_file_priv=/var/lib/mysql
expire_logs_days=7
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
max_connections=1000
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
- 运行mysql ,这里需要注意启动myslq是需要配置密码的
docker run --name mysql01 -p 3306:3306 -v /data/mysql/conf/my.cnf:/etc/mysql/my.cnf -v /data/mysql/data:/var/lib/mysql -v /data/mysql/log:/var/log/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
#启动我们得
-d 后台运行
-p 端口映射
-v 卷挂载【宿主机文件/目录:容器里对应的文件/目录】
# 这里需要注意 宿主机上的文件/目录是要提前存在的
-e 环境配置
-- name 容器名字
#数据卷权限:
#挂载的数据默认为可读写权限。
#但也可以根据自己的需求,将容器里挂载共享的数据设置为只读,这样数据修改就只能在宿主机上操作
启动成功远程测试连接
测试连接正常,查看宿主机上挂载的Mysql数据
测试创建的test数据库以及被持久化
停止并且删除mysql容器
再次查看宿主机中mysql持久化的数据
数据依然存在
具名和匿名挂载
匿名挂载
匿名挂载就是在指定数据卷的时候,不指定容器路径对应的主机路径,这样对应映射的主机路径就是默认的路径/var/lib/docker/volumes/中自动生成一个随机命名的文件夹。
- 运行并匿名挂载
nginx
容器
docker run -d -P --name nginx01 -v /etc/nginx nginx
- 查看所有的数据卷
volume
的情况,VOLUME NAME
这里的值是真实存在的目录
具名挂载
具名挂载,就是指定文件夹名称,区别于指定路径挂载,这里的指定文件夹名称是在Docker指定的默认数据卷路径下的。通过docker volume ls
命令可以查看当前数据卷的目录情况。
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
4ceaff19e5275dcd3014a8e7a8af618f7f7ce0da18d605c7c41a8653e78bf912
# 挂载的时候,指定一个名称
root@iZwz99sm8v95sckz8bd2c4Z ~]# docker volume ls
DRIVER VOLUME NAME
local 0cd45ab893fc13971219ac5127f9c0b02491635d76d94183b0261953bdb52d26
local 668a94251e562612880a2fdb03944d67d1acdbbdae6ef7c94bee8685644f2956
local e605f3dc4bf11ab693972592b55fb6911e5bf2083425fd58869c5f574998a09a
local juming-nginx
查看指定的数据卷信息的命令:docker volume inspect数据卷名称
docker volume inspect
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker volume inspect juming-nginx
[
{
"CreatedAt": "2020-12-29T22:40:25+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
"Name": "juming-nginx",
"Options": null,
"Scope": "local"
}
]
可以看到主机数据卷挂载在/var/lib/docker/volumes/juming-nginx/_data上
Docker所有的数据卷默认在/var/lib/docker/volumes/ 目录下
[root@iZwz99sm8v95sckz8bd2c4Z volumes]# ls
0cd45ab893fc13971219ac5127f9c0b02491635d76d94183b0261953bdb52d26 backingFsBlockDev juming-nginx
668a94251e562612880a2fdb03944d67d1acdbbdae6ef7c94bee8685644f2956 e605f3dc4bf11ab693972592b55fb6911e5bf2083425fd58869c5f574998a09a metadata.db
匿名挂载,具名挂载,指定路径挂载的命令区别如下:
-v 容器内路径 #匿名挂载
-v 卷名:容器内路径 #具名挂载
-v /宿主机路径:容器内路径 #指定路径挂载
指定数据卷映射的相关参数:
ro —— readonly 只读。设置了只读则只能操作宿主机的路径,不能操作容器中的对应路径。
rw ----- readwrite 可读可写
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
[root@iZwz99sm8v95sckz8bd2c4Z ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
容器数据卷
容器数据卷是指建立数据卷,来同步容器之间的数据,实现容器间的数据同步【简单来说就是,容器挂载同一个目录,到达数据持久化共享据】
volumes-from容器间传递共享
docker run -it --name dc02 --volunes-from dc01 leyton/centos
相关Docker文章推荐
参考文章
Image的本地存储结构【https://segmentfault.com/a/1190000017579626】
深入理解 Docker 容器镜像分层的原理【https://iswbm.com/371.html】
docker文件系统分层存储原理【https://www.cnblogs.com/v-fan/p/14453223.html】
挂载Linux系统外的文件【http://c.biancheng.net/view/885.html】