理解 Docker 中的 Volumes
Why
本文翻译自 Understanding Volumes in Docker,和原文作者一样,我在刚接触 Docker 的时候,对 Docker 中 Volumes 的概念也有些困惑,docker run -it -v /some/dir:/another/dir someImage:someTag 中的 -v 选项和 Dockerfile 中定义的 VOLUME 能达到相同的效果吗?看完这篇文章后才豁然开朗,所以翻译过来一方面巩固理解,另一方面分享一下。
译文
从 Docker IRC Channel (Ross:用了一下这里提到的 Docker IRC Channel,顺便看了关于 Python 和 C++ 的聊天室,原来网络上还有一群人在用古老的聊天室,联想到纸牌屋里提到的 Deep Web,果然我们平时能看到的网上资讯只是冰山一角)和 StackOverflow 中经常看到的问题可以很清楚地看出,很多人对 Volumes 在 Docker 中如何工作是很困惑的。在这篇文章中,我将解释 Volumes 如何工作以及使用它的一些 Best Practice。虽然本文定位于对 Docker 感兴趣后者只有一点知识的人群,但是 Docker 的练家子也很可能从中学到一些东西,因为本文讨论了一些很多人不是很了解的关于 Volumes 的话题。
如果要理解什么是 Docker 中的 Volume,我们首先需要搞清楚 Docker 的文件系统是如何工作的。Docker 的 Image 是以一系列 Layers 的形式存储的。当我们启动一个 Container 的时候,Docker 在只读的 Image 的上层添加一个可读写的 Layer 。当一个运行中的 Container 修改了一个已存在的文件时,这个文件会被只读 Layer 中拷贝到最上层的可读写 Layer 中并修改保存,这样可读写 Layer 中的版本会隐藏只读 Layer 中受影响的文件,而不是删除原始文件,原始文件会一直存在于最初的 Image 中。当这个 Docker 的 Container 被删除之后,重新使用这个 Image 创建一个新的 Container 会丢失之前 Container 中进行的所有修改。总之,Docker 会在调用者一系列只读 Layers 的时候在 Union File System 的最上层添加一个可读写的新 Layer。
为了能够保存或者说持久化数据并将数据在不同的 Containers 之间共享,Docker 提出了 Volumes 的概念。非常简单,Volumes 就是独立于默认的 Union File System 并且存在于 host 文件系统中的普通文件夹或者文件。
有两种方式可以创建 Volumes,这两种方式有些细微的差别,需要小心对待。我们可以在新建一个 Container 的时候声明一个 Volume:
$ docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
root@CONTAINER:/# ls /data
root@CONTAINER:/#
这样做会使用 Container 容器中的 /data 目录独立于 Union File System存在而且可以被 host 访问。这个 Image 中 /data 目录下的所有文件都会被拷贝到这个 Volume 里面,我们可以通过在 host 上执行 docker inspect 命令来得到这个 Volume 在 host 上的具体路径。打开一个新的命令行窗口并保持当前 Container 处于运行状态并输入下面的命令:
$ docker inspect -f container-test
我们会看到类似于下面的信息:
map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f7c0d65b32cecbada822b0db4cc92e79059437a9]
这说明 Docker 将 /var/lib/docker 目录下的一个文件夹在这个 Container 中挂载为 /data 目录。现在我们从 host 在这个目录中放入一个文件:
$ sudo touch /var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f7c0d65b32cecbada822b0db4cc92e79059437a9/test-file
然后回到我们之前创建的 Container 中并查看一下 /data 目录:
$ root@CONTAINER:/# ls /data
test-file
上面的修改即时生效了,因为 Container 中的 /data 目录就是 host 上的目录的挂载。
我们可以通过在 Dockerfile 中定义 VOLUME 标签来达到同样的效果:
FROM debian:wheezy
VOLUME /data
但是这里和创建 Docker Container 的时候使用 -v 参数有一个区别,具体地说就是__-v__ 参数可以做到但是 Docker VOLUME 却做不到的就是:我们可以挂载 host 上面的一个特定目录到 Container:
$ docker run -v /home/adrian/data:/data debian ls /data
这个命令会将 host 上面的 /home/adrian/data 目录挂载为 Container 的 /data 目录。所有 /home/adrian/data 中已存在的文件以及文件夹都可以在 Container 中马上访问。这在需要 host 和 Container 共享数据的时候非常有用。举个例子来说,将源代码目录挂载到 Container 中进行编译。host 中用于 Volumes 的目录不能在 Dockerfile 中进行定义,这是为了保证可移植性,因为无法保证所有 host 都有相同的目录。当 -v 参数被使用时,任何 Image 中挂载后的目录中的内容都 不会 被拷贝到这个 VOLUME 中。
共享数据
为了使得一个 Container 可以访问另一个 Container 中的数据,我们可以在执行 docker run 命令的时候使用 –volumes-from 参数:
$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
root@NEWCONTAINER:/# ls /data
test-file
root@NEWCONTAINER:/#
需要注意的是,无论 container-test 这个 Container 是否运行。当一个 Volume 有 Container 与其连接时是不会被删除的。
数据容器
使用一个 data-only 的 Container 来进行持久化数据库、配置文件以及数据文件等已经成为一个通行的做法。Docker website has some good documentation 就这个话题进行了讨论。举例来说:
$ docker run --name dbdata postgres echo "Data-only container for postgres"
这个命令会创建一个包含 Dockerfile 中定义的 Volume 的 Postgres Container,执行 echo 命令并退出。这里 echo 命令对于帮助我们了解 Container 的用途是很有用的。我们可以在创建另一个 Container 的时候来使用这里定义的 Volume:
$ docker run -d --volumes-from dbdata --name db1 postgres
使用数据容器的时候有两点需要特别注意:
- 没有必要让数据容器处于运行状态,运行它纯属浪费资源。
- 不要使用 busybox 或者 scratch 这些最小 Images 用于数据容器,就是用数据库 Image 本身就好。既然我们已经下载了数据库 Image,建立一个 Container 不会增加更多资源占用,而且 Volumes 还可以用来从 Image 的挂载目录中获取一些数据。
权限和所有者
很多时候,我们会需要对一个 Volume 进行权限和所有者控制,后者在初始化 Volume 的时候放入一些默认数据或者配置文件。关键一点在于认识到所有 VOLUME 标签之后对于挂载目录的修改都不会生效,比如:
FROM debian:wheezy
RUN useradd foo
VOLUME /data
RUN touch /data/x
RUN chown -R foo:foo /data
这个 Dockerfile 不会按照我们的预期进行工作。我们期望 touch 命令的结果保存在 Image 的文件系统中,但是其实它只在一个临时的 Container 中执行了而不会被保存。下面的写法才是正确的:
FROM debian:wheezy
RUN useradd foo
RUN mkdir /data && touch /data/x
RUN chown -R foo:foo /data
VOLUME /data
Docker 会智能地从 Image 中挂载后的目录里拷贝所有内容到这个 Volume 下面并赋予合适的权限和所有者。当我们使用 -v 参数将 host 上面的某个目录挂载到 Container 中时,拷贝不会发生,以避免 host 上的内容被 Image 中的内容覆盖。
虽然我们无法使用 RUN 命令进行权限和所有者的设置,但是我们可以使用 CMD 或者 ENTRYPOINT 在 Container 被创建后运行脚本来做这些事情。
删除 Volumes
和很多预想的不同,当我们使用 docker rm 删除 Containers 的时候,我们可能不会将这些 Containers 关联的 Volumes 删除。
Volumes 只有在下面的情况下才会被删除:
- Container 被使用 docker rm -v 命令删除并且没有其他 Containers 于这个 Volume 关联,也没有 host 上的目录和这个 Volume 关联,-v 在这里是必需的。
- -rm 参数和 docker run 是一对。
我们要遵循上面的规则进行 Containers 的管理,否则时间久了就会在 /var/lib/docker/vfs/dir 下产生很多垃圾文件,而且无法分辨哪些可以被安全删除。
更多资料
下面的资源提供了这个话题的更深层讨论并且是本文写作的参考:
最后,关于管理 Volumes,我们有很多工具可以选择:Proposal #8484
声明:本文译文的英文版权属于 原作者,本文转载请注明出处。
声明: 本文采用 BY-NC-SA 协议进行授权. 转载请注明转自: 理解 Docker 中的 Volumes
有疑问加站长微信联系(非本文作者)