最新消息:比度技术-是关注云计算、大数据、分布式存储、高并发、高性能、人工智能等互联网技术的个人博客。

Docker 中管理数据-Docker Volume

云计算 bidu 1170浏览

Docker 中管理数据-Docker Volume

了解什么是Docker Volume,首先我们需要明确Docker内的文件系统是如何工作的。 Docker镜像被存储在一系列的只读层。当我们开启一个容器,Docker读取 只读镜像并添加一个读写层在顶部。如果正在运行的容器修改了现有的文件,该文件将被拷贝出底层的只读层到最顶层的读写层。在读写层中的旧版本文件隐藏于该 文件之下,但并没有被不破坏 – 它仍然存在于镜像以下。当Docker的容器被删除,然后重新启动镜像时,将开启一个没有任何更改的新的容器 – 这些更改会丢失。此只读层及在顶部的读写层的组合被Docker称为 Union File System(联合文件系统)。 为了能够保存(持久)数据以及共享容器间的数据,Docker提出了Volumes的概念。很简单,volumes是目录(或者文件),它们是外部默认的联合文件系统或者是存在于宿主文件系统正常的目录和文件。 一个 data volume 就是一个在一个或者多个container里的特殊用途的目录。它绕过了 Union File System ,为持久化数据、共享数据提供了下面这一些有用的特性

1、添加数据卷

(1)你可以在docker run 的时候使用 -v 来添加一个 data volume。这个参数在docker run 的时候可以多次使用来添加多个 data volumes。让我们为我们的web application container挂载一个 volume。

  • 注意: 你也可以在Dockerfile里添加 VOLUME 字段,这样在创建一个新的image的 container是就会自动的创建新的volume
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash 
root@CONTAINER:/# ls /data 
root@CONTAINER:/# 

这将在容器内创建路径/data,它存在于联合文件系统外部并可以在主机上直接访问。任何在该镜像/data路径的文件将被复制到volume。我们可以使用docker inspect命令找出Volume在主机存储的地方:

$ docker inspect -f {{.Volumes}} container-test

你会看到以下类似内容:

map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f…b32cec92e79059437a9]

这说明Docker把在/var/lib/docker下的某个目录挂载到了容器内的/data目录下。让我们从主机上添加文件到此文件夹下:

$ sudo touch /var/lib/docker/vfs/dir/cde167197ccc3e13814f…b32ce9059437a9/test-file

进入我们的容器内可以看到:

$ root@CONTAINER:/# ls /data test-file

改变会立即生效只要将主机的目录挂载到容器的目录上。我们可以在Dockerfile中通过使用VOLUME指令来达到相同的效果:

FROM debian:wheezy
VOLUME /data 

(2)安装一个目录作为数据卷

使用 -v 不仅能创建一个新的 volume, 还可以把宿主机一个目录mount到container里。

$ sudo docker run -d -P –name web -v /src/webapp:/opt/webapp training/webapp python app.py

这条命令会把本地目录 /src/webapp mount到container里的 /opt/webapp 目录上。用这个方法来测试程序非常方便, 比如我们可以把我们的源代码通过这个方法mount到container里, 修改本地代码后立即就可以看到修改后的代码是如何在container里工作的了。宿主机的目录必须是绝对路径, 如果这个目录不存在docker会为你自动创建。

  • 注意 这里是没法用 Dockerfile实现的, 因为这样的用法有悖于可移植性和共享. 因为本地目录就像他名字告诉我们的, 是和本地相关的, 不一定可以在所有的宿主机上工作.(译者: 鬼知道你在使用image的时候的host是啥样子的)
  • Docker默认设置volume是可读写的,但是我们也可以mount一个目录为只读:

    $ sudo docker run -d -P –name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py

这里我们同样mount了 /src/webapp 目录, 但是我们加上了 ro 参数, 告诉docker这个volume是只读的.

(3)数据共享

数据共享:从一个容器访问另一个容器的volumes,我们只用使用-volumes-from参数来执行docker run。

$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash 
root@NEWCONTAINER:/# ls /data 
test-file 
root@NEWCONTAINER:/# 

值得注意的是不管container-test运没运行,它都会起作用。Volume直到容器没有连接到它才会被删除。

使用纯数据容器来持久数据库、配置文件或者数据文件等等是普遍的做法。 官方的文档就讲解的不错。例如: 
$ docker run --name dbdata postgres echo "Data-only container for postgres" 

该命令将会创建一个包含已经在Dockerfile里定义过Volume的postgres镜像,运行echo命令然后退出。
当我们运行docker ps命令时,echo是有用的作为我们识别某镜像的用途。我们可以用-volumes-from命令使用其他容器的Volume: 
$ docker run -d --volumes-from dbdata --name db1 postgres 

使用数据容器两个要点: 
不要不管运行中的数据容器,这是无意义的浪费资源
不要为了数据容器来使用“最小的镜像”如busybox或scratch。只要使用数据库镜像本身就可以了。
如果你已经有了该镜像,那么它并不需要花费额外的空间并且它还允许镜像内的数据来做Volume

(4)备份、恢复和移植数据卷

Volume的另外一个用处就是备份、恢复和migrate数据。 具体的做法如下,使用 --volumes-from 来创建一个新的container mount这个volume
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
这里我们启动了一个新的container, 从 dbdata 挂载了一个volume。同时挂载了一个本地目录到这个container里。
最后我们通过一个 tar命令把 dbdata 里的数据备份到了 /backup 里。命令结束并且停止这个container后我们就在本地得到了一个备份的数据.
(译者: 这里使用的 ubuntu container, 就是为了把volume中的数据打包备份到host的某一个目录里。)
备份的数据可以恢复到这个container, 或者其他使用这个volume的container。首先创建一个container
$ sudo docker run -v /dbdata --name dbdata2 ubuntu
之后un-tar备份文件到 data volume 里
$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
你可以使用你喜欢的工具加上上面的技术来自动备份,迁移和恢复数据

(5)权限与属主设置

通常你需要设置Volume的权限或者为Volume初始化一些默认数据或者配置文件。要注意的关键点是,
在Dockerfile的VOLUME指令后的任何东西将不能改变该volume,比如: 
FROM debian:wheezy RUN useradd foo 
VOLUME /data 
RUN touch /data/x 
RUN chown -R foo:foo /data 

该Docker file预期所料将不会工作,我们希望touch命令在镜像的文件系统上运行,但是实际上它是在一个临时容器的Volume上运行。如下所示: 
FROM debian:wheezy RUN useradd foo 
RUN mkdir /data && touch /data/x 
RUN chown -R foo:foo /data 
VOLUME /data 

上面要注意VOLUME一定要放在最后,然后要先建立目录,再写个文件,文件随意。

Docker是足够聪明的复制存在挂载于镜像Volume下的文件到Volume下,并正确地设置权限。
如果您指定Volume的主机目录(使主机文件不小心被覆盖)将不会出现这种情况。 
如果你能设置权限在RUN指令,那么你将不得不在容器创建后使用CMD或ENTRYPOINT脚本来执行。 

以上内容来源参考:

http://www.open-open.com/lib/view/open1420611970109.html

http://www.oschina.net/translate/dockervolumes

(6)删除Volume

该功能比大多数人意识到的可能更微妙一些。如果你已经使用docker rm来删除你的容器,可能有很多的孤立的Volumes在占用着那些空间。
Volume只有在下列情况下才能被删除:
该容器可以用docker rm -v来删除且没有其他容器连接到该Volume(以及主机目录是也没被指定为Volume)。注意,-v是必不可少的。
该-rm标志被提供给docker run的
除非你已经很小心的,总是像这样来运行容器,否则你将会在/var/lib/docker/vfs/dir目录下得到一些僵尸文件和目录,并且还不容易说出他们到底代表什么。

给一个正在运行的Docker容器动态添加Volume

来源:http://www.open-open.com/lib/view/open1421996521062.html
之前有人问我Docker容器启动之后还能否再挂载卷,考虑到mnt命名空间的工作原理,我一开始认为这很难实现。不过现在Petazzoni通过使用nsenter和绑定挂载实现了这个需求,你可以在你的环境中测试下。

之前有人问我Docker容器启动之后还能否再挂载卷,考虑mnt命名空间的工作原理,我一开始认为这很难实现。不过现在我认为是它实现的。

简单来说,要想将磁盘卷挂载到正在运行的容器上,我们需要:
使用nsenter将包含这个磁盘卷的整个文件系统mount到临时挂载点上;
从我们想当作磁盘卷使用的特定文件夹中创建绑定挂载(bind mount)到这个磁盘卷的位置;
umount第一步创建的临时挂载点。

注意事项
在下面的示例中,我故意包含了$符号来表示这是Shell命令行提示符,以帮助大家区分哪些是你需要输入的,哪些是机器回复的。有一些多行命令,我也继续用>。我知道这样使得例子里的命令无法轻易得被拷贝粘贴。如果你想要拷贝粘贴代码,请查看文章最后的示例脚本。
详细步骤
下面示例的前提是你已经使用如下命令启动了一个简单的名为charlie的容器:
$ docker run --name charlie -ti ubuntu bash

我们需要做的是将宿主文件夹/home/jpetazzo/Work/DOCKER/docker挂载到容器里的/src目录。好了,让我们开始吧。

nsenter

首先,我们需要nsenter以及 docker-enter帮助脚本。为什么?因为我们要从容器中mount文件系统。由于安全性的考虑,容器不允许我们这么做。使用nsenter,我们可以突破上述安全限制,在容器的上下文(严格地说,是命名空间)中运行任意命令。当然,这必须要求拥有Docker宿主机的root权限。
nsenter最简单的安装方式是和docker-enter脚本关联执行:
$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter

更多细节,请查看nsenter项目主页。

找到文件系统

我们想要在容器里挂载包含宿主文件夹(/home/jpetazzo/Work/DOCKER/docker)的文件系统。那我们就需要找出哪个文件系统包含这个目录。

首先,我们需要canonicalize(或者解除引用)文件,以防这是一个符号链接,或者它的路径包含符号链接:
$ readlink --canonicalize /home/jpetazzo/Work/DOCKER/docker
/home/jpetazzo/go/src/github.com/docker/docker

哈,这的确是一个符号链接!让我们将其放入一个环境变量中:
$ HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
$ REALPATH=$(readlink --canonicalize $HOSTPATH)

接下来,我们需要找出哪个文件系统包含这个路径。我们使用一个有点让人意想不到的工具来做,它就是df:
$ df $REALPATH
Filesystem     1K-blocks      Used Available Use% Mounted on
/sda2          245115308 156692700  86157700  65% /home/jpetazzo

使用-P参数(强制使用POSIX格式,以防是exotic df,或者是其他人在Solaris或者BSD系统上装Docker时运行的df),将结果也放到一个变量里:
$ FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

找到文件系统的设备(和sub-root)

现在,系统里已经没有绑定挂载(bind mounts)和BTRFS子卷了,我们仅仅需要查看/proc/mounts,找到对应于/home/jpetazzo文件系统的设备就可以了。但是在我的系统里,/home/jpetazzo是BTRFS池的子卷,要想得到子卷的信息(或者bind mount信息),需要查看/proc/self/moutinfo。

如果你从来没有听说过mountinfo,可以查看内核文档的proc.txt。

首先,得到文件系统设备信息:
$ while read DEV MOUNT JUNK
> do [ $MOUNT = $FILESYS ] && break
> done  do [ $MOUNT = $FILESYS ] && break
> done < /proc/self/mountinfo 
$ echo $SUBROOT
/jpetazzo

很好。现在我们知道需要挂载/dev/sda2。在文件系统内部,进入/jpetazzo,从这里可以得到到所需文件的剩余路径(示例中是/go/src/github.com/docker/docker)。
让我们计算出剩余路径:
$ SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)

注意:这个方法只适用于路径里没有符号“,”的。如果你的路径里有“,”并且想使用本文方法挂载目录,请告诉我。(我需要调用Shell Triad来解决这个问题:jessie,soulshake,tianon?)
在进入容器之前最后需要做的是找到这个块设备的主和次设备号。可以使用stat:
$ stat --format "%t %T" $DEV
8 2

注意这两个数字是十六进制的,我们之后需要的是二进制。可以这么转换:
$ DEVDEC=$(printf "%d %d" $(stat --format "0x%t 0x%T" $DEV))

总结
还有最后一步。因为某些我无法解释的原因,一些文件系统(包括BTRFS)在挂载多次之后会更新/proc/mounts里面的设备字段。
也就是说,如果我们在容器里创建了名为/tmpblkdev的临时块设备,并用其挂载我们自己的文件系统,
那么文件系统(在宿主机器里!)会显示为/tmpblkdev,而不是/dev/sda2。这听起来无所谓,但实际上这会让之后试图得到文件系统块设备的操作都失败。

长话短说,我们想要确保块设备节点在容器里位于和宿主机器上的同一个路径下。

需要这么做:
$ docker-enter charlie -- sh -c \
> "[ -b $DEV ] || mknod --mode 0600 $DEV b $DEVDEC"

创建临时挂载点挂载文件系统:
$ docker-enter charlie -- mkdir /tmpmnt
$ docker-enter charlie -- mount $DEV /tmpmnt

确保卷挂载点存在,bind mount卷:
$ docker-enter charlie -- mkdir -p /src
$ docker-enter charlie -- mount -o bind /tmpmnt/$SUBROOT/$SUBPATH /src

删除临时挂载点:
$ docker-enter charlie -- umount /tmpmnt
$ docker-enter charlie -- rmdir /tmpmnt

(我们并不清除设备节点。一开始就检查设备是否存在可能有点多余,但是现在再检查就已经很复杂了。)

大功告成!

让一切自动化
下面这段可以直接拷贝粘贴了。
#!/bin/sh
set -e
CONTAINER=charlie
HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
CONTPATH=/src

REALPATH=$(readlink --canonicalize $HOSTPATH)
FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

while read DEV MOUNT JUNK
do [ $MOUNT = $FILESYS ] && break 
done  


 

 

$ docker run -v /home/adrian/data:/data debian ls /data
$ docker run -v /data debian ls /data
$ docker run -d –volumes-from dbdata –name db1 postgres

docker run -i -t -v /tmp:/mnt ubuntu/14.04:14.04 /bin/bash

你也可以把一个本地主机的目录当做数据卷挂载在容器上,同样是在docker run后面跟-v参数,不过-v后面跟的不再是单独的目录了,他是[host-dir]:[Container-dir]:[rw|ro]这样格式的,host-dir是一个绝对路径的地址,如果host-dir不存在,则docker会创建一个新的数据卷,如果host-dir存在,但是指向的是一个不存在的目录,则docker也会创建该目录,然后使用该目录做数据源。

实践:

docker -H 172.16.10.219:2376 run -itd -v /testvolumedata –name ubantupcVolume –net ovlnet1 ubuntu:14.04

docker -H 172.16.10.219:2376 run -itd –volumes-from ubantupcVolume –name ubantupcVolumefrom01 –net ovlnet1 ubuntu:14.04

docker -H 172.16.10.219:2376 run -itd -v /dockervolume:/dockertestvolumedata –name ubantupcHostVolume –net ovlnet1 ubuntu:14.04

###Swarm 容器依赖
[root@219Node etcd-v2.3.3-linux-amd64]# docker -H 172.16.10.219:2376 run -itd –volumes-from ubantupcVolume –name ubantupcVolumefrom01 –net ovlnet1 ubuntu:14.04
a48147e6510c7c2e68267eba1369d250eb56b9aac2935d607619dbdc5882d210
[root@219Node etcd-v2.3.3-linux-amd64]# docker -H 172.16.10.219:2376 run -itd –volumes-from ubantupcVolume02 –name ubantupcVolumefrom –net ovlnet1 ubuntu:14.04
docker: Error response from daemon: Conflict: The name ubantupcVolumefrom is already assigned. You have to delete (or rename) that container to be able to assign ubantupcVolumefrom to a container again..
See ‘docker run –help’.
[root@219Node etcd-v2.3.3-linux-amd64]# docker -H 172.16.10.219:2376 run -itd –volumes-from ubantupcVolume –name ubantupcVolumefrom02 –net ovlnet1 ubuntu:14.04
fe8533aa8b8d3e59e8e9d5b6ca1187539abd17603198860a9cc82e7b7c7e7c6e
[root@219Node etcd-v2.3.3-linux-amd64]# docker -H 172.16.10.219:2376 run -itd –volumes-from ubantupcVolume –name ubantupcVolumefrom03 –net ovlnet1 ubuntu:14.04
16f525db64e11b449cf9d17721d6d203845e0dd92685844a5d0218c34c964b0e
[root@219Node etcd-v2.3.3-linux-amd64]#

这些 数据卷容器都被分配到 同一个节点
[root@07Node ~]# docker -H 172.16.10.219:2376 ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
16f525db64e1 ubuntu:14.04 “/bin/bash” 4 seconds ago Up 3 seconds 216Node/ubantupcVolumefrom03
fe8533aa8b8d ubuntu:14.04 “/bin/bash” 9 seconds ago Up 8 seconds 216Node/ubantupcVolumefrom02
a48147e6510c ubuntu:14.04 “/bin/bash” About a minute ago Up About a minute 216Node/ubantupcVolumefrom01
450a35cfc428 ubuntu:14.04 “/bin/bash” 26 minutes ago Up 26 minutes 219Node/ubantupcHostVolume
95f5dfa6c4f5 ubuntu:14.04 “/bin/bash” 29 minutes ago Up 29 minutes 216Node/ubantupcVolumefrom
cfb01466db54 ubuntu:14.04 “/bin/bash” 30 minutes ago Up 30 minutes 216Node/ubantupcVolume
3ed3e876e659 ubuntu:14.04 “/bin/bash” 7 days ago Up 7 days 07Node/ubantupc002
879325902baf ubuntu:14.04 “/bin/bash” 8 days ago Up 8 days 07Node/ubantupc001
7d0c6638509f swarm “/swarm join –addr=1” 11 days ago Up 3 days 2375/tcp 219Node/swarm_agent
15bed519143a swarm “/swarm manage –repl” 11 days ago Up 3 days 172.16.10.219:2376->2375/tcp 219Node/swarm_manager003
808f0b8bb23b swarm “/swarm join –addr=1” 11 days ago Up 10 days 2375/tcp 216Node/swarm_agent
6b690f0deb82 swarm “/swarm manage –repl” 11 days ago Up 10 days 172.16.10.216:2376->2375/tcp 216Node/swarm_manager002
48c8f99a41d9 swarm “/swarm join –addr=1” 11 days ago Up 10 days 2375/tcp 07Node/swarm_agent
969ffe8e92d5 swarm “/swarm manage –repl” 11 days ago Up 10 days 172.16.40.7:2376->2375/tcp 07Node/swarm_manager001
[root@07Node ~]#

转载请注明:比度技术-关注互联网技术的个人博客 » Docker 中管理数据-Docker Volume