一、介绍
如果把数据存在容器内,可能会出现如下两个问题:
1.当容器不再运行时,我们无法使用数据,并且容器被删除时,数据并不会被保存。
2.数据保存在容器中的可写层中,我们无法轻松的将数据移动到其他地方。
针对上面的问题,Docker提供了三种解决方法:
volumes, 卷存储在 Docker 管理的主机文件系统的一部分中(/var/lib/docker/volumes/) 中。完全由 Docker 管理
bind mounts, 绑定挂载,可以将主机上的文件或目录挂载到容器中
tmpfs, 仅存储在主机系统的内存中,而不会写入主机的文件系统
二、各种解决方法
1.Volume是由Docker进行管理的,可以使用Docker CLI来管理卷。
①列出所有的卷的列表
[root@TBEARZ206458 ~]# docker volume ls DRIVER VOLUME NAME
②上面列表是空的,因为本地还没有创建,可以使用 docker volume create 创建卷
[root@TBEARZ206458 ~]# docker volume create 13c0ce66e1dae1cbe5df3c6881e35b575ea26508ecaa0b2b54e2502d25ce5113 [root@TBEARZ206458 ~]# docker volume ls DRIVER VOLUME NAME local 13c0ce66e1dae1cbe5df3c6881e35b575ea26508ecaa0b2b54e2502d25ce5113
如果想要创建一个自定义名称的卷,可以在create后指定名称。
[root@TBEARZ206458 ~]# docker volume create myvolume myvolume [root@TBEARZ206458 ~]# docker volume ls DRIVER VOLUME NAME local 13c0ce66e1dae1cbe5df3c6881e35b575ea26508ecaa0b2b54e2502d25ce5113 local myvolume [root@TBEARZ206458 ~]#
③卷创建好之后,可以用它来启动一个容器,这里主要是在 docker container run命令中使用如下的参数:
由三个由冒号(:)分隔的字段组成,[HOST-DIR:]CONTAINER-DIR[:OPTIONS]。
HOST-DIR 代表主机上的目录或数据卷的名字。省略该部分时,会自动创建一个匿名卷。如果是指定主机上的目录,需要使用绝对路径。
CONTAINER-DIR 代表将要挂载到容器中的目录或文件,即表现为容器中的某个目录或文件
OPTIONS 代表配置,例如设置为只读权限(ro),此卷仅能被该容器使用(大写Z),或者可以被多个容器使用 (小写z)。多个配置项由逗号分隔。
例如,我们使用 -v volume1:/volume1:ro,z。代表的是意思是将卷 volume1 挂载到容器中的 /volume1 目录。ro,z 代表该卷被设置为只读(ro),并且可以多个容器使用该卷(z)
由多个键值对组成,键值对之间由逗号分隔。例如: type=volume,source=volume1,destination=/volume1,ro=true。
type,指定挂载类型,可以指定为 bind,volume,tmpfs。
source,当类型为 volume 时,指定卷名称,匿名卷时省略该字段。当类型为 bind,指定路径。可以使用缩写 src。
destination,挂载到容器中的路径。可以使用缩写 dst 或 target。
ro 为配置项,多个配置项直接由逗号分隔一般使用 true 或 false。
举例:
[root@TBEARZ206458 ~]# docker run -v myvolume:/myvolume:ro,z -p 8080:80 -d centosdotent:1.0.0 dd73b7c10ca6865a21a3092e245589e3a4bcbbd3e4401f83632858cfaf4f0e6c
进入到容器内,可以看到myvolume
这里我也实验了一下 如果volume没有创建,那么run一个容器也会成功
然后会自动创建一个volume
[root@TBEARZ206458 ~]# docker volume ls DRIVER VOLUME NAME local 13c0ce66e1dae1cbe5df3c6881e35b575ea26508ecaa0b2b54e2502d25ce5113 local myvolume local myvolume22222
如果进入到这个文件夹,然后做一些操作,因为我现在是只读模式,它会报错
[root@TBEARZ206458 ~]# docker exec -it elated_tu /bin/bash root@6d3f37ae1b65:/home/DotnetWebDemo# cd / root@6d3f37ae1b65:/# ls bin boot dev etc home lib lib64 media mnt myvolumex opt proc root run sbin srv sys tmp usr var root@6d3f37ae1b65:/# cd myvolumex/ root@6d3f37ae1b65:/myvolumex# ls root@6d3f37ae1b65:/myvolumex# mkdir testdir mkdir: cannot create directory 'testdir': Read-only file system root@6d3f37ae1b65:/myvolumex#
创建一个使用同一个volume的容器,修改它的权限是可读可写(去掉了ro)
[root@TBEARZ206458 ~]# docker run -v myvolume:/myvolumey:z -p 8084:80 -d centosdotent:1.0.0 219e4183b6aa0639a8454ed10d4048c12571ae2851d4b27f1f99e2837bda29b4
然后在里面创建一个目录和文件
[root@TBEARZ206458 ~]# docker exec -it 219e4183b6aa0639a8454ed10d4048c12571ae2851d4b27f1f99e2837bda29b4 /bin/bash root@219e4183b6aa:/home/DotnetWebDemo# cd / root@219e4183b6aa:/# cd myvolumey/ root@219e4183b6aa:/myvolumey# mkdir test root@219e4183b6aa:/myvolumey# cd test/ root@219e4183b6aa:/myvolumey/test# touch file1.txt
然后退出,进入上一个容器,可以看到共享的volume里面的内容
[root@TBEARZ206458 ~]# docker exec -it elated_tu /bin/bash root@6d3f37ae1b65:/home/DotnetWebDemo# cd /myvolumex/ root@6d3f37ae1b65:/myvolumex# ls test root@6d3f37ae1b65:/myvolumex# cd test/ root@6d3f37ae1b65:/myvolumex/test# ls file1.txt
2. bind-mounts
对于volume来说,其优点在于方便管理。而对于绑定挂载(bind-mounts)来说,通过将主机上的目录绑定到容器中,容器就可以操作和修改主机上该目录的内容。这既是其优点也是其缺点。
举例:例如把本地主机上的一个目录挂在到容器当中,可以使用如下的操作:
[root@TBEARZ206458 ~]# cd /home/ [root@TBEARZ206458 home]# mkdir test [root@TBEARZ206458 home]# ls lic test [root@TBEARZ206458 home]# docker run -v /home/test:/home/mytest:z -p 8085:80 -d centosdotent:1.0.0 37c6ce742548956a8619b42a2da56b9342b49be634761ce6e369abf96452b03e
和上面一样,只要把 -v 后面的 HOST-DIR 的换成本地刚刚创建好的目录(/home/test)就行
如果绑定挂载时指定的容器目录是非空的,则该目录中的内容将会被覆盖。并且如果主机上的目录不存在,会自动创建该目录。
注意:对于挂载文件(一定是文件!)来说,可能会出现一些特殊情况,涉及到绑定挂载和使用卷的区别。
①首先在/home/test 下创建一个文件
[root@TBEARZ206458 home]# cd test/ [root@TBEARZ206458 test]# ls [root@TBEARZ206458 test]# touch filehello.txt
②创建一个容器,并将该文件挂载到容器内
docker run -v /home/test/filehello.txt:/home/mytest/filehello.txt:z -p 8086:80 -d centosdotent:1.0.0 检查一下 [root@TBEARZ206458 test]# docker exec -it loving_einstein /bin/bash root@5175c06e34de:/home/DotnetWebDemo# cd /home/mytest/ root@5175c06e34de:/home/mytest# ls filehello.txt
③退出容器,现在在外层通过 echo 写入内容到 filehello.txt当中
[root@TBEARZ206458 test]# echo "hello world" > filehello.txt [root@TBEARZ206458 test]# cat filehello.txt hello world
④ 进入docker内部查看一下,发现也存在了刚刚输入的文字,顺便看下文件的inode(1315091 )
[root@TBEARZ206458 test]# docker exec -it loving_einstein /bin/bash root@5175c06e34de:/home/DotnetWebDemo# cd /home/mytest/ root@5175c06e34de:/home/mytest# cat filehello.txt hello world root@5175c06e34de:/home/mytest# stat filehello.txt File: filehello.txt Size: 12 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 1315091 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-03-09 08:52:00.112634707 +0000 Modify: 2020-03-09 08:51:52.612701048 +0000 Change: 2020-03-09 08:51:52.612701048 +0000 Birth: -
⑤现在退出容器,查看下外部的文件的indoe,使用Vim编辑文件,修改文件,发现inode 从 1315091 -> 1315097
[root@TBEARZ206458 test]# stat filehello.txt File: ‘filehello.txt’ Size: 12 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 1315091 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-03-09 16:52:00.112634707 +0800 Modify: 2020-03-09 16:51:52.612701048 +0800 Change: 2020-03-09 16:51:52.612701048 +0800 Birth: - [root@TBEARZ206458 test]# vim filehello.txt [root@TBEARZ206458 test]# cat filehello.txt hello world lalala hehehe [root@TBEARZ206458 test]# stat filehello.txt File: ‘filehello.txt’ Size: 26 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 1315097 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-03-09 16:56:02.736480509 +0800 Modify: 2020-03-09 16:55:56.144539240 +0800 Change: 2020-03-09 16:55:56.156539134 +0800 Birth: -
⑥再次进入容器,查看一下该文件, 发现容器内的文件 inode和内容全都不变。
[root@TBEARZ206458 test]# docker exec -it loving_einstein /bin/bash root@5175c06e34de:/home/DotnetWebDemo# cd /home/mytest/ root@5175c06e34de:/home/mytest# stat filehello.txt File: filehello.txt Size: 12 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 1315091 Links: 0 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-03-09 08:52:00.112634707 +0000 Modify: 2020-03-09 08:51:52.612701048 +0000 Change: 2020-03-09 08:55:56.156539134 +0000 Birth: - root@5175c06e34de:/home/mytest# cat filehello.txt hello world
Vim是采用先备份,后修改替换的方式修改文件,所以每次Vim操作完之后文件的inode会变化。如果修改的文件正好是挂载在容器里面的文件,那么容器内的文件还是旧文件。
对于数据卷来说,由 docker 完全管理,而绑定挂载,则需要我们自己去维护。我们需要自己手动去处理这些问题,这些问题并不仅仅是上面演示的内容,还可能有用户权限,SELINUX 等问题。
3. tmpfs
tmpfs 只存储在主机的内存中。当容器停止时,相应的数据就会被移除。不举例了。
三、数据卷容器
如果容器之间需要共享一些持续更新的数据,最简单的方式就是使用用户数据卷容器。
其他容器通过挂载这个容器实现数据共享,这个挂载数据卷的容器就叫做数据卷容器。
数据卷容器就是一种普通容器,它专门提供数据卷供其它容器挂载使用。
①创建数据卷容器,并在卷文件夹下创建一个文件
[root@TBEARZ206458 ~]# docker container run -it -v vdata:/sharedata --name IamVolumeContainer centos /bin/bash [root@TBEARZ206458 ~]# docker exec -it IamVolumeContainer /bin/bash [root@efdbc9a25138 /]# cd sharedata/ [root@efdbc9a25138 sharedata]# echo "I'm a VolumeContainer" > file.txt //写入文件 [root@efdbc9a25138 sharedata]# ls file.txt [root@efdbc9a25138 sharedata]# cat file.txt I'm a VolumeContainer
②创建新的容器,使用刚刚创建的数据卷容器,需要使用的run参数是 --volumes-from 继承数据卷容器 IamVolumeContainer 挂载的数据卷
[root@TBEARZ206458 ~]# docker run -it --volumes-from IamVolumeContainer --name shareContainer centos /bin/bash [root@d087086ada1a /]# cd sharedata/ [root@d087086ada1a sharedata]# ls file.txt [root@d087086ada1a sharedata]# echo "I'm sharecontainer1" > file2.txt [root@d087086ada1a sharedata]# ls file.txt file2.txt
③和上面一样创建新容器,查看是否有上两次创建的文件
[root@TBEARZ206458 ~]# docker run -it --volumes-from IamVolumeContainer --name shareContainer2 centos /bin/bash [root@d26d32b26060 /]# cd sharedata/ [root@d26d32b26060 sharedata]# ls file.txt file2.txt [root@d26d32b26060 sharedata]# cat file2.txt I'm sharecontainer1 [root@d26d32b26060 sharedata]# echo "I'm sharecontainer2" > file3.txt [root@d26d32b26060 sharedata]# ls file.txt file2.txt file3.txt
④再回到原来的数据卷容器,可以发现文件被共享了
[root@TBEARZ206458 ~]# docker exec -it IamVolumeContainer /bin/bash [root@efdbc9a25138 /]# cd /sharedata/ [root@efdbc9a25138 sharedata]# ls file.txt file2.txt file3.txt
II.数据备份和还原
①使用备份容器,备份数据
[root@TBEARZ206458 ~]# docker container run --volumes-from IamVolumeContainer -v /home/share/backup:/backup centos tar cvf /backup/backup.tar /sharedata/ 以下是执行 tar: Removing leading `/' from member names /sharedata/ /sharedata/file2.txt /sharedata/file3.txt [root@TBEARZ206458 ~]#
命令解析
--volumes-from IamVolumeContainer 代表继承自IamVolumeContainer这个容器的卷启动新容器
-v /home/share/backup:/backup 代表把 /home/share/backup 挂载到容器的 /backup 目录
centos 镜像名称
tar cvf /backup/backup.tar /sharedata/ 代表把 /sharedata/ 的内容全部压缩打包到 /backup/backup.tar文件
执行之后可以看到,本地主机已经有了对应的tar文件
[root@TBEARZ206458 ~]# cd /home/share/backup/ [root@TBEARZ206458 backup]# ls backup.tar [root@TBEARZ206458 backup]#
②使用恢复容器恢复数据
例如创建这样一个文件
[root@TBEARZ206458 recover]# pwd /home/share/recover [root@TBEARZ206458 recover]# ls recover.tar
创建一个恢复容器
[root@TBEARZ206458 recover]# docker container run --volumes-from IamVolumeContainer -v /home/share/recover:/recover centos tar xvf /recover/recover.tar 以下是执行 recover/ recover/readme.txt