当我同时使用主机卷(绑定挂载)和命名卷(一种类型的docker受管卷)时发生了什么?

时间:2019-02-19 05:34:28

标签: docker docker-volume mount-point

在问这个问题之前,我已经阅读了一些文件:

我对以下两个部分感到困惑:

  1. 绑定坐骑:Mounting into a non-empty directory on the container
  2. 命名卷:Populate a volume using a container

他们的行为完全相反:

对于绑定安装:

  

如果您将其绑定安装到容器上的非空目录中,则该目录的现有内容会被绑定安装遮盖。

对于命名卷和匿名卷:

  

如果您如上所述启动一个容器来创建新的卷,并且该容器中有要挂载的目录(例如/ app /)中有文件或目录,则目录的内容将复制到该卷中。然后,容器安装并使用该卷,使用该卷的其他容器也可以访问预先填充的内容。

我的问题是,如果我在dockerfile中使用VOLUME命令来创建命名卷匿名卷,同时使用绑定安装来安装不存在的路径进入容器,幕后发生了什么?

例如:

# in docker file
VOLUME /path/in/container

# when run container
docker run -v /not-exist-dir/in/host:/path/in/container  ... /< image >

我做了一些测试,结果是:

  1. /var/lib/docker/volumes/中没有创建匿名卷
  2. not-exist-dir是在主机上创建的,它现在不为空,我认为Populate a volume using a container已经生效。

所以

  1. 为什么没有按预期创建匿名卷?
  2. 为什么绑定挂载不会掩盖目录的现有内容(在容器端)?

在这种情况下发生了什么?


让我举一个更具体的例子:

这里是dockerfile of the openfrontier/gerrit image,我们可以在docker文件的末尾看到一个VOLUME命令:

VOLUME $GERRIT_SITE

实际上会在主机上创建一个匿名卷,并在从该映像创建任何容器时将其装入容器中的/var/gerrit/review_site(GERRIT_SITE的值):

docker run -dit --name gerrit  openfrontier/gerrit:2.15.3

此后,我可以在/var/lib/docker/volumes/下看到匿名卷,并且可以使用docker volume ls来查看其名称be4538dbf3a51da463391c6eca9714fb6dd0c11379f1e2918f74c33d56633f00,并且还可以使用docker inspect gerrit命令来查看:

"Mounts": [
            {
                "Type": "volume",
                "Name": "be4538dbf3a51da463391c6eca9714fb6dd0c11379f1e2918f74c33d56633f00",
                "Source": "/var/lib/docker/volumes/be4538dbf3a51da463391c6eca9714fb6dd0c11379f1e2918f74c33d56633f00/_data",
                "Destination": "/var/gerrit/review_site",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

在这两个文件夹下都有一些文件,这些文件是容器运行后创建的。

到目前为止,这是使用VOLUME命令创建匿名卷的正常示例。

但是,如果我使用以下命令运行此容器:

docker run -dit --name gerrit -v /home/test:/var/gerrit/review_site openfrontier/gerrit:2.15.3

在主机上不存在/home/test的情况下,则不会创建匿名卷,而是会创建/home/test文件夹并且它不为空!!!,安装信息如下:< / p>

"Mounts": [
            {
                "Type": "bind",
                "Source": "/home/test",
                "Destination": "/var/gerrit/review_site",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

我认为绑定挂载在这里生效,但是我只是不明白为什么主机上的路径不为空(由于将绑定挂载到容器上的非空目录中,该目录的现有内容被绑定遮盖了安装。)

在使用docker run启动容器之前,我还通过在“ / home / test”下放置一些文件来测试将不为空的文件夹安装到容器中,结果是保留了原始文件,而保留了新文件也添加了。

我知道上面的示例不是使用docker卷的好习惯,但是我只是想知道后面发生了什么。

2 个答案:

答案 0 :(得分:1)

Dockerfile中的卷声明在映像上设置了一些元数据,该元数据告诉docker run每当从该映像创建容器时都在该位置定义匿名卷。这不是命名卷或主机挂载,但两者都有很多共同点(默认情况下它们都是绑定挂载,匿名卷与命名卷一起列在docker volume ls中)。

但是,当您将主机装载指定到同一容器目录时,该优先级优先(不会发生两次卷装载到容器内的同一目录)。容器中运行的命令对该目录所做的任何修改在主机上都是可见的,但是该目录本身不会被映像内容初始化。

如果要使用图像内容初始化主机目录,则可以使用执行绑定安装的命名卷。主机挂载在行为上有一些差异。主要是目录必须预先存在,并且在撰写文件中,必须使用绝对路径而不是相对路径。在这里的演示中,我已经找到了该语法:

https://sudo-bmitch.github.io/presentations/dc2018eu/tips-and-tricks-of-the-captains.html#48

答案 1 :(得分:0)

您的问题中有些杂乱无章的事情,并且由于它并不总是直观的,因此我将通过示例来使其更加清晰。

注意事项:

  • 这是我为同事写的一卷小书。解释中肯定有一些缺陷,但示例都经过了检验。
  • 重要的是,您应按以下顺序运行事物,而不要重新运行命令(因为“新事物”可能会变成“现有”事物,并且与它们的目的不符)
  • 我将挂载到某些系统路径上……这仅是出于示例目的:确保它们存在并避免增加预创建命令。

Dockerfile

FROM ubuntu

VOLUME /tmp/dockerfilevolumefromnowhere

RUN mkdir -p /dir/created/from/container
RUN touch /dir/created/from/container/emptyFile.txt
VOLUME /dir/created/from/container

CMD "sh"

让我们删除所有以前的测试数据

请注意,它将删除所有不再使用的卷,包括您以前创建的卷。

如果有任何正在运行的容器(尤其是它们使用卷),请出于测试目的将其停止。

root@host:~# docker container prune 
root@host:~# docker volume prune 
root@host:~# rm -r /does/not/exit/within/host
root@host:~# rm -r /does/not/exit/within/host2
root@host:~# rm -r /tmp/dockerfilevolumefromnowhere

一些图像和体积的创建和填充

root@host:~# docker build -t tmpcontainer .
root@host:~# docker volume create existingVolume
root@host:~# touch /var/lib/docker/volumes/existingVolume/_data/someExistingFile.txt
root@host:~# docker volume create existingVolume2
root@host:~# touch /var/lib/docker/volumes/existingVolume2/_data/anotherExistingFile.txt
root@host:~# docker volume create existingEmptyVolume

这时您有3个“现有卷”:

root@host:~# docker volume ls
DRIVER              VOLUME NAME
local               existingVolume
local               existingVolume2
local               existingEmptyVolume

现在运行我们的容器

docker run -it \
-v /does/not/exit/within/host:/does/not/exist/within/container \
-v /does/not/exit/within/host2:/sbin \
-v /tmp:/again/another/does/not/exist/within/container \
-v /tmp:/tmp/ \
-v newVolume:/another/does/not/exist/within/container \
-v newVolume2:/bin \
-v existingVolume:/new/path/on/container \
-v existingVolume2:/usr \
-v existingEmptyVolume:/var \
bash: groups: command not found # that's normal. you overrided the /usr... see beyond

现在,我们已连接到新创建的容器。

让我们看看容器和主机上都有什么

# -v /does/not/exit/within/host:/does/not/exist/within/container 
#no one exists on both sides : both directories are created, and are now bound to each other
root@host:~# ll /does/not/exit/within/host
. 
root@ffb82b56d64b:/# ll /does/not/exist/within/container
.

# -v /does/not/exit/within/host:/sbin 
# the directory on host is created, and the one on container is erased with this new one. Both are now bound to each other
root@host:~# ll /does/not/exit/within/host2
. 
root@ffb82b56d64b:/# ll /sbin
.


# -v /tmp:/again/another/does/not/exist/within/container
# the path on host exists, and the path on container will be created and will hold the content on container (they are bound to each other)
root@host:~# ll /tmp
    -rw-------  1 root root 65536 Feb 19 11:11 one.txt
    -rw-------  1 root root 65536 Feb 19 11:11 two.yml
root@ffb82b56d64b:/# ll /again/another/does/not/exist/within/container
    -rw-------  1 root root 65536 Feb 19 11:11 one.txt
    -rw-------  1 root root 65536 Feb 19 11:11 two.yml

# -v /tmp:/tmp
# the path on host exists, so all its content will replace the previously path on container
# there were some data on container's /tmp, but they are replaced with hosts ones
root@host:~# ll /tmp
    -rw-------  1 root root 65536 Feb 19 11:11 one.txt
    -rw-------  1 root root 65536 Feb 19 11:11 two.yml
root@ffb82b56d64b:/# ll /tmp
    -rw-------  1 root root 65536 Feb 19 11:11 one.txt
    -rw-------  1 root root 65536 Feb 19 11:11 two.yml

# -v newVolume:/another/does/not/exist/within/container \
# the newVolume does not exist, so it will be created and bound to path on container.  
# since the path on container is new, it will be created empty
root@host:~#  ll /var/lib/docker/volumes/newVolume/_data/
..
root@ffb82b56d64b:/# ll /another/does/not/exist/within/container
..

# -v newVolume2:/bin \
# once again, a volume will be created, but since it matches an existing path on container, it will hold all the content of it (no erasal!)
root@host:~#  ll /var/lib/docker/volumes/newVolume2/_data/
<all the content of Ubuntu's /bin from within container>
root@ffb82b56d64b:/# ll /bin
<whole expected content of /bin on Ubuntu>

# -v existingVolume:/new/path/on/container \
# the volume exists, and it -and all files within- will be bound to a newly created path on container
root@host:~#  ll /var/lib/docker/volumes/existingVolume/_data/
-rw-------  1 root root 65536 Feb 19 11:11 someExistingFile.txt
 root@ffb82b56d64b:/# ll /new/path/on/container
-rw-------  1 root root 65536 Feb 19 11:11 someExistingFile.txt


# -v existingVolume2:/usr \ 
# the volume exists, so does the path on container. It will replace the existing path (and thus erase the former files there) and will be bound to this replaced path.
root@host:~#  ll /var/lib/docker/volumes/existingVolume2/_data/
-rw-------  1 root root 65536 Feb 19 11:11 anotherExistingFile.txt
root@ffb82b56d64b:/# ll /usr    
-rw-------  1 root root 65536 Feb 19 11:11 anotherExistingFile.txt

# -v existingEmptyVolume:/var \ 
# the volume exists, but is empty. the path on container exists in the container and is not empty. In this case, the path on container will not be erased and this will act as a new volume.
root@host:~#  ll /var/lib/docker/volumes/existingEmptyVolume/_data/
drwxr-xr-x  2 root root  4096 Apr 24  2018 backups/
drwxr-xr-x  5 root root  4096 Feb 19 14:36 cache/
drwxr-xr-x  7 root root  4096 Feb 19 14:36 lib/
drwxrwsr-x  2 root staff 4096 Apr 24  2018 local/
lrwxrwxrwx  1 root root     9 Nov 12 21:54 lock -> /run/lock/
drwxr-xr-x  3 root root  4096 Feb 19 14:36 log/
drwxrwsr-x  2 root mail  4096 Nov 12 21:54 mail/
drwxr-xr-x  2 root root  4096 Nov 12 21:54 opt/
lrwxrwxrwx  1 root root     4 Nov 12 21:54 run -> /run/
drwxr-xr-x  2 root root  4096 Feb 19 14:36 spool/
drwxrwxrwt  2 root root  4096 Nov 12 21:56 tmp/
root@ffb82b56d64b:/# ll /var  
drwxr-xr-x  2 root root  4096 Apr 24  2018 backups/
drwxr-xr-x  5 root root  4096 Feb 19 14:36 cache/
drwxr-xr-x  7 root root  4096 Feb 19 14:36 lib/
drwxrwsr-x  2 root staff 4096 Apr 24  2018 local/
lrwxrwxrwx  1 root root     9 Nov 12 21:54 lock -> /run/lock/
drwxr-xr-x  3 root root  4096 Feb 19 14:36 log/
drwxrwsr-x  2 root mail  4096 Nov 12 21:54 mail/
drwxr-xr-x  2 root root  4096 Nov 12 21:54 opt/
lrwxrwxrwx  1 root root     4 Nov 12 21:54 run -> /run/
drwxr-xr-x  2 root root  4096 Feb 19 14:36 spool/
drwxrwxrwt  2 root root  4096 Nov 12 21:56 tmp/

请注意,现在,先前存在的卷和新创建的卷可以用于其他容器(与您在上面使用先前先前存在的卷的方式相同)。

最终,我们剩下的所有涉及用例的内容都是从Dockerfile创建的。 让我们看一下/ var / lib / docker / volumes的内容。请记住,我们在执行测试之前已将其清理干净,因此此处所有这些都与我们的测试相匹配。

root@host:~# ll /var/lib/docker/volumes
drwx------  6 root root  4096 Feb 19 11:11 ./
drwx--x--x 14 root root  4096 Feb 14 09:50 ../
-rw-------  1 root root 65536 Feb 19 14:36 metadata.db #indeed 
drwxr-xr-x  3 root root  4096 Feb 19 14:36 635af95ec06f8a44c22915005189bfb12d5bcf2e5ac97c25112d3e65a72546f4/ # anonymous container 1
drwxr-xr-x  3 root root  4096 Feb 19 14:36 897b28ec17275a3c3be184bb20b6314b38c1404e638080c8fe4fc36cae2f9f65/ # anonymous container 2
drwxr-xr-x  3 root root  4096 Feb 19 14:36 existingEmptyVolume/ #we created it (empty) before the run command
drwxr-xr-x  3 root root  4096 Feb 19 14:35 existingVolume/ #we created it before the run command
drwxr-xr-x  3 root root  4096 Feb 19 14:36 existingVolume2/ #we created it before the run command
drwxr-xr-x  3 root root  4096 Feb 19 14:36 newVolume/ #we created it during run command
drwxr-xr-x  3 root root  4096 Feb 19 14:36 newVolume2/ #we created it during run command

那么那些匿名容器是什么:它们实际上是从您的dockerfile创建的那些容器。 让我们检查一下它们的内容。

root@host:~#  ll /var/lib/docker/volumes/63eeedcb1aa2e4d8785cca409698371381558348ce19bc614d87da372901d224/_data/
-rw-r--r-- 1 root root    0 Feb 19 10:14 emptyFile.txt
root@host:~#  ll /var/lib/docker/volumes/ea0ed5ff271cba03a8b7d35144b58e8da1b2e50b4e05c4cccda7f19b401d7f0b/_data/
..

看,他们持有从dockerfile中放入的所有内容。 它们是匿名的,因此您不能(或实际上不能)在其他容器中使用它们,因为每次从该映像运行容器时都会创建新容器。

注意:您此时可能已经注意到了一些奇怪的事情:

root@host:~# ll /tmp/dockerfilevolumefromnowhere #YES : from HOST!
.. #it exists... but is not linked to the volume or the path in container in any way.

在这里...不要问:仍然让我感到困惑的是为什么它是在主机上创建的。我猜这是一个错误,但是这个问题应该在Docker ML上讨论,而不是在这里。 无论如何,您不应该那样做:在其上创建卷之前,先执行mkdir!

那么我们可以从所有这些示例中看到什么:

  • 从主机 path 绑定/挂载到容器(/ some / path / on / host:/ a / path / on / container)将在容器上创建路径(无论以前是否存在) ),并在此处替换所有以前的数据。这两个目录(及其内容)现在已相互绑定。
  • volume (someVolumeName:/ a / path / on / container)安装到container,会将卷绑定到container上的路径。
  • 现有非空卷安装到容器的路径中,会将容器中的(可能)内容替换为现有卷中的内容(如果为空,则将作为新卷)
  • 新卷安装到容器的路径会将容器中路径的内容与该卷绑定。该卷现在将保留此内容(例如,供以后使用):此处不会擦除/替换任何内容。
  • 如果不存在,则创建卷或路径(无论是在容器还是主机上)。
  • 每次您从该映像运行另一个容器时,
  • Dockerfile中的卷将是匿名的并重新创建(以前的容器将保留)。它们也将在/ var / lib / docker / volumes下创建。
  • 如果以前不存在
  • 在运行时声明的卷,则将创建它们(即为“新”卷)。否则,将使用现有的代替。
  • 通过运行命令创建的
  • 将保存在/ var / lib / docker / volumes与其名称相匹配的目录下。

注意:在这里我不是在谈论所有权和权利。故意:这是另一件事,我们稍后会讨论。