Docker卷管理
卷管理在实际应用中扮演着重要的角色,可以说任何应用只要产生数据,就会用到卷管理。
- 如何在容器内创建数据卷,并且把本地的目录或文件挂载到容器内的数据卷中。
- 如何使用数据卷容器在容器和主机、容器和容器之间共享数据,并实现数据的备份和恢复。
为什么需要数据卷
我们知道,Docker容器里产生的数据,如果不通过docker commit生成新的镜像,使数据作为镜像的一部分保存下来,就会在容器删除后丢失。 为了能够持久化保存和共享容器的数据,Docker提出了卷(volume)的概念。简单来讲,卷就是目录或文件,由Docker daemon挂载到容器中,因此不属于联合文件系统,卷中的数据在容器被删除后仍然可以访问。 Docker提供了两种管理数据的方式:数据卷和数据卷容器。
- 数据卷(Data Volumes):容器内数据直接映射到本地主机环境;
- 数据卷容器(Data Volume Containers):使用特定容器维护数据卷。
Docker卷管理基础
增加新数据卷
用户可以在执行docker create或docker run命令时使用-v参数来添加数据卷,也可以通过多次指定该参数来挂载多个数据卷。这里以创建busybox容器为例:
$ docker run -d -v /tmp/data --name busyboxtest busybox
其中,-v参数会在容器的/tmp/data目录下创建一个新的数据卷。用户可以通过docker inspect命令查看数据卷在主机中的位置:
$ docker inspect busyboxtest…
"Volumes": {
"/tmp/data":
"/var/lib/docker/volumes/08c5670c709f53dd10313e4309a4bb19e883102c0ed99207c4833eb
1d1abc8e1/_data"
},
"VolumesRW": {
"/tmp/data": true
},…
从返回结果中可以看到,/tmp/data数据卷对应的主机目录位置为:
/var/lib/docker/volumes/08c5670c709f53dd10313e4309a4bb19e883102c0ed99207c4833eb1d1abc8e1/_data
将主机目录挂载为数据卷
-v参数除了可以用于创建数据卷外,还可以用来将Docker daemon所在主机上的文件或文件夹挂载到容器中,比如:
$ docker run -d -v /host/data:/data --name busyboxtest busybox
上述命令可以将Docker daemon所在主机的/host/data目录挂载到容器的/data路径下。将主机目录挂载为数据卷的功能在有些场景下非常有用。 比如,我们把程序的编译环境打包在一个Docker镜像里,当需要编译修改过的源码时,只需要将源码目录作为数据卷挂载到Docker容器里即可。-v参数的主机目录必须使用绝对路径,如果指定路径不存在,Docker会自动创建该目录。
另外,还可以以只读的方式挂载一个数据卷,如下:
$ docker run -ti -v /host/data:/data:ro --name busyboxtest busybox
root@6ec2dc54a379:/ # echo abc > /data/id
/bin/sh: can't create /data/id: Read-only file system
如果容器中/data路径已经存在,Docker会使用/host/data的内容覆盖该目录,与mount命令的行为一致。
创建数据卷容器
如果用户需要在容器之间共享一些需要永久存储的数据,或者想要使用一个临时容器中的相关数据,可以创建一个数据卷容器,然后使用该容器进行数据共享。
例如想要创建多个Postgres数据库,并且希望这些数据库之间共享数据,可以先创建一个数据卷容器,该容器中并不运行任何应用:
$ docker create -v /dbdata --name dbdata training/postgres /bin/true
然后启动Postgres数据库服务,使用–volumes-from参数将上面生成的数据卷挂载进来,之后启动多个容器,各个容器之间就可以通过dbdata数据卷共享数据了:
$ docker run -d --volumes-from dbdata --name db1 training/postgres
$ docker run -d --volumes-from dbdata --name db2 training/postgres
也可以使用–volume-from db1或–volume-from db2的方式挂载dbdata数据卷:
$ docker run -d --name db3 --volumes-from db1 training/postgres
使用数据卷容器存储的数据不会轻易丢失,即便删除db1、db2容器甚至是初始化该数据卷的dbdata容器,该数据卷也不会被删除。只有在删除最后一个使用该数据卷的容器时显式地指定docker rm–v$CONTAINER才会删除该数据卷。
数据卷的备份、转储和迁移
使用数据卷的方式管理容器数据时,可以很方便地对其中的数据进行备份、转储和迁移。可以使用如下命令将数据卷中的数据打包,并将打包后的文件拷贝到主机当前目录中:
$ docker run –-rm --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf
/backup/backup.tar /dbdata
上述命令创建了一个容器,该容器挂载了dbdata数据卷,并将主机的当前目录挂载到了容器的/backup目录中;然后在容器中使用tar命令将dbdata数据卷中的内容打包存放到/backup目录的backup.tar文件中。待容器执行结束后,备份文件就会出现在主机的当前目录。之后可以将该备份文件恢复到当前容器或新创建的容器中,完成数据的备份和迁移工作。
Docker卷管理的问题
虽然Docker可以很灵活地将服务器上的目录挂载到容器中,但是Docker在卷管理上还有些不尽如人意的地方:
- 只支持本地数据卷。Docker没有办法把远程服务器的数据卷挂载到本机来,虽然可以手工将NFS挂载到本地,但那也需要自己做大量的工作,不方便集中部署。对于这个问题,Docker引入了卷插件机制,允许使用者以第三方插件的形式,提供对分布式存储的支持。
- 缺乏对数据卷生命周期的有效管理。Docker没有办法对数据卷进行系统管理。例如,无法使用Docker命令查看当前系统的所有数据卷。再例如,之前说过只有当使用docker rm删除最后一个使用数据卷的容器时显式加上-v参数,才会删除数据卷,否则数据卷永不会被删除,并且Docker将再也无法管理该数据卷。而这就会造成一些问题,比如久而久之这些悬挂(dangling)数据卷将浪费大量的磁盘空间。
例如以创建的busyboxtest容器为例,若删除该容器时不添加-v参数。
$ docker rm busyboxtest
我们发现该容器关联的数据卷仍然存在,这就是悬挂数据卷。
$ ls /var/lib/docker/volumes 08c5670c709f53dd10313e4309a4bb19e883102c0ed99207c4833eb1d1abc8e1
对于这个问题,Docker社区正在开发docker volume命令,通过该命令可支持比较完善的卷管理。另一方面,第三方卷插件也可以提供功能更丰富更完善的卷管理器。
\(\)\(\)\(\)\(\)\(\)\(\)\(\)\(\)\(\)$4
长风破浪会有时,直挂云帆济沧海。——李白《行路难》
Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷(Data Volume)。类似我们平时使用的移动硬盘。数据卷可以用来存储Docker应用的数据,也可以用来在Docker容器间进行数据共享。数据卷呈现给Docker容器的形式就是一个目录,支持多个容器间共享,修改也不会影响镜像。使用Docker的数据卷,类似在系统中使用 mount 挂载一个文件系统。
为什么需要数据卷
这得从 docker 容器的文件系统说起。出于效率等一系列原因,docker 容器的文件系统在宿主机上存在的方式很复杂,这会带来下面几个问题:
- 不能在宿主机上很方便地访问容器中的文件。
- 无法在多个容器之间共享数据。
- 当容器删除时,容器中产生的数据将丢失。 为了解决这些问题,docker 引入了数据卷(volume) 机制。数据卷是存在于一个或多个容器中的特定文件或文件夹,这个文件或文件夹以独立于 docker 文件系统的形式存在于宿主机中。数据卷的最大特定是:其生存周期独立于容器的生存周期。
Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来, 那么当容器删除后,数据自然也就没有了。
容器删除后,里面的数据将一同被删除,因此需要将容器内经常变动的数据存储在容器之外,这样删除容器之后数据依然存在。为了能保存数据在docker中我们使用数据卷。
用户在使用 Docker 的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多个容器之间进行数据的共享,这必然涉及容器的数据管理操作。容器中管理数据主要有两种方式:数据卷(Data Volumes),数据卷容器(Data Volume Containers)。
使用数据卷的最佳场景
数据卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性: 数据卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷
- 在多个容器之间共享数据,多个容器可以同时以只读或者读写的方式挂载同一个数据卷,从而共享数据卷中的数据。
- 当宿主机不能保证一定存在某个目录或一些固定路径的文件时,使用数据卷可以规避这种限制带来的问题。
- 当你想把容器中的数据存储在宿主机之外的地方时,比如远程主机上或云存储上。
- 当你需要把容器数据在不同的宿主机之间备份、恢复或迁移时,数据卷是很好的选择。
Docker 提供三种方式将数据从宿主机挂载到容器中:
- volumes:由 Docker 管理的数据卷,是宿主机文件系统的一部分(/var/lib/docker/volumes)。是保存数据的最佳方式。
- bind mounts:将宿主机上的文件或目录挂载到容器中,通常在容器需要使用宿主机上的目录或文件时使用,比如搜集宿主机的信息、挂载宿主机上的 maven 仓库等。
- tmpfs:挂载存储在主机系统的内存中,而不会写入主机的文件系统。如果不希望将数据持久存储在任何位置,可以使用 tmpfs,同时避免写入容器可写层提高性能。这种方式使用比较少。
数据卷
数据卷是一个可供容器使用的特殊目录,它绕过文件系统,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用。
- 对数据卷的更改会立即生效。
- 对数据卷的更新不会影响镜像。
- 数据卷会一直存在,直到没有容器使用。 数据卷的使用,类似于 linux 下对目录或文件进行 mount 操作。
docker volume 子命令
docker 专门提供了 volume 子命令来操作数据卷:
创建数据卷:docker volume create
列出所有的数据卷:docker volume ls
显示数据卷的详细信息:docker volume inspect
删除所有未使用的 volumes,并且有 -f 选项:docker volume prune
删除一个或多个未使用的 volumes,并且有 -f 选项:docker volume rm
在容器内创建一个数据卷
在用 docker run 命令的时候,使用 -v 标记可以在容器内创建一个数据卷。多次使用 -v 标记可以创建多个数据卷。
下面的例子中我们使用 nginx:latest 镜像创建一个 web 容器,并创建一个数据卷挂载到容器的 /data 目录。
docker run -id --name ubuntu1 -v /data ubuntu /bin/bash
挂载一个主机目录作为数据卷
使用 -v 标记也可以指定挂载一个本地的已有目录到容器中去作为数据卷:
docker run -id --name ubuntu2 -v /var/data:/data ubuntu /bin/bash
上面的命令挂载主机的 /var/data 目录到容器的 /data 目录。
这个功能在测试的时候特别方便,比如用户可以放置一些程序或数据到本地目录中,然后在容器中使用。另外,本地目录的路径必须是绝对路径,如果目录不存在,Docker 会自动创建。
Docker 挂载数据卷的默认权限是可读写(rw),用户也可以通过 ro 标记指定为只读:
docker run -id --name ubuntu2 -v /var/data:/data:ro ubuntu /bin/bash
加了 :ro 之后,容器内挂载的数据卷内的数据就变成只读的了。
挂载一个本地主机文件作为数据卷
-v 标记也可以挂载一个主机中的文件到容器中作为数据卷,但是这样做会带来一些问题。建议还是挂载文件所在的目录。
数据卷容器
如果用户需要在容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器其实就是一个普通的容器,专门用它提供数据卷供其他容器挂载。下面简单介绍其使用方法。
首先要创建一个数据卷容器 mydata,并在其中创建一个数据卷挂载到 /data 目录。
docker run -it -v /data –-name mydata ubuntu
然后在其他容器中使用 –volumes-from 来挂载 mydata 容器中的数据卷。例如创建两个容器 mycon1 和 mycon2,并从 mydata 容器挂载数据卷:
docker run -it --volumes-from mydata –-name mycon1 ubuntu
docker run -it --volumes-from mydata –-name mycon2 ubuntu
(注意,命令中没有指定数据卷的信息,也就是说新容器中挂载数据卷的目录和源容器中是一样的。)
此时容器 mycon1 和 mycon2 都挂载同一个数据卷到相同的目录 /data。三个容器任何一个在该目录下写入数据其他容器都能看到。
可以多次使用 –volumes-from 参数来从多个容器挂载多个数据卷。还可以从其他已经挂载了容器的容器来挂载数据卷。并且使用 –volumes-from 参数所挂载数据卷的容器自身并不需要保持在运行状态。
但删除挂载了数据卷的容器时,数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式的使用 docker rm -v 命令来指定同时删除关联的容器。
使用数据卷容器可以让用户在容器之间自由的升级和移动数据卷
利用数据卷容器迁移数据
可以利用数据卷容器对其中的数据卷进行备份、恢复,以实现数据的迁移。
备份
使用下面的命令来备份 mydata 数据卷容器内的数据卷:
docker run --volumes-from mydata -v $(pwd):/backup –-name worker ubuntu tar cvf /backup/backup.tar /data
这个命令首先利用 Ubuntu 镜像创建了一个容器 worker。又使用 –volumes-from mydata 参数来让 worker 容器挂载 mydata 容器的数据卷。接下来使用 -v $(pwd):/backup 参数来挂载本地的当前目录到 worker 容器的 /backup 目录。 在 worker 容器启动后,使用了 tar cvf /backup/backup.tar /data 命令来将 /data 下内容备份为容器内的 /backup/backup.tar,即宿主主机的当前目录下的backup.tar。
恢复
如果要恢复数据到一个容器,可以按照下面的操作。首先创建一个带有数据卷的容器 mydata2:
docker run -v /data –-name mydata2 ubuntu /bin/bash```
然后创建另一个新的容器,挂载 mydata2 的数据卷,并使用 tar 解压缩备份文件到所挂载的容器卷中:
$ sudo docker run --volumes-from mydata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
使用 mount 语法挂载数据卷
docker 提供了更强大的 –mount 选项来管理数据卷。mount 选项可以通过逗号分隔的多个键值对一次提供多个配置项,因此 mount 选项可以提供比 volume 选项更详细的配置。
使用 mount 选项的常用配置如下:
type 指定挂载方式,我们这里用到的是 volume,其实还可以有 bind 和 tmpfs。 volume-driver 指定挂载数据卷的驱动程序,默认值是 local。 source指定挂载的源,对于一个命名的数据卷,这里应该指定这个数据卷的名称。在使用时可以写 source,也可以简写为 src。 destination 指定挂载的数据在容器中的路径。在使用时可以写 destination,也可以简写为 dst 或 target。 readonly 指定挂载的数据为只读。 volume-opt 可以指定多次,用来提高更多的 mount 相关的配置。
下面我们看个具体的例子:
docker volume create hello
docker run -id --mount type=volume,source=hello,target=/world ubuntu /bin/bash
我们创建了名称为 hello 的数据卷,然后把它挂在到容器中的 /world 目录。 通过 inspect 命令查看容器的详情中的 “Mounts” 信息可以验证实际的数据卷挂载结果 :
"Mounts": [
{
"Type": "volume",
"Name": "hello",
"Source": "/var/lib/docker/volumes/hello/_data",
"Destination": "/world",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
使用 volume driver 把数据存储到其它地方
除了默认的把数据卷中的数据存储在宿主机,docker 还允许我们通过指定 volume driver 的方式把数据卷中的数据存储在其它的地方,比如 Azrue Storge 或 AWS 的 S3。 简单起见,我们接下来的 demo 演示如何通过 vieux/sshfs 驱动把数据卷的存储在其它的主机上。 docker 默认是不安装 vieux/sshfs 插件的,我们可以通过下面的命令进行安装:
docker plugin install --grant-all-permissions vieux/sshfs
然后通过 vieux/sshfs 驱动创建数据卷,并指定远程主机的登录用户名、密码和数据存放目录:
docker volume create --driver vieux/sshfs \
-o sshcmd=nick@10.32.2.134:/home/nick/sshvolume \
-o password=yourpassword \
mysshvolume
注意,请确保你指定的远程主机上的挂载点目录是存在的(demo 中是 /home/nick/sshvolume 目录),否则在启动容器时会报错。 最后在启动容器时指定挂载这个数据卷:
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 rm **
Volume 只有在下列情况下才能被删除:
- docker rm -v删除容器时添加了-v选项
- docker run –rm运行容器时添加了–rm选项 否则,会在/var/lib/docker/volumes目录中遗留很多不明目录。
数据卷是独立于容器的生命周期的,删除容器是不会删除数据卷的,除非删除数据卷,删除数据卷之后数据也就丢失了。
如果数据卷正在被某个容器使用,将不能被删除,需要先删除使用此数据卷的所有容器之后才能删除数据卷。
数据卷原理
docker 数据卷的本质是容器中的一个特殊目录。在容器创建的过程中,docker 会将宿主机上的指定目录(一个以数据卷 ID 为名称的目录)挂载到容器中指定的目录上。这里使用的挂载方式为绑定挂载(bind mount),所以挂载完成后的宿主机目录和容器内的目标目录表现一致。 比如我们执行下面的命令创建数据卷 hello,并挂载到容器 testcon 的 /world 目录:
$ docker volume create hello
$ docker run -id --name testcon --mount type=volume,source=hello,target=/world ubuntu /bin/bash
实际上在容器的创建过程中,类似于在容器中执行了下面的代码:
// 将数据卷 hello 在宿主机上的目录绑定挂载到 rootfs 中指定的挂载点 /world 上
mount("/var/lib/docker/volumes/hello/_data", "rootfs/world", "none", MS_BIND, NULL)
在处理完所有的 mount 操作之后(真正需要 docker 容器挂载的除了数据卷目录还包括 rootfs,init-layer 里的内容,/proc 设备等),docker 只需要通过 chdir 和 pivot_root 切换进程的根目录到 rootfs 中,这样容器内部进程就只能看见以 rootfs 为根的文件系统以及被 mount 到 rootfs 之下的各项目录了。 例如我们启动的 testcon 中的文件系统为:
docker exec -it 4366e0d8c534 bash
root@4366e0d8c534:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var world
数据的覆盖问题
如果挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中。
如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录中会显示数据卷中的数据。如果原来容器中的目录中有数据,那么这些原始数据会被隐藏掉。
这两个规则都非常重要,灵活利用第一个规则可以帮助我们初始化数据卷中的内容。掌握第二个规则可以保证挂载数据卷后的数据总是你期望的结果。
Docker实现容器数据持久化的两种方法 Bind volume 和 Volume
Docker中的数据,比如Mysql,Redis这些,在容器重启或被删除以后,数据是不会保留的,也就是说数据没有持久化。 在Docker中实现数据持久化有两种方式:
Bind mount
Bind mount 方式是 docker 早期使用的容器与宿主机数据共享的方式,可以实现将宿主机上的文件或目录挂载(mount)到 docker 容器中使用。 相对于 volume 方式,bind mount 方式存在不少的局限。例如,bind mount 在 Linux 和 Windows 操作系统下不可移植。因此 docker 官方推荐使用 volume 方式。 对于bind mount,有几点需要注意。 -v 宿主机目录路径必须以 / 或 ~/ 开头,否则 docker 会将其当成是 volume 而不是 bind mount 如果宿主机上的目录不存在,docker 会自动创建该目录 如果容器中的目录不存在,docker 会自动创建该目录 如果容器中的目录已有内容,那么 docker 会使用宿主机上目录的内容覆盖容器目录的内容
"Mounts": [
{
"Type": "bind",
"Source": "/var/data",
"Destination": "/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
Volume
与 bind mount 不同,volume 由 docker 创建和管理,docker 所有的 volume 都保存在宿主机文件系统的 /var/lib/docker/volumes 目录下(但是macOS 是以虚拟机形式运行 docker的,因此并不存在该目录,可以参考 stackoverflow)。 Docker 引入 volume 的原因有:
- 删除容器时,volume 不会被删除
- 在不同的容器之间共享 volume (存储 / 数据)
- 容器与存储分离
- 将 volume 存储在远程主机或云上
- 可以使用 docker run -v 参数为启动容器加载一个 volume,例如(最新版本镜像默认不用加tag,特定版本要加):
"Mounts": [
{
"Type": "volume",
"Name": "48ddd61409cdec13d9d64fcf3efc4593036740519bc75e0a6535ade0213fd20c",
"Source": "/var/lib/docker/volumes/48ddd61409cdec13d9d64fcf3efc4593036740519bc75e0a6535ade0213fd20c/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],