Docker-06-数据卷技术

Docker-06-数据卷技术

mark

1. 什么是数据卷?

数据卷是一个可供容器使用的特殊目录,它绕过文件系统,可以提供很多有用的特性:

  1. 数据卷可以在容器之间共享和重用。
  2. 对数据卷的更改会立即生效。
  3. 对数据卷的更新不会影响镜像。
  4. 数据卷会一直存在,直到没有容器使用。

数据卷的使用,类似于 linux 下对目录或文件进行 mount 操作。

2. 使用数据卷的场景

  • 在多个容器之间共享数据,多个容器可以同时以只读或者读写的方式挂载同一个数据卷,从而共享数据卷中的数据。

  • 当宿主机不能保证一定存在某个目录或一些固定路径的文件时,使用数据卷可以规避这种限制带来的问题。

  • 当你想把容器中的数据存储在宿主机之外的地方时,比如远程主机上或云存储上。

  • 当你需要把容器数据在不同的宿主机之间备份、恢复或迁移时,数据卷是很好的选择。

3. 数据卷原理

下图描述了 docker 容器挂载数据的三种方式:

mark

docker数据卷的本质是容器中一个特殊目录。在容器创建过程中,docker会将宿主机上的指定目录(一个以数据卷ID为名称的目录)挂在到容器指定的目录上。这里使用的挂载方式是(bind mount),所以挂载完成后的宿主机目录和目标目录表现一致。

比如我们执行下面的命令创建数据卷 hello,并挂载到容器 testcon 的 /world 目录:

1
2
$ docker volume create hello
$ docker run -id --name testcon --mount type=volume,source=hello,target=/world ubuntu /bin/bash

实际上在容器的创建过程中,类似于在容器中执行了下面的代码:

1
2
// 将数据卷 hello 在宿主机上的目录绑定挂载到 rootfs 中指定的挂载点 /world 上
mount("/var/lib/docker/volumes/hello/_data", "rootfs/world", "none", MS_BIND, NULL)

4. 具名挂载 匿名挂载

1
2
3
4
5
6
7
8
# 如何确定是具名挂载还是匿名挂载,还是指定路径挂载
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v /宿主机路径:容器内路径 # 指定路径挂载

# ro read-only
# rw read-write
# 如果设置了ro,说明只能通过外部宿主机改变容器内操作,容器内部无法操作。

5. 数据卷的创建,挂载

  1. 创建随机名字的volume,并挂载到容器的/data目录(匿名挂载)
1
docker  run  -it  -d --name u1  -v /data  ubuntu /bin/bash

说明: 在用 docker run 命令的时候,使用 -v 标记可以在容器内创建一个数据卷。多次使用 -v 标记可以创建多个数据卷

  1. 创建命名的数据卷并挂载(具名挂载)
1
2
3
4
5
6
7
8
法1:
docker volume create u1_vol #创建
docker run -it -d --name u1 -v u1_vol:/data ubuntu #将数据卷u1_vol挂载到容器的/data目录


法2:
直接下面这条命令会创建命名数据卷后挂载。
docker run -it -d --name u1 -v u1_vol:/data
  1. 将宿主机目录 挂载到容器(指定路径挂载)
1
docker run  -it -d  --name u1  -v /tmp:/opt  ubuntu  /bin/bash
  1. 将单个文件作为volume挂载到容器中.
1
2
#将当前目录下的t.txt 文件挂载为 容器的/opt/t.txt
docker run -it -d --name u1 -v $(pwd)/t.txt:/opt/t.txt ubuntu /bin/bash
  1. 挂载数据卷为只读 ro
1
docker run -it -d --name u1 -v u1_vol:/data:ro  ubuntu
  1. 使用Dockerfile 添加volume
1
2
3
4
#使用VOLUME 指令向容器添加volume,与上面(1)相同
VOLUME /data

VOLUME ["/data1","/data2"] #添加多个

在使用 docker build 命令生成镜像并且以该镜像启动容器时会挂载一个数据卷到 /data 目录。根据我们已知的数据覆盖规则,如果镜像中存在 /data 目录,这个目录中的内容将全部被复制到宿主机中对应的目录中,并且根据容器中的文件设置合适的权限和所有者。

注意:

  • VOLUME 指定不能挂载主机中的指定目录。这是为了保证DockerFile的一致性,因为不能保证所有的宿主机都有对应的目录。

  • 在实际的使用中,还有一个陷阱需要大家注意:在Dockerfile使用VOLUME指令之后的代码,如果尝试对这个数据卷进行修改,这些修改都不会生效

下面是一个这样的例子:

1
2
3
4
5
FROM ubuntu
RUN useradd nick
VOLUME /data #VOLUME添加数据卷
RUN touch /data/test.txt #对上面的数据卷修改是不会生效的
RUN chown -R nick:nick /data

通过这个 Dockerfile 创建镜像并启动容器后,该容器中存在用户 nick,并且能够看到 /data 目录挂载的数据卷。但是 /data 目录内并没有文件 test.txt,更别说 test.txt 文件的所有者属性了。要解释这个现象需要我们了解通过 Dockerfile 创建镜像的过程:
Dockerfile 中除了 FROM 指令的每一行都是基于上一行生成的临时镜像运行一个容器,执行一条指令并执行类似 docker commit 的命令得到一个新的镜像。这条类似 docker commit 的命令不会对挂载的数据卷进行保存。
所以上面的 Dockerfile 最后两行执行时,都会在一个临时的容器上挂载 /data,并对这个临时的数据卷进行操作,但是这一行指令执行并提交后,这个临时的数据卷并没有被保存。因而我们最终通过镜像创建的容器所挂载的数据卷是没有被最后两条指令操作过的。我们姑且叫它 “Dockerfile 中数据卷的初始化问题”。

下面的写法可以解决 Dockerfile 中数据卷的初始化问题:

1
2
3
4
5
FROM ubuntu
RUN useradd nick
RUN mkdir /data && touch /data/test.txt
RUN chown -R nick:nick /data
VOLUME /data

通过这个 Dockerfile 创建镜像并启动容器后,数据卷的初始化是符合预期的。这是由于在挂载数据卷时,/data 已经存在,/data 中的文件以及它们的权限和所有者设置会被复制到数据卷中。
还有另外一种方法可以解决 Dockerfile 中数据卷的初始化问题。就是利用 CMD 指令和 ENTRYPOINT 指令的执行特点:与 RUN 指令在镜像构建过程中执行不同,CMD 指令和 ENTRYPOINT 指令是在容器启动时执行。因此使用下面的 Dockerfile 也可以达到对数据卷的初始化目的:

1
2
3
4
FROM ubuntu
RUN useradd nick
VOLUME /data
CMD touch /data/test.txt && chown -R nick:nick /data && /bin/bash

6. 数据的覆盖问题

  • 如果挂载一个空的数据卷到容器的一个非空目录中,那么这个目录下的文件会被复制到数据卷中
  • 如果挂载一个非空的数据卷到容器的一个目录中,那么容器中的目录会显示数据卷的数据。如果原来容器中的目录有数据,那么这些原始数据会被隐藏掉

总结:

灵活利用第一个规则可以帮助我们初始化数据卷中的内容。

掌握第二条规则可以保证挂载数据卷后的数据总是你期望的数据。

7. 共享数据卷与数据卷容器

数据卷的共享有两种方式:

7.1 两个docker之间直接共享数据卷

--volumes-from:相当于继承的概念

1
2
3
4
5
6
7
8
9
10
11
12
13
root@ubuntu:~# docker run  -it -d  --name u1  -v share_vol:/opt   ubuntu:14.04  /bin/bash  #将数据卷share_vol挂载到容器的/opt
7c9bcad41f3d9172699f0add3d6ba65d4a50c6ebac57fc5a042938198896e951
root@ubuntu:~# docker run -it -d --name u2 --volumes-from u1 ubuntu:14.04 /bin/bash #u2 使用u1的数据卷
58a1a4dad1bed5372d9c30d6a8d13644f376426c1fa603de5bab5b52199a6095
root@ubuntu:~# docker exec -it u1 /bin/bash #在容器u1 /opt 中新建文件
root@7c9bcad41f3d:/# cd opt/
root@7c9bcad41f3d:/opt# echo "u1">>1.txt
root@7c9bcad41f3d:/opt# exit
root@ubuntu:~# docker exec -it u2 /bin/bash #容器u2与u1用的是共享数据卷,所以也能看见u1创建的文件,反之.
root@58a1a4dad1be:/# cd opt/
root@58a1a4dad1be:/opt# ls
1.txt
root@58a1a4dad1be:/opt#

7.2 单独创建一个数据容器,其他容器与之共享volume,推荐使用这种方式

--volumes-from:相当于继承的概念

  • 如果用户需要在容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器其实就是一个普通的容器,专门用它提供数据卷供其他容器挂载。

  • 一个容器挂载了一个volume,即使这个容器停止运行,该volume仍然存在,其他容器也可以使用--volumes-from 与这个容器共享卷

  1. 创建数据卷容器,给容器挂载一个 volume后容器停止,好节约资源.
1
docker run --name share_docker  -v share_vol:/data  ubuntu /bin/bash
  1. 然后再其他容器中使用 --volumes-from 来挂在 share_docker 容器中的数据卷
1
2
3
4
5
6
root@ubuntu:~# docker run -it -d --name u1 --volumes-from share_docker  ubuntu /bin/bash
fdc15fca2a7e2545b0751315797cbe8fe868b7fd37c295cff8df8189f3254e5f
root@ubuntu:~# docker run -it -d --name u2 --volumes-from share_docker ubuntu /bin/bash
98e511fb3ef5e6fe3a06ac0b6aa3a97e8881ffaeb48ccb585d3b8cd92b429d1d

(注意,命令中没有指定数据卷的信息,也就是说新容器中挂载数据卷的目录和源容器中是一样的。)

8. 备份,恢复或者迁移 volume

volume作为数据的载体,在很多情况下需要对其中的数据进行备份迁移。

一个很容易想到的办法是使用 docker inspect 查找到volume 在宿主机上对应的文件夹的位置,然后复制其中的内容或者打包。这种做法不推荐,推荐使用 –volumes-from来实现.

8.1 备份

1
2
3
4
5
6
7
root@ubuntu:~# docker run --rm --volumes-from share_docker -v $(pwd):/backup ubuntu tar cvf /backup/data.tar /data
/data/
/data/2.txt
/data/1.txt
tar: Removing leading `/' from member names

说明:share_docker容器包含了我们希望备份的一个volume,上面这行命令启动了另外一个临时容器,这个容器挂载了两个volume,第一个volume来自 share_docker 的共享,也就是需要备份的volume。第二个volume将宿主机的当前目录挂载到临时容器的/backup目录。容器运行后要将备份内容(/data文件夹)备份到容器的/backup/data.tar.然后删除容器,备份后的data.tar就留在了宿主机的当前目录.

8.2 恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
创建需要恢复数据的目标容器:
root@ubuntu:~# docker run -it -d --name back_docker -v back_vol:/data ubuntu /bin/bash
e24b5e898c2016b7ebda93a266a351c6d9a47e6a3f828dac3c8a56cadfc0ce26


启动临时容器用于恢复。
docker run --rm --volumes-from back_docker -v $(pwd):/backup ubuntu tar xvf /backup/data.tar -C /

data/
data/2.txt
data/1.txt
root@ubuntu:~# docker exec -it back_docker /bin/bash #查看发现数据恢复
root@e24b5e898c20:/# cd data/
root@e24b5e898c20:/data# ls
1.txt 2.txt

9. 使用mount语法挂载数据卷

之前我们使用 –volume(-v) 选项来挂载数据卷,现在docker提供了更强大的 –mount 选项来管理数据卷。

mount 选项可以通过逗号分割多个键值对一次提供多个配置项,因此mount 选项可以提供比volume 选项更详细的配置

使用 mount 选项的常用配置如下:

  • type 指定挂载方式,我们这里用到的是 volume,其实还可以有 bind 和 tmpfs。
  • volume-driver 指定挂载数据卷的驱动程序,默认值是 local。
  • source 指定挂载的源,对于一个命名的数据卷,这里应该指定这个数据卷的名称。在使用时可以写 source,也可以简写为 src。
  • destination 指定挂载的数据在容器中的路径。在使用时可以写 destination,也可以简写为 dst 或 target。
  • readonly 指定挂载的数据为只读。
  • volume-opt 可以指定多次,用来提高更多的 mount 相关的配置。

下面我们来看具体的例子:

1
2
$ docker volume create hello
$ docker run -id --mount type=volume,source=hello,target=/world ubuntu /bin/bash

我们创建了名称为 hello 的数据卷,然后把它挂在到容器中的 /world 目录。通过 inspect 命令查看容器的详情中的 “Mounts” 信息可以验证实际的数据卷挂载结果.

  • 使用volume driver 把数据存储到别的地方(远程)

除了默认的把数据卷中的数据存储在宿主机,docker 还允许我们通过指定 volume driver 的方式把数据卷中的数据存储在其它的地方,比如 Azrue Storge 或 AWS 的 S3。
简单起见,我们接下来的 demo 演示如何通过 vieux/sshfs 驱动把数据卷的存储在其它的主机上。

  1. docker 默认是不安装 vieux/sshfs 插件的,我们可以通过下面的命令进行安装:
1
docker plugin install --grant-all-permissions vieux/sshfs
  1. 然后通过 vieux/sshfs 驱动创建数据卷,并指定远程主机的登录用户名、密码和数据存放目录:
1
2
3
4
docker volume create --driver vieux/sshfs \
-o sshcmd=nick@39.106.171.56:/home/nick/sshvolume \
-o password=yourpassword \
mysshvolume

注意,请确保你指定的远程主机上的挂载点目录是存在的(demo 中是 /home/nick/sshvolume 目录),否则在启动容器时会报错。

  1. 最后在启动容器的时候指定挂载这个数据卷:
1
2
3
4
docker run -id \
--name testcon \
--mount type=volume,volume-driver=vieux/sshfs,source=mysshvolume,target=/world \
ubuntu /bin/bash

你在容器中 /world 目录下操作的文件都存储在远程主机的 /home/nick/sshvolume 目录中。进入容器testcon然后在 /world 目录下创建一个文件,然后打开远程主机/home/nick/sshvolume 目录进行查看,你新建的文件看看是不是已经在那里了!

  • docker volume 使用NFS存储
1
2
3
4
5
6
7
8
#NFS服务端,配置nfs共享
yum install nfs-utils rpcbind -y
mkdir -p /data/nfs/docker
echo "/data/nfs *(rw,no_root_squash,sync)">>/etc/exports
exportfs -r
systemctl start rpcbind nfs-server
systemctl enable rpcbind nfs-server
showmount -e localhost
1
2
 #nfs客户端
yum install -y nfs-utils rpcbind
1
2
3
4
5
6
 #创建volume 连接 39.106.171.56:/data/nfs
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=39.106.171.56,rw \
--opt device=:/data/nfs \
volume-nfs
1
2
3
 #查看
docker volume ls
docker volume inspect volume-nfs
1
2
 #容器使用volume-nfs
docker run -dit --name busybox7 -v volume-nfs:/nfs busybox
1
2
3
4
 #查看
docker inspect -f {{.Mounts}} busybox7
df -h |grep /data/nfs
#volume目录/var/lib/docker/volumes/volume-nfs/_data自动挂载到了nfs服务上
1
2
 #容器创文件测试
docker exec busybox7 touch /nfs/testfiles.txt
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信