使用绑定挂载

自Docker诞生以来,绑定挂载就已经存在。 与卷相比,绑定挂载的功能有限。 使用绑定挂载时,主机上的文件或目录将挂载到容器中。 文件或目录由主机上的完整或相对路径引用。 相比之下,当您使用卷时,将在主机上Docker的存储目录中创建一个新目录,并且Docker管理该目录的内容。

该文件或目录不需要在Docker主机上已经存在。 如果尚不存在,则按需创建。 绑定挂载性能非常好,但是它需要主机文件系统具有特定目录结构的。 如果是开发新的Docker应用程序,请考虑使用命名卷。 不能使用Docker CLI命令直接管理绑定挂载。

types-of-mounts-bind.png

选择-v--mount标记

最初,-v--volume标记用于独立容器,而--mount标记用于swarm集群服务。从Docker 17.06开始,您还可以将--mount用于独立容器。通常,--mount更为明确和详细。最大的区别是-v语法在一个字段中将所有选项组合在一起,而--mount语法将它们分开。这是每个标记的语法比较。

提示:新用户应使用--mount语法。有经验的用户可能更熟悉-v--volume语法,但建议使用--mount,因为研究表明它更易于使用。

  • -v--volume:由三个字段组成,以冒号(:)分隔。这些字段必须以正确的顺序排列,并且每个字段的含义不是不是很直观。
    • 对于绑定挂载,第一个字段是主机上文件或目录的路径。
    • 第二个字段是文件或目录在容器中的挂载路径。
    • 第三个字段是可选的,是用逗号分隔选项列表,例如roconsistentdelegatecachedzZ。下面会讨论这些选项。
  • --mount:由多个键/值对组成,用逗号分隔,每对均由一个<key>=<value>元组组成。 --mount语法比-v--volume更长,但是键不用按顺序排列,并且标记的值更易于理解。
    • 挂载的type,可以是bindvolumetmpfs。本主题讨论绑定挂载,因此类型始终为bind
    • 挂载的source。对于绑定挂载,这是Docker后台程序主机上文件或目录的路径。可以指定为sourcesrc
    • destination表示文件或目录在容器中的挂载路径。可以指定为destinationdsttarget
    • readonly选项,如果存在,使绑定挂载以只读方式挂载到容器中。
    • bind-propagation选项(如果存在)将更改绑定传播(本文稍后会详细讲述)。可以是rprivateprivatersharedsharedrslaveslave之一。
    • consistency选项,如果存在,则可以是consistentdelegatedcached之一。此设置仅适用于Docker Desktop for Mac,在所有其他平台上将被忽略。
    • --mount标记不支持用于修改selinux标签的zZ选项。

下面的示例在可能的情况下同时显示--mount-v语法,并且首先显示--mount

-v--mount行为之间的区别

由于-v--volume标记成为Docker的一部分很长一段时间了,因此它们的行为无法更改。这意味着 -v--mount之间存在不同的行为

如果使用-v--volume绑定挂载Docker主机上尚不存在的文件或目录,则-v为您创建端点。始终将其创建为目录

如果使用--mount绑定挂载Docker主机上尚不存在的文件或目录,则Docker不会自动为您创建文件或目录,但会生成错误。

使用绑定挂载启动容器

考虑以下情况:您有source目录,并且在构建源代码时,工件保存到另一个目录source/target/中。您希望工件可以在容器的/app/上可用,并且希望每次在开发主机上构建源代码时,容器都可以访问新的构建。使用以下命令将target/目录绑定挂载到的容器中/app/处。在source目录中运行命令。 $(pwd)子命令将展开为Linux或macOS主机上的当前工作目录。

下面的--mount-v示例产生相同的结果。除非在运行第一个容器后删除devtest容器,否则无法运行另一个命令。

``` $ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,target=/app nginx:latest ```
``` $ docker run -d -it --name devtest -v "$(pwd)"/target:/app nginx:latest ```

使用docker inspect devtest来验证绑定挂载是否正确创建。 查找Mounts部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
],

这表明挂载是bind挂载,显示正确的源和目标,表明挂载是可读写的,并且传播设置为rprivate

停止容器:

$ docker container stop devtest

$ docker container rm devtest

挂载到容器上的非空目录中

如果您绑定挂载到容器上的一个非空目录中,则该目录的现有内容将被绑定挂载隐藏。 这可能是有益的,例如,当您要测试应用程序的新版本而不构建新映像时。 但是,这也可能令人惊讶,并且此行为不同于Docker卷的行为。

本示例有意列举的极端例子,用主机上的/tmp/目录替换了容器的/usr/目录。 在大多数情况下,这将导致容器的功能不正常。

--mount-v示例具有相同的最终结果。

``` $ docker run -d -it --name broken-container --mount type=bind,source=/tmp,target=/usr nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262: starting container process caused "exec: "nginx": executable file not found in $PATH".

``` $ docker run -d -it --name broken-container -v /tmp:/usr nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262: starting container process caused "exec: "nginx": executable file not found in $PATH".

容器已创建但未启动。 删除它:

$ docker container rm broken-container

使用只读绑定挂载

对于某些应用程序开发,容器需要写入绑定挂载,因此更改将传播回Docker主机。 在其他时间,容器仅需要读取访问权限。

此示例修改了上面的示例,但通过在容器中的挂载点之后将ro添加到(默认为空)选项列表中,将目录作为只读绑定挂载进行挂载。 如果有多个选项,请用逗号分隔。

--mount-v示例具有相同的结果。

``` $ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,target=/app,readonly nginx:latest ```
``` $ docker run -d -it --name devtest -v "$(pwd)"/target:/app:ro nginx:latest ```

使用docker inspect devtest来验证绑定挂载是否正确创建。 查找Mounts部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    }
],

停止容器:

$ docker container stop devtest

$ docker container rm devtest

配置绑定传播

对于绑定挂载和卷,绑定传播默认为rprivate。它仅可用于绑定挂载,并且只能在Linux主机上配置。绑定传播是一个高级主题,许多用户不需要配置它。

绑定传播是指是否可以将在给定绑定挂载或命名卷中创建的挂载传播到该挂载的副本。考虑一个挂载点/mnt,它也挂载在/tmp上。传播设置控制/tmp/a上的挂载是否也可以在/mnt/a上使用。每个传播设置都有一个递归对点。在递归的情况下,请考虑将/tmp/a也挂载为/foo。传播设置控制/mnt/a和/或/tmp/a是否存在。

传播设置 说明
shared 原始挂载的子挂载公开给副本挂载,副本挂载的子挂载也传播到原始挂载。
slave 类似于共享挂载,但仅在一个方向上。如果原始挂载公开了子挂载,则副本挂载可以看到它。但是,如果副本挂载公开了子挂载,则原始挂载看不到它。
private 挂载是私有的。其中的子挂载不暴露于副本挂载,副本挂载的子挂载也不暴露于原始挂载。
rshared 与shared相同,但是,在任何原始或副本挂载点中,传播也扩展到嵌套的挂载点以及从这些挂载点扩展。
rslave slave相同,但是,在任何原始或副本挂载点中,传播也扩展到嵌套的挂载点以及从这些挂载点扩展。
rprivate 默认值。与private相同,这意味着原始或副本挂载点内的任何挂载点都不会向任一方向传播。

在可以在挂载点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。

有关绑定传播的更多信息,请参阅Linux内核文档中的共享子树

以下示例两次将target/目录挂载到容器中,第二次挂载同时设置了ro选项和rslave绑定传播选项。

--mount-v示例具有相同的结果。

``` $ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,target=/app --mount type=bind,source="$(pwd)"/target,target=/app2,readonly,bind-propagation=rslave nginx:latest ```
``` $ docker run -d -it --name devtest -v "$(pwd)"/target:/app -v "$(pwd)"/target:/app2:ro,rslave nginx:latest ```

现在,如果您创建/app/foo/,则/app2/foo/也存在。

配置selinux标签

如果使用selinux,则可以添加zZ选项来修改要挂载容器的主机文件或目录的selinux标签。这会影响主机上的文件或目录本身,并可能导致超出Docker范围的后果。

  • z选项指示绑定挂载内容在多个容器之间共享。
  • Z选项表示绑定挂载内容是私有的且未共享。 使用这些选项请格外小心。使用Z选项绑定挂载系统目录,例如/home/usr会使主机无法操作,并且您可能需要手动重新标记主机文件。

重要说明:将绑定挂载与服务一起使用时,将忽略selinux标签(:Z:z)以及:ro。有关详细信息,请参见moby/moby #32579

本示例将z选项设置为指定多个容器可以共享绑定挂载的内容:

不能使用--mount标记来修改selinux标签。

$ docker run -d   -it   --name devtest   -v "$(pwd)"/target:/app:z   nginx:latest

配置macOS的挂载一致性

Docker Desktop for Mac使用osxfs将从macOS共享的目录和文件传播到Linux VM。这种传播使这些目录和文件可用于在Docker Desktop for Mac上运行的Docker容器。

默认情况下,这些共享是完全一致的,这意味着每次在macOS主机上或通过容器中的挂载进行写操作时,所做的更改都会刷新到磁盘上,以便共享中的所有参与者都具有完全一致的视图。在某些情况下,完全一致性可能会严重影响性能。 Docker 17.05及更高版本引入了一些选项,可以针对每个容器,每个容器来调整一致性设置。提供以下选项:

  • consistentdefault:如上所述,具有完全一致性的默认设置。

  • delegated:以容器运行时的挂载视图为准。在主机上看到容器中所做的更新之前可能会有所延迟。

  • cached:以macOS主机的挂载视图为准。在容器中可以看到主机上所做的更新,这可能会有所延迟。

这些选项在除macOS之外的所有主机操作系统上均被完全忽略。

--mount-v示例具有相同的结果。

``` $ docker run -d -it --name devtest --mount type=bind,source="$(pwd)"/target,destination=/app,consistency=cached nginx:latest ```
``` $ docker run -d -it --name devtest -v "$(pwd)"/target:/app:cached nginx:latest ```

下一步