Docker底层存储机制
对于Docker来讲,它作为容器运行的底层引擎,在组织和运行其容器时,每个容器内运行一个程序及子程序,容器启动时依赖于底层可能不止一层的只读镜像联合挂载启动而成。底层能够存储此类分层构建并联合挂载镜像的文件系统包含AUFS、Overlayfs2、devmapper文件系统。最后一定要在最上层构建一个可写层。对于此可写层来说,所有在容器中所执行的写操作(对数据的修改、对内容的修改),都是保存在最上层的可写层。对下层内容的增删改操作我们需要使用“写时复制”(COW)机制来实现。
COW(写时复制)
如果一个文件在最底层是存在的,在任意层中被标记为删除,那么用户最上层就看不到这个文件了。用户能看到的只能是没被标记为删除的或者被标记为删除而用户在最上层又自己创建了同名的文件。
这种方式我们去修改操作的时候,效率会非常的低,尤其是那些对I/O要求较高的应用比如redis、mysql。要想绕过这种使用的限制,我们可以通过使用存储卷的机制来实现
存储卷(volume)介绍
所谓存储卷可以简单想象成在特权级的名称空间(宿主机)当中找一个本地文件系统之上存在某一个目录,把这个目录直接与容器内部的文件系统之上的某一个目录建立绑定关系,随后,容器内的进程向这个目录中写数据时,是直接被写在宿主机的目录上的,这和使用mount --bind命令的功能非常相似,这样就使得我们容器内部进程在实现数据保存时,能绕过容器内部文件系统的限制,从而与宿主机的文件系统建立了关联关系。
存储卷的好处
存储卷带来的好处是当容器关闭甚至是删除时。我们都不用 担心数据丢失了,只要不删除绑定的在宿主机上的目录(存储卷)就可以。随后再次重建这个容器时,我们能让它关联到同一个存储卷上,就可以使用相同的数据。因此就能够数据持久脱离容器的生命周期而持久。
docker默认的存储卷是在本地的宿主机的文件系统之上,如果容器要在多个docker host之间迁移(使用docker 集群),我们还可以添加共享存储,如NFS文件系统使得容器内的有状态应用可以将迁移变的容易
volume的种类
Docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但在宿主机上的位置有所不同
- Bind mount volume:在宿主机和容器内的路径需要人工分别指定一个特定路径,两个已知路径建立绑定关系。
- 语法:
docker run --name test1 -it -v HOSTDIR:VOLUMEDIR nginx:latest
- Docker managed volume:只需要在容器内指定容器内的挂载点是哪里,而被绑定的是宿主机上的哪个路径下的目录由Docker的daemon自行创建一个空目录或者使用一个已存在的目录与存储卷建立绑定关系。
- 语法:
docker run --name test1 -it -v /data nginx:latest
注意:这种方式在第一次启动容器时非常方便,他会自动为容器在宿主机上的一个路径下创建volumen,但是在该容器删除且重新启动时,它有可能会重新生成一个新的volume。也就是删除容器后,新容器不会自动承载旧容器后数据
容器卷的使用
创建 Docker managed volume
- 将容器的/data目录与物理机挂载
[root@localhost ~]# docker run --name test1 -it -v /data nginx
/ # ls
bin dev home root tmp var
data etc proc sys usr
- 验证是否挂载成功
[root@localhost overlay2]# docker inspect test1
......
"Mounts": [
{
"Type": "volume",
"Name": "9c83d1e4b9adf4d8797eb926a1f2985115530b69abcae728c2f12d2ad0f72ab9",
"Source": "/var/lib/docker/volumes/9c83d1e4b9adf4d8797eb926a1f2985115530b69abcae728c2f12d2ad0f72ab9/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
## 物理机进入此目录,并创建文件
[root@localhost overlay2]# cd /var/lib/docker/volumes/9c83d1e4b9adf4d8797eb926a1f2985115530b69abcae728c2f12d2ad0f72ab9/_data/
[root@localhost _data]# ls
[root@localhost _data]# echo 'print("hello,world")' > test.txt
[root@localhost _data]# ls
test.txt
- 容器内验证此文件
/data # ls
test.txt
/data # cat test.txt
print("hello,world")
- 删除此容器并查看存储内容
## 容器已删除
[root@localhost ~]# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
## 查看不到此前保存的内容
root@localhost _data]# ls
创建Bind mount volume
- 创建test1的容器,挂载点为data,存储卷为物理机的/contain_share/busybox1
[root@localhost ~]# docker run -it --rm --name test1 -v /contain_share/busybox1:/data busybox
/ # ls
bin data dev etc home proc root sys tmp usr var
/ # cd data
/data # ls
/data #
[root@localhost busybox1]# docker inspect test1
......
"Mounts": [
{
"Type": "bind",
"Source": "/contain_share/busybox1",
"Destination": "/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
注意:如果-v选项后的目录不存在,docker会自动创建目录,无需手动创建
- 进入存储卷,并创建文件
[root@localhost /]# cd contain_share/busybox1/
[root@localhost busybox1]# echo 'hello world' > test.txt
- 容器内查看创建的文件
/data # cat test.txt
hello world
- 删除此容器,并查看存储卷的数据
[root@localhost ~]# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
## 主机查看存储卷,发现里面的内容仍然存在
[root@localhost busybox1]# cat test.txt
hello world
- 新创建容器test2,并挂载到上述存储卷,从而延长数据的生命周期
[root@localhost ~]# docker run -it --rm --name test2 -v /contain_share/busybox1:/data busybox
/ # cd data/
/data # cat test.txt
hello world
数据卷容器
通过将两个容器使用同一个宿主机的目录,可以实现容器间的数据共享,数据卷容器其实就是一个普通的容器,专门用它提供数据卷供其他容器挂载使用。
- 语法:
docker run -d --name test --volumes-from dbdata IMAGESNAME
dbdata为创建的数据卷容器,其提供数据,供其他容器挂载
创建使用数据卷容器
- 选择挂载点,创建数据卷容器,挂载点为/sharedata这个目录
[root@localhost ~]# docker run -it --name testdata -v /sharedata busybox
/ # ls
bin etc proc sharedata tmp var
dev home root sys usr
- 创建test1容器,使其与数据卷容器同步
[root@localhost contain_share]# docker run -it --name test1 --rm --volumes-from testdata busybox
## 查看发现有sharedata这个目录
/ # ls
bin etc proc sharedata tmp var
dev home root sys usr
- 在test1容器中新建内容,在testdata中查看
/ # cd sharedata/
/sharedata # echo '123456' > test.txt
/sharedata # hostname
96cef4f40866
## 在testdata中查看
/ # cd sharedata/
/sharedata # cat test.txt
123456
/sharedata # hostname
a2ef010e1d54
利用数据卷容器迁移数据
- 现数据卷容器内有 test.txt文件,需要将其备份到物理机内
## 创建一个容器,专门用来提取数据卷容器内的共享数据,将其打包到物理机指定目录内
docker run --name back_up --volumes-from testdata -v /root:/backup busybox tar -cvf /backup/210109bak.tar /sharedata
tar: removing leading '/' from member names
sharedata/
sharedata/test.txt
## 本机/root目录中已存在备份文件
[root@localhost ~]# ls
210109bak.tar anaconda-ks.cfg
[root@localhost ~]# tar -tf 210109bak.tar
sharedata/
sharedata/test.txt
- 模拟数据卷容器中的数据丢失或误删除
## 先删除数据卷容器中的数据
[root@localhost ~]# docker container start testdata
testdata
[root@localhost ~]# docker container exec -it testdata /bin/sh
/ # ls
bin etc proc sharedata tmp var
dev home root sys usr
/ # cd sharedata/
/sharedata # ls
test.txt
/sharedata # rm -rf test.txt
- 恢复数据
## 再次创建一个容器,数据同步与数据卷容器testdata,并设置本地挂载点/root,然后将此前备份好的tar包解压即可。
[root@localhost ~]# docker run --name huifu --volumes-from testdata -v /root:/back_up busybox tar -xf /back_up/210109bak.tar
## 数据容器内查看
/sharedata # ls
test.txt
/sharedata # cat test.txt
123456