目录
docker容器的数据卷
什么是数据卷
我们知道,docker的理念是将我们运行的程序与运行环境进行打包,因此docker容器的生存周期与容器中运行的程序相一致,而我们对数据的要求是持久化的。另一方面,docker容器之间也需要一个共享容器的渠道。这些需求就催生了数据卷的产生。
数据卷的特点
1、数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含了数据,这些数据会拷贝到新初始化的数据卷中。
2、数据卷会拷贝到新初始化的数据卷中
3、可以对数据卷里面的内容直接进行修改
【即所有的修改就会直接体现到数据卷中】
4、数据卷的变化不会影响镜像的更新
【因为数据卷是独立于联合文件系统的,而镜像是基本联合文件系统,所以镜像与数据卷之间不会有相互影响的情况】
5、卷会一直存在,即使挂载数据卷的容器已经被删除
【因为数据卷本质上是宿主机上面的一个目录】
如何为容器添加数据卷
命令:sudo docker run -v ~/container_data:/data -it ubuntu /bin/bash
说明:-v 选项来指定数据卷在本机文件系统中的目录和在容器中映射的目录名
注意:若本地文件目录中没有我们指定的文件目录的话,run命令会自动创建
[leechm@localhost ~]$ docker run -it -v ~/datavolume:/data ubuntu /bin/bash
root@04cdef6057a9:/# ls -l 查看是否生成data目录
root@04cdef6057a9:/# touch /data/c1 在data目录下创建一个文件
root@04cdef6057a9:/# echo "i am container" > /data/c1 往文件里面写东西,以便后面测试
root@04cdef6057a9:/# exit 退出容器
exit
[leechm@localhost ~]$ ls -l 在本机上运行这个命令,发现有一个datavolume文件夹
[leechm@localhost ~]$ vi datavolume/c1 打开在容器中创建的文件C1,发现里面有内容
[leechm@localhost ~]$ docker ps -l 查看刚才生成的镜像
[leechm@localhost ~]$ docker inspect 04cdef6057a9 查看数据卷是否挂载成功在容器中
数据卷的使用
1、为数据卷添加访问权限
命令:sudo docker run -it -v ~/datavolume:/data:ro --name dvt1 ubuntu /bin/bash
我们使用docker run命令重新创建一个数据卷,ro表示只读的文件权限限制
[leechm@localhost ~]$ docker run -it -v ~/datavolume:/data:ro --name dvt1 ubuntu /bin/bash
root@cdd57700fdbe:/# ls -l 查看data目录是否挂载上
root@cdd57700fdbe:/# touch /data/c2 创建一个文件
touch: cannot touch '/data/c2': Read-only file system 由于我们设置了只读选项,因此我们不能在这个容器中的数据卷中创建文件
root@cdd57700fdbe:/# exit 退出容器
[leechm@localhost ~]$ docker inspect dvt1 使用inspect命令查看刚刚运行的容器,
发现如下的"RW": 为false,即不能读写
"Mounts": [
{
"Type": "bind",
"Source": "/home/leechm/datavolume",
"Destination": "/data",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
],
除了使用docker run命令添加数据卷,我们也可以使用Dockerfile构建包含数据卷的镜像
Docker指令:
VOLUME["/data"]
例如:
下面的是一个Dockerfile例子
FROM ubuntu:14.04
VOLUME ["/datavolume1","datavolume2"]
CMD /bin/bash
分析:我们创建了两个数据卷,与docker run不同的是,在Dockerfile中创建的数据卷不能映射到已经存在的本地文件目录中的,在镜像构建时指定的数据卷会在容器启动时创建我们指定名字的数据卷,并且运行同样镜像的不同容器所创建的不同数据卷也是不一样的。
步骤演示:
[leechm@localhost dvt]$ mkdir -p ~/dockerfile/dvt
[leechm@localhost dvt]$ cd ~/dockerfile/dvt
[leechm@localhost dvt]$ vi Dockerfile 填写上面的例子
[leechm@localhost dvt]$ docker run --name dvt3 -it dormancyprss/dvt 运行这个镜像(由于我们在镜像中添加数据卷的指令,所以我们在容器启动的时候不用-v参数了)
root@1b78a072c918:/# ls 查看有两个文件目录生成datavolume1和datavolume2
root@1b78a072c918:/# exit 退出容器
[leechm@localhost dvt]$ docker inspect dvt3 查看刚刚创建的容器
........
"Mounts": [
{
"Type": "volume",
"Name": "f20805cf48500e46583eef6d63f644ef590459e69ab42ec2496c79c93f754f92",
"Source": "/var/lib/docker/volumes/f20805cf48500e46583eef6d63f644ef590459e69ab42ec2496c79c93f754f92/_data",
"Destination": "/datavolume2",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "59b57fa43c6fa8a55d376b9951502c9b582fe6e1f9680022946582255318bddc",
"Source": "/var/lib/docker/volumes/59b57fa43c6fa8a55d376b9951502c9b582fe6e1f9680022946582255318bddc/_data",
"Destination": "/datavolume1",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
分析:我们发现上面的数据卷Source文件目录是docker自动创建的,如果我们使用同样的镜像再创建一个容器dvt4 ,我们再使用inspect命令去找那个Source文件目录,发现两个容器的数据卷的地址就不一样了。
[leechm@localhost dvt]$ docker run --name dvt4 -it dormancyprss/dvt
root@97127fda2547:/# exit
[leechm@localhost dvt]$ docker inspect dvt4
..........
"Mounts": [
{
"Type": "volume",
"Name": "810eb6c40e986f6973562af5e9ba15e718a002f9b38e1b6567eab3a3214d10d1",
"Source": "/var/lib/docker/volumes/810eb6c40e986f6973562af5e9ba15e718a002f9b38e1b6567eab3a3214d10d1/_data",
"Destination": "/datavolume2",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "c133b5691731843372058dddc4d4b09f4566c4ec92111248d2dde3d5dc86c907",
"Source": "/var/lib/docker/volumes/c133b5691731843372058dddc4d4b09f4566c4ec92111248d2dde3d5dc86c907/_data",
"Destination": "/datavolume1",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
这个就说明在容器启动时,我们在镜像中指定的数据卷都会进行一次完整的初始化,那么我们根据镜像指定数据卷来创建的容器来使用的数据卷就无法共享,当我们不能访问到本地目录时,我们如何在容器中共享数据?
docker的数据卷容器
定义:命名的容器挂载数据卷,其他容器通过挂载这个容器实现数据共享,挂载数据卷的容器,就叫做数据卷容器。
数据卷容器和容器数据卷的不同
数据卷容器挂载了一个本地目录,其他容器通过连接这个数据卷容器来实现数据的共享。
那么我们的数据卷容器是怎么启动的呢?我们通过挂载数据卷容器的方法
命令: docker run --volumes-from [CONTAINER NAME]
后面的参数是已经挂载数据卷的容器的名字,也就是我们接下来都是依靠这个容器进行数据的共享。
例如:
[leechm@localhost dvt]$ docker run -it --name dvt4 dormancyprss/dvt 使用run命令来建立一个dvt4容器,使用的镜像是dormancyprss/dvt
root@24deaad9c651:/# ls 查看dormancyprss/dvt镜像生成的数据卷
注意:我们的dormancyprss/dvt镜像已经包含了数据卷的指令,即VOLUME字段,所以我们不用使用-v选项来生成数据卷。我们这个镜像是之前做过的一个镜像,可以用其他镜像来演示
root@24deaad9c651:/# touch /datavolume1/dvt4_1 在其中一个数据卷中写入一个文件
root@24deaad9c651:/# exit
[leechm@localhost dvt]$ docker run -it --name dvt5 --volumes-from dvt4 ubuntu /bin/bash
建立一个容器来挂载刚刚创建好的包含数据卷的容器,新建的容器名字叫dvt5 ,挂载的容器名叫dvt4
root@64754b711194:/# ls 查看挂载在dvt4中挂载的数据卷
root@64754b711194:/# ls /datavolume1 查看刚刚在的dvt4中创建的文件
root@64754b711194:/# touch /datavolume1/dvt5_1 再创建一个文件
root@64754b711194:/# exit 退出这个容器
[leechm@localhost dvt]$ docker run -it --name dvt6 --volumes-from dvt4 ubuntu /bin/bash 再启动一个新的容器dvt6,同样挂载dvt4
root@907fc1a36265:/# ls /datavolume1 就可以看到两个文件,即挂载成功
总结:使用数据卷容器可以很好的在不同的容器之间共享数据。同时,我们也不需要使用者确切的连接到已知的docker宿主机的文件目录,这个在多住户的选择环境下非常重要,因为我们在多住户的环境下不想要暴露我们服务器的实际目录。
下面检验一下删除共享的数据卷容器
[leechm@localhost dvt]$ docker rm dvt4 删除用来提供数据共享的数据卷容器
[leechm@localhost dvt]$ docker restart dvt5 重新启动容器dvt5
[leechm@localhost dvt]$ docker attach dvt5 重新进入容器dvt5
root@64754b711194:/# touch datavolume1/dvt6_1 创建一个新的文件
root@64754b711194:/# ls -l datavolume1 查看删除共享的数据卷容器之后,之前创建的文件还在
下面再检验一个其他的情况
[leechm@localhost dvt]$ docker run --name dvt8 dormancyprss/dvt 再运行一个容器dvt8
[leechm@localhost dvt]$ docker run -it --name dvt9 --volumes-from dvt8 ubuntu /bin/bash 运行一个容器来挂载dvt8这个数据卷容器
root@0fe2be594948:/# touch /datavolume1/dvt9_1 创建一个文件
root@0fe2be594948:/# exit
[leechm@localhost dvt]$ docker rm -v dvt8 删除dvt8,-v选项告诉删除时也要删除其挂载的数据卷
[leechm@localhost dvt]$ docker restart dvt9
[leechm@localhost dvt]$ docker attach dvt9
root@0fe2be594948:/# ls
root@0fe2be594948:/# ls -l datavolume1 查看这个目录下,没有之前的残留文件,只有自己创建的,这个就是-v的作用。
总结:我们手动删除容器,如上面的docker rm dvt4,只是删除一个容器,并不会删除这个容器启动的时候创建的volume卷(在/var/lib/docker/volumes/下或者使用docker volume ls命令可以看到)。但是如果加了-v参数,即上面的docker rm -v dvt8,这个命令在删除容器的同时,也会把容器创建的volume卷删除了。
Docker数据卷的备份与还原
前言:既然docker数据卷是用来保存数据的,那么我们如何备份和还原这个数据就很重要了。
数据备份方法
docker run --volumes-from [container name] -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar [container data volume]
分析:这里面有设置的目录和要执行的命令,tar命令
[leechm@localhost ~]$ docker restart dvt5
[leechm@localhost ~]$ docker attach dvt5
root@64754b711194:/# ls 查看当前有啥目录
root@64754b711194:/# ls datavolume1 查看该目录下有啥
root@64754b711194:/# exit 退出容器
[leechm@localhost ~]$ docker run --volumes-from dvt5 -v ~/backup:/backup --name dvt10 ubuntu tar cvf /backup/dvt5.tar /datavolume1
解释一下这个命令,其实就是启动一个新的容器用来执行备份命令
--volumes-from dvt5 :指定我们需要备份的容器的名字
-v ~/backup:/backup :-v 指定我们需要存放的路径,这里指定的是本地的backup目录下,后面的:是指定容器中的目录,这里是backup目录。
tar cvf /backup/dvt5.tar : 是要执行的命令
/datavolume1: 需要备份的目录,dvt5这个容器中实际上有两个数据卷datavolume1和datavolume2,这里只使用datavolume1
[leechm@localhost ~]$ ls backup/ 查看本地backup目录下
数据还原的话,命令如下:
docker run --volumes-from [container name] -v $(pwd):/backup ubuntu tar xvf /backup/backup.tar [container data volume]
docker overlay存储驱动
我们先了解一下docker中的写时复制机制
docker镜像由多个只读层通过ufs叠加而成,启动容器的时候,docker会加载只读镜像层并在镜像栈顶部添加一个读写层。如果运行中的容器修改了现有的一个已经存在的文件,那么该文件将会从读写层下面的的只读层复制到读写层该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏,即“写时复制”机制。
简单解释一下上面的示例图
假如我们有一个数据A,这个在layer 0 是叫做A,但是在layer2就叫做D,或者说layer2里面没有修改这个数据,在layer3里面叫做D。如果我们拿这个镜像运行一个容器,这个时候就会有一个可写层,注意刚开始的可写层是空白的,不包含镜像层的所有文件,如果我们想要修改镜像层的某个数据的时候,直接把镜像层的数据拷贝至可写层,然后进行修改,并且修改完的文件会把镜像层的文件隐藏,即暴露给用户的数据只是可写层的。这个就是简单的docker里面的写时复制,总结是把镜像层的要修改的数据拷贝至可写层,在可写层中修改。
关于Docker 存储驱动( storage driver ),它是是 Docker 的核心组件,也是Docker 实现分层镜像的基础
1、device mapper ( DM ):性能和稳定性存在问题,不推荐生产环境使用
2、btrfs:社区实现了 btrfs dniver,稳定性和性能存在问题
3、overlayfs:内核3.18 overlayfs进入主线,性能和稳定性优异,第一选择
OverlayFS使用两个目录,把一个目录置放于另一个之上,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作union mount。术语上,下层的目录叫做lowerdir,上层的叫做upperdir。对外展示的统一视图称作merged。
注意镜像层和容器层是如何处理相同的文件的:容器层(upperdir)的文件是显性的,会隐藏镜像层(lowerdir)相同文件的存在。容器映射(merged)显示出统一的视图。
上面的图其实跟之前讲写时复制的时候,差不多的意思,多了一个右边的overlatFS,这个其实是我们容器的不同layer对应overlay文件系统的中的不同目录,lowerdir,upperdir,merged
下面演示一下overlayfs的工作特性,这一点很有意思。
我们随便在哪个目录下创建几个目录,这里我选择/var/overlay,创建完成之后,再使用overlay的挂载命令挂载overlay文件系统到overlay目录下
[root@master1 overlay]# mkdir low work upper merged
[root@master1 overlay]# mount -t overlay overlay -olowerdir=./low,upperdir=./upper,workdir=./work ./merged
这里work目录,工作目录,不需要去管理,自己管理的。
现在我们往不同的目录下写入文件,这里我们只在low和upper目录下写入文件
[root@master1 overlay]# cat low/1.txt
aaaaaaa
[root@master1 overlay]# cat upper/2.txt
bbbbbbb
[root@master1 overlay]#
[root@master1 overlay]# tree
.
├── low
│ └── 1.txt
├── merged
│ ├── 1.txt
│ └── 2.txt
├── upper
│ └── 2.txt
└── work
└── work
5 directories, 4 files
[root@master1 overlay]#
我们发现merged目录也会自动生成两个文件,而且这两个文件跟low,upper里面的一模一样。
我们的用户是自上而下的看到数据,如果下面有的(即upper和low层)就会在上面(merged层)显示出来,如果上面(merged层)有的,就会把下面(upper和low层)覆盖掉。
下面用实例演示这句话的意思
此时我们修改merged里面的数据,low层没有影响,upper层多了一个数据,这个多的数据跟merged一样,这个就是之前说的写时复制。
[root@master1 overlay]# vim merged/1.txt
[root@master1 overlay]# cat merged/1.txt
aaaaaaa
bbbbb
[root@master1 overlay]#
[root@master1 overlay]# cat low/1.txt
aaaaaaa
[root@master1 overlay]# cat upper/
1.txt 2.txt
[root@master1 overlay]# cat upper/1.txt
aaaaaaa
bbbbb
[root@master1 overlay]#
若我们此时删除merged里面的数据,low层不变,upper里面的数据属性变了,c其实就是表示这个文件已经被删除了
[root@master1 overlay]# rm -rf merged/1.txt
[root@master1 overlay]# ls -l low/
总用量 4
-rw-r--r-- 1 root root 8 8月 15 16:28 1.txt
[root@master1 overlay]# ls -l upper/
总用量 4
c--------- 1 root root 0, 0 8月 15 16:38 1.txt
-rw-r--r-- 1 root root 8 8月 15 16:28 2.txt
[root@master1 overlay]#
[root@master1 overlay]# cat upper/1.txt
cat: upper/1.txt: 没有那个设备或地址
此时我们删除upper里面的数据,我们发现merged里面的数据回来了,且跟我们low里面的保持一致
[root@master1 overlay]# rm -rf upper/1.txt
[root@master1 overlay]# cat merged/1.txt
aaaaaaa
[root@master1 overlay]#
总结:
- upper目录是容器的可读写层。任何对容器的改变都写在这个目录中。
- merged目录就是容器的mount point,这就是暴露的镜像(lowerdir)和容器(upperdir)的统一视图。任何对容器的改变也影响这个目录。
- work目录是OverlayFS功能需要的,会被如copy_up之类的操作使用。
对于如何修改docker的存储驱动:
/etc/docker/daemon.json这个文件中增加"storage-driver": "overlay2",然后systemdctl daemon-reload,systemctl restart docker即可。
关于docker存储方式的选型建议,请参考https://www.cnblogs.com/fengjian2016/p/6638205.html
自我感觉这张照片拍的听好的啊,哈哈
参考资料:《docker practice》