容器技术之Docker数据卷

前一篇随笔中我们了解了docker的网络相关说明,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12952616.html;今天我们来聊一聊docker的数据管理相关说明;

在前面的博客中我们有强调过docker的镜像是分层构建的,把镜像启动为容器后,docker会加载只读层镜像并在镜像最顶层添加一个读写层;我们还说过该读写层不属于镜像属于容器;如果该容器宕掉了,那么随之镜像最顶层的读写层也随之消失;那么问题来了,如果之前运行的容器,在宕掉前在可写层生成的数据怎么保存下来呢?为了解决这个问题,我们先来看看docker的COW机制吧;

容器技术之Docker数据卷

   如上图所示,docker镜像是分层构建的,在最下面一层的数据到第二层或更高层有相同数据时,在上面一层的数据就会覆盖下面一层相同的数据;从而使得在上面一层和下面一层相同的数据只会存在一份,不同的数据都会存在;同样的道理,docker镜像通过层层镜像构建,在docker镜像最顶层看到的数据就应该是下面若干层重叠以后,相同的数据在最上层只会看到一份,而其他数据则是由下面若干层不同数据的叠加构成;在运行层容器时,容器内部看到的数据就是所有层镜像不同数据的集合和相同数据留一份的结果;如果运行过程中容器修改了现有文件,那么该文件就从读写层下面去复制一份到读写层,该文件的原来在只读层的文件还是存在,只是已经被读写层把该文件的副本隐藏了,这就叫docker的写时复制(cow)机制;

从上面的信息我们了解到docker容器在产生数据是在可写层,在修改数据时,会把原来的数据从原来只读层复制到可写层,从而隐藏原来的只读层的,但是只读层的数据还在;所以容器删除后,我们修改的数据不会保存到镜像,而是随容器的删除而删除;关闭并重启容器对于数据不受什么影响;我们现在有没有一种办法让读写层的数据保存起来,即便容器被删除后数据还依然可存在呢?

先来说说nfs数据共享吧;nfs共享是通过把一个目录通过网络的形式挂载到另外一个目录;从而实现往一个目录写数据时,就相当于把数据写到远端的目录的文件中;在docker容器中我们把这种类似的方式叫做卷;所谓卷就是容器上的一个或多个目录,此类目录可绕过联合文件系统,与宿主机上的某目录产生关联关系(绑定,类似nfs里的挂载);volume于容器初始化之时就会创建,由base image提供的卷中的数据会在此期间完成复制;volume的初衷是独立于容器的生命周期实现数据持久化,因此删除容器时,既不会删除卷,也不会对那些没有被引用的卷作垃圾回收操作;

卷为docker提供了独立于容器的数据管理机制;我们可以把镜像想象成静态文件,例如程序,把卷类比成动态内容,比如数据,于是镜像可以复用,而卷可以共享;卷实现了程序(镜像)和数据(卷)的分离;实现了容器之间的数据共享和复用,使得容器间传递数据变得高效方便;对数据卷内数据的修改会立马生效,无论是在容器中修改还是在本地操作;docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同而已;bind mount volume这种卷是由用户指定目录把存储上的一个目录挂载到容器内部的某个目录;docekr-managed volume这种卷是docker自己管理的卷,通常表现形式上把宿主机上的/var/lib/docker/vfs/dir/某个卷的ID 挂载到容器内部某个目录下;

示例:在的docker容器中使用docker-managed 类型的volume

[ ~]# docker run --name m2 -it --rm  -v /mydata linux1874/myimg:v0.1 /bin/sh  
/ # ls /
bin     dev     etc     home    mydata  proc    root    sys     tmp     usr     var
/ # cd /mydata/
/mydata # ls
/mydata # [ ~]# docker container inspect m2 -f {{.Mounts}}        
[{volume 65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422 /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data /mydata local  true }]
[ ~]# ll /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data 
total 0
[ ~]# echo "hello world" > /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data/aa.txt
[ ~]# cat /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data/aa.txt
hello world
[ ~]# docker attach m2
/mydata # ls
aa.txt
/mydata # cat aa.txt 
hello world
/mydata #

提示:-v只指定了一个目录表示指定容器内部的目录,它会通过docker daemon 自动在宿主机上生成挂载目录;像这种方式的挂载就叫做docker -managed类型的数据卷;从上面的信息可以看到我们可以用docker container inspect -f {{.Mounts}} 容器名称,来查看容器的卷、标识符以及挂载点主机目录信息;我们在生成的挂载目录下创建一个aa.txt的文件,然后在容器里/mydata里能够看到该文件和文件内容;

测试:我们把容器停掉看看数据卷是否会被删除呢?

[ ~]# cat /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data/aa.txt
hello world
[ ~]# docker attach m2
/mydata # ls
aa.txt
/mydata # cat aa.txt 
hello world
/mydata # exit
[ ~]# 
[ ~]# docker ps 
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS               NAMES
33c6f135eb86        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   20 minutes ago      Up 20 minutes                           m1
[ ~]# cat /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data/aa.txt
cat: /var/lib/docker/volumes/65db95bc3010530381e2bcc20fdd216329a502527e17a1e70c1cb7f6e2d2a422/_data/aa.txt: No such file or directory
[ ~]#

提示:在运行容器时如果使用了--rm选项,但容器停掉以后,对应挂载点数据卷会随之删除;

测试:运行容器不使用--rm选项,看看容器停掉后,是否还会删除数据卷?

[ ~]# docker run --name m2 -v /mydata -it linux1874/myimg:v0.1 /bin/sh
/ # ls /
bin     dev     etc     home    mydata  proc    root    sys     tmp     usr     var
/ # cd mydata/
/mydata # ls
/mydata # [ ~]# docker container inspect -f {{.Mounts}} m2
[{volume 28c477840d926c2fc33ebfacd607b84cfd5a272d279e1f966a65b7cb2f1d9a2e /var/lib/docker/volumes/28c477840d926c2fc33ebfacd607b84cfd5a272d279e1f966a65b7cb2f1d9a2e/_data /mydata local  true }]
[ ~]# cd /var/lib/docker/volumes/28c477840d926c2fc33ebfacd607b84cfd5a272d279e1f966a65b7cb2f1d9a2e/_data
[ _data]# ls
[ _data]# echo "hello tom" > aa.txt
[ _data]# cat aa.txt
hello tom
[ _data]# docker attach m2
/mydata # ls
aa.txt
/mydata # cat aa.txt 
hello tom
/mydata # exit
[ _data]#docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS                     PORTS               NAMES
e6560584d41e        linux1874/myimg:v0.1   "/bin/sh"                About a minute ago   Exited (0) 9 seconds ago                       m2
33c6f135eb86        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   32 minutes ago       Up 32 minutes                                  m1
[ _data]# docker container rm m2
m2
[ _data]# docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS               NAMES
33c6f135eb86        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   35 minutes ago      Up 35 minutes                           m1
[ _data]# cd 
[ ~]# cd -
/var/lib/docker/volumes/28c477840d926c2fc33ebfacd607b84cfd5a272d279e1f966a65b7cb2f1d9a2e/_data
[ _data]# ls
aa.txt
[ _data]# cat aa.txt 
hello tom
[ _data]#

提示:在不使用--rm选项运行容器,容器停止后删除容器,对于数据卷是不会被删除的;

示例:在docker容器中使用bind-mount 类型volume

[ ~]# mkdir /hostdir/v1 -p
[ ~]# docker run --name m3 -it -v /hostdir/v1/:/mydata linux1874/myimg:v0.1 /bin/sh
/ # ls /
bin     dev     etc     home    mydata  proc    root    sys     tmp     usr     var
/ # cd /mydata/
/mydata # ls
/mydata # [ ~]# docker container inspect -f {{.Mounts}} m3
[{bind  /hostdir/v1 /mydata   true rprivate}]
[ ~]# cd /hostdir/v1/
[ v1]# ls
[ v1]# echo "hello jerry" > aa.sh
[ v1]# cat aa.sh 
hello jerry
[ v1]# docker attach m3
/mydata # ls
aa.sh
/mydata # cat aa.sh 
hello jerry
/mydata # exit
[ v1]# docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS                     PORTS               NAMES
62ddbdc11a62        linux1874/myimg:v0.1   "/bin/sh"                About a minute ago   Exited (0) 4 seconds ago                       m3
33c6f135eb86        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   41 minutes ago       Up 41 minutes                                  m1
[ v1]# docker container rm m3
m3
[ v1]# cd
[ ~]# cd -
/hostdir/v1
[ v1]# ls
aa.sh
[ v1]# cat aa.sh 
hello jerry
[ v1]#

提示:-v指定宿主机目录:容器目录表示把宿主机目录挂载到容器的某个目录;容器里的目录可以是不存在的目录,它会自动创建;这种数据卷我们叫做bind-mount类型的数据卷;通常表现形式就是用户自己定义把宿主机的那个目录当作数据卷挂载到容器里的某个目录;和上面一样我们在宿主机上的对应目录下创建文件,在容器对应目录是可以正常访问到该文件的;-v可使用多次来指定不同的数据卷挂载关系;通常bind-mount类型的卷用的比较多;

示例:多个容器的卷使用同一宿主机目录

[ ~]# ll /hostdir/v1/aa.sh 
-rw-r--r-- 1 root root 12 May 24 13:59 /hostdir/v1/aa.sh
[ ~]# cat /hostdir/v1/aa.sh
hello jerry
[ ~]#  docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[ ~]# docker run --name m1 -d -v /hostdir/v1/:/var/www/web/html linux1874/myimg:v0.1
9eb3287e4007f0656b1f099aac9504724307679ac41689774059388a96d75cf0
[ ~]# docker container inspect -f {{.Mounts}} m1
[{bind  /hostdir/v1 /var/www/web/html   true rprivate}]
[ ~]# docker container inspect -f {{.NetworkSettings.IPAddress}} m1      
172.17.0.2
[ ~]# curl http://172.17.0.2
<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>
<BODY><H1>404 Not Found</H1>
The requested URL was not found
</BODY></HTML>
[ ~]# curl http://172.17.0.2/aa.sh
hello jerry
[ ~]# docker run --name m2 -d -v /hostdir/v1/:/var/www/web/html linux1874/myimg:v0.1
ff877e29d10c55b355b5270218d486894958880058e2eb7bef9bdd1c7c531f0f
[ ~]# docker container inspect -f {{.Mounts}} m2
[{bind  /hostdir/v1 /var/www/web/html   true rprivate}]
[ ~]# docker container inspect -f {{.NetworkSettings.IPAddress}} m2
172.17.0.3
[ ~]# curl http://172.17.0.3
<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>
<BODY><H1>404 Not Found</H1>
The requested URL was not found
</BODY></HTML>
[ ~]# curl http://172.17.0.3/aa.sh
hello jerry
[ ~]#

提示:数据卷挂载到容器会覆盖容器原有文件;这个同mount挂载没有本质的不同;从上面信息可以看到aa.sh就把原有的index.html给覆盖了;除了以上方式指定挂载同一宿主机目录外,我们还可以使用--volumes-from选项来指定从那个容器里复制数据挂载到本容器;如下

[ ~]# docker run --name m3 -d --volumes-from m1 linux1874/myimg:v0.1
61a1fbdc9c559a594870dc1e2bbf505a4eb588fc03e5ab50233b55b907beb9b0
[ ~]# docker container inspect -f {{.Mounts}} m3
[{bind  /hostdir/v1 /var/www/web/html   true rprivate}]
[ ~]# docker container inspect -f {{.NetworkSettings.IPAddress}} m3 
172.17.0.4
[ ~]# curl http://172.17.0.4/aa.sh
hello jerry
[ ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
linux1874/myimg     v0.1                e408b1c6e04f        38 hours ago        1.22MB
busybox             latest              78096d0a5478        10 days ago         1.22MB
centos              7                   b5b4d78bc90c        2 weeks ago         203MB
nginx               stable-alpine       ab94f84cc474        4 weeks ago         21.3MB
[ ~]# docker run --name c1 -it --volumes-from m1 centos:7 /bin/sh
sh-4.2# ls /
anaconda-post.log  dev  home  lib64  mnt  proc  run   srv  tmp  var
bin                etc  lib   media  opt  root  sbin  sys  usr
sh-4.2# cat /var/www/web/html/aa.sh 
hello jerry
sh-4.2#

示例:以只读方式挂载数据卷

[ ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[ ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
linux1874/myimg     v0.1                e408b1c6e04f        38 hours ago        1.22MB
busybox             latest              78096d0a5478        10 days ago         1.22MB
centos              7                   b5b4d78bc90c        2 weeks ago         203MB
nginx               stable-alpine       ab94f84cc474        4 weeks ago         21.3MB
[ ~]# docker run --name c1 -it -v /hostdir/v1/:/mydata:ro centos:7 /bin/sh
sh-4.2# ls /
anaconda-post.log  dev  home  lib64  mnt     opt   root  sbin  sys  usr
bin                etc  lib   media  mydata  proc  run   srv   tmp  var
sh-4.2# cd mydata/
sh-4.2# ls
aa.sh
sh-4.2# echo "hello tom" > aa.sh 
sh: aa.sh: Read-only file system
sh-4.2# exit
exit
[ ~]# docker run --name c2 -it -v /hostdir/v1/:/mydata centos:7 /bin/sh
sh-4.2# cd /mydata/
sh-4.2# ls
aa.sh
sh-4.2# echo "hello tom" > aa.sh 
sh-4.2# cat aa.sh 
hello tom
sh-4.2# exit
exit
[ ~]#

提示:我们挂载数据卷不指定权限默认是rw,以读写方式挂载;指定权限为ro(只读)挂载数据卷后,在容器内部就不能修改数据卷里的文件内容了;

示例:利用容器备份另一容器的数据

[ ~]# ll /hostdir/v1/
total 4
-rw-r--r-- 1 root root 10 May 24 14:42 aa.sh
[ ~]# echo "hello world" > /hostdir/v1/bb.sh
[ ~]# echo "hello world,hello tom" > /hostdir/v1/cc.sh  
[ ~]# ls /hostdir/v1/
aa.sh  bb.sh  cc.sh
[ ~]# docker run --name m1 -d -v /hostdir/v1/:/var/www/web/html linux1874/myimg:v0.1
5959e832c01f68c24e2542138a95eb7bfeabcb7608b070ef1b536c625ddfd612
[ ~]# docker run --name c1 --volumes-from m1 -v $(pwd):/backup centos:7 tar cvf /backup/backup.tar.gz /var/www/web/html
tar: Removing leading `/‘ from member names
/var/www/web/html/
/var/www/web/html/aa.sh
/var/www/web/html/bb.sh
/var/www/web/html/cc.sh
[ ~]# ls
backup.tar.gz
[ ~]# tar xf backup.tar.gz 
[ ~]# ls
backup.tar.gz  var
[ ~]# cd var/www/web/html/
[ html]# ls
aa.sh  bb.sh  cc.sh
[ html]# cat aa.sh bb.sh cc.sh 
hello tom
hello world
hello world,hello tom
[ html]#

提示:解释下上面利用容器备份另一容器里的数据;首先通过--volumes-from来克隆m1的数据卷,然后在通过-v来指定把当前路径挂载到容器内部的/backup目录下,然后通过启动容器执行tar cvf /backup/backup.tar.gz /var/www/web/html来把容器里的/var/www/web/html的文件打包到/backup/backup.tar.gz(容器内部的目录);能够在当前目录看到打包的文件原因是因为我们把当前路径挂载到容器里的/backup目录,所以我们在当前宿主机目录能够看到打包好的文件;

示例:利用容器恢复刚才打包的数据文件

首先运行一个容器

[ ~]# docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS               NAMES
5959e832c01f        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   15 minutes ago      Up 15 minutes                           m1
[ ~]# docker run --name m2 -it -v /var/www/web/html linux1874/myimg:v0.1 /bin/sh
/ # ls /var/www/web/html/
index.html
/ # [ ~]#

然后在来一容器克隆之前容器的数据卷,通过挂载当前目录为数据卷,在执执行 tar xf命令来解包

[ ~]# docker ps -a
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS               NAMES
6462f22e0d7f        linux1874/myimg:v0.1   "/bin/sh"                2 minutes ago       Up 2 minutes                            m2
5959e832c01f        linux1874/myimg:v0.1   "/bin/sh -c ‘/bin/ht…"   22 minutes ago      Up 21 minutes                           m1
[ ~]# docker run --name c2 --volumes-from m2 -v $(pwd):/backup centos:7 tar xf /backup/backup.tar.gz
[ ~]# docker attach m2
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # ls /var/www/web/html/
aa.sh       bb.sh       cc.sh       index.html
/ # cd /var/www/web/html/
/var/www/web/html # cat aa.sh bb.sh cc.sh 
hello tom
hello world
hello world,hello tom
/var/www/web/html #

提示:以上命令的思想是运行容器C2 把m2的数据卷挂载先克隆到c2上,然后通过挂载当前宿主机目录到容器的/backup,然后再执行解包操作即可;这里还需要注意一点,如果容器的工作目录不是根目录或者是其他特殊目录,解压命令可以通过-C来指定解压到那个目录;