了解DockerFile中的"VOLUME"指令

ref*_*tor 94 docker dockerfile

以下是我的"Dockerfile"的内容

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]
Run Code Online (Sandbox Code Playgroud)

在这个文件中,我期待"VOLUME./ usr/src/app"指令将主机中当前工作目录的内容挂载到容器的/ usr/src/app文件夹中.

如果这是正确的方法,请告诉我?

Mar*_*son 213

简而言之:不,你的VOLUME指示不正确.

Dockerfile VOLUME指定给定容器端路径的一个或多个卷.但它不允许图像作者指定主机路径.在主机端,在Docker根目录中创建的卷具有非常长的ID类名称.在我的机器上这是/var/lib/docker/volumes.

注意:因为自动生成的名称非常长并且从人的角度来看没有意义,所以这些卷通常被称为"未命名"或"匿名".

你使用'.'的例子.无论我是否将点作为第一个或第二个参数,角色甚至都不会在我的机器上运行.我收到此错误消息:

docker:来自守护进程的错误响应:oci运行时错误:container_linux.go:265:启动容器进程导致"process_linux.go:368:容器init导致"打开/ dev/ptmx:没有这样的文件或目录\"".

我知道,对于那些试图理解的人而言,对于这一点所说的内容可能并不是很有价值VOLUME,-v而且它肯定不能为你想要完成的事情提供解决方案.因此,希望以下示例能够更好地阐述这些问题.

Minitutorial:指定卷

鉴于此Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2
Run Code Online (Sandbox Code Playgroud)

(对于这个小型教程的结果,如果我们指定vol1 vol2或者/vol1 /vol2- 不要问我原因,它没有任何区别)

建立它:

docker build -t my-openjdk
Run Code Online (Sandbox Code Playgroud)

跑:

docker run --rm -it my-openjdk
Run Code Online (Sandbox Code Playgroud)

在容器内,运行ls,你会发现存在两个目录; /vol1/vol2.

运行容器还会在主机端创建两个目录或"卷".

让容器运行时,docker volume ls主机上执行,你会看到类似的东西(为了简洁起见,我用两个点替换了名称的中间部分):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0
Run Code Online (Sandbox Code Playgroud)

回到容器中,执行touch /vol1/weird-ass-file(在所述位置创建一个空白文件).

此文件现在可在主机上的一个未命名卷lol中使用.我花了两次尝试,因为我第一次尝试了第一个列出的卷,但最终我确实在第二个列出的卷中找到了我的文件,在主机上使用此命令:

sudo ls /var/lib/docker/volumes/f670...49f0/_data
Run Code Online (Sandbox Code Playgroud)

同样,您可以尝试在主机上删除此文件,它也将在容器中删除.

注意:该_data文件夹也称为"安装点".

退出容器并列出主机上的卷.他们走了.我们--rm在运行容器时使用了该标志,这个选项不仅有效地消除了退出时的容器,还消除了卷.

运行新容器,但使用-v以下命令指定卷:

docker run --rm -it -v /vol3 my-openjdk
Run Code Online (Sandbox Code Playgroud)

增加了第三个卷,整个系统最终有三个未命名的卷.如果我们只指定了命令就会崩溃-v vol3.参数必须是容器绝对路径.在主机端,新的第三卷是匿名的,并与其他两个卷一起驻留./var/lib/docker/volumes/

前面已经说过,Dockerfile在运行时尝试将文件从主机引入容器时,无法映射到主机路径,这对我们造成了问题.不同的-v语法解决了这个问题.

想象一下,我的项目目录./src中有一个子文件夹,我希望/src在容器内同步.这个命令可以解决问题:

docker run -it -v $(pwd)/src:/src my-openjdk
Run Code Online (Sandbox Code Playgroud)

:角色的两边都需要绝对的路径.左侧是主机上的绝对路径,右侧是容器内的绝对路径.pwd是一个"打印当前/工作目录"的命令.将命令放入$()括号内的命令,在子shell中运行它并返回到项目目录的绝对路径.

把它们放在一起,假设我们./src/Hello.java在主机上的项目文件夹中有以下内容:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}
Run Code Online (Sandbox Code Playgroud)

我们构建这个Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello
Run Code Online (Sandbox Code Playgroud)

我们运行这个命令:

docker run -v $(pwd)/src:/src my-openjdk
Run Code Online (Sandbox Code Playgroud)

这打印出"Hello,World!".

最好的部分是我们可以完全自由地修改.java文件,并在第二次运行时为另一个输出修改新消息 - 无需重建图像=)

最后的评论

我对Docker很陌生,前面提到的"教程"反映了我从一个为期3天的命令行黑客马拉松收集到的信息.我几乎感到惭愧我无法提供链接来清除支持我的陈述的英文文档,但老实说,我认为这是由于缺乏文档而不是个人努力.我知道这些示例的工作原理是使用我当前的设置,即"Windows 10 - > Vagrant 2.0.0 - > Docker 17.09.0-ce".

本教程没有解决问题"我们如何在Dockerfile中指定容器的路径,让run命令只指定主机路径".可能有办法,我还没有找到它.

最后,我有一种直觉,认为VOLUME在Dockerfile 中指定并不常见,但它可能是永远不会使用的最佳做法VOLUME.有两个原因.我们已经确定的第一个原因:我们无法指定主机路径 - 这是一件好事,因为Dockerfiles应该与主机的细节非常不相关.但第二个原因是人们可能忘记--rm在运行容器时使用该选项.有人可能记得要移除容器但忘记移除卷.此外,即使拥有最佳的人类记忆,也可能需要确定哪些匿名卷可以安全删除.

  • “对于这个小教程的结果,如果我们指定 vol1 vol2 或 /vol1 /vol2 没有区别 - 不要问我为什么”。@MartinAndersson 那是因为当前工作目录是 `/`,所以 `vol1` 相对于 `/`,它解析为 `/vol1`。如果你使用 `WORKDIR` 来指定一个不是 `/` 的工作目录,`vol1` 和 `/vol1` 将不再指向同一个目录。 (8认同)
  • @Martin非常感谢你.你的黑客马拉松及其产生的教程非常有用. (7认同)
  • "我无法提供清除类似英文文档的链接......老实说,我认为这是由于缺乏文档".我可以证实.这是我发现的最全面,最新的文档,我一直在寻找. (4认同)
  • `docker volume prune`可以用于清除未连接到正在运行的容器的剩余卷。并不是说仅凭id就能识别潜在的重要信息很容易... (4认同)
  • 我们什么时候应该使用未命名/匿名卷? (2认同)

Buk*_*gey 56

官方码头教程告诉:

数据卷是绕过Union文件系统的一个或多个容器中的特殊指定目录.数据卷为持久性或共享数据提供了几个有用的功能:

  • 创建容器时初始化卷.如果容器的基本映像包含指定安装点的
    数据,则在卷
    初始化时将现有数据复制到新卷中.(请注意,安装主机
    目录时不适用.)
  • 可以在容器之间共享和重用数据卷.

  • 直接对数据卷进行更改.

  • 更新映像时,不会包括对数据卷的更改.

  • 即使删除容器本身,数据量仍然存在.

进入Dockerfile您只能容器指定卷的目的地.例如/usr/src/app.

当您运行容器时,docker run --volume=/opt:/usr/src/app my_image可以但不必在主机中指定安装点(/ opt).如果未指定--volume参数,则将自动选择安装点

  • `在 Dockerfile 中,您只能指定容器内卷的目标。例如 /usr/src/app.` 这正是我需要阅读的行,以停止在此解决方案上投入更多时间 (20认同)
  • 视频中关于 Docker 中的[存储如何工作](https://youtu.be/zJ6WbK9zFpI?t=3837) 的精彩解释 (2认同)

mr *_*ven 22

a中的VOLUME命令Dockerfile相当合法,完全是常规的,使用起来绝对好,而且无论如何也不会过时。只需要了解它。

我们使用它来指向容器中应用程序将写入很多目录。我们VOLUME不仅仅因为我们想像配置文件一样在主机和容器之间共享。

该命令只需要一个参数。相对于WORKDIR容器(如果已设置)的文件夹路径。然后docker将在其graph(/ var / lib / docker)中创建一个卷并将其安装到容器中的文件夹中。现在,该容器将可以在某处写入高性能文件。如果没有该VOLUME命令,则写入指定文件夹的速度将非常慢,因为现在容器copy on write在容器本身中正在使用其策略。该copy on write策略是存在卷的主要原因。

如果在VOLUME命令指定的文件夹上挂载,则命令永远不会运行,因为VOLUME仅在容器启动时才执行,就像ENV

基本上,使用VOLUME命令您无需外部装入任何卷即可获得性能。数据也将保存在整个容器运行中,而无需任何外部安装。然后,准备好后,只需在其上安装一些东西即可。

一些很好的示例用例:
-日志
-临时文件夹

一些不好的用例:
-静态文件
-配置
-代码

  • 关于好的和坏的示例用例,Docker 的 [“dockerfile best-practices”](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#volume) 页面说:“强烈鼓励您使用将 VOLUME 用于图像的任何可变和/或用户可维护的部分。”。我认为配置在那里。 (2认同)
  • 明确配置的“VOLUME”目录是可以的。然而,一旦您实际安装了配置,您将必须安装在该目录上,因此“VOLUME”命令不会运行。因此,在为配置指定的目录上使用“VOLUME”命令是没有意义的。另外,使用单个静态只读文件初始化卷图也是严重的矫枉过正。所以我坚持我所说的,不需要在配置上使用“VOLUME”命令。 (2认同)

BMi*_*tch 22

VOLUME在Dockerfile中指定一行会在图像上配置一些元数据,但是如何使用该元数据很重要。

首先,这两行做了什么:

WORKDIR /usr/src/app
VOLUME . /usr/src/app
Run Code Online (Sandbox Code Playgroud)

WORKDIR如果该目录不存在,该行将创建目录,并更新一些图像元数据以指定所有相对路径,以及命令的当前目录(例如RUN将位于该位置)。VOLUME那里的行指定了两个卷,一个是相对路径.,另一个是/usr/src/app,这两个卷恰好是同一目录。通常,该VOLUME行仅包含一个目录,但可以包含多个目录,也可以是json格式的数组。

您无法在Dockerfile中指定卷源:在Dockerfile中指定卷时,常见的混乱原因是试图在映像构建时匹配源和目标的运行时语法,这将不起作用。Dockerfile只能指定卷的目的地。如果有人可以定义卷的源,这将是一个微不足道的安全漏洞,因为他们可以更新docker hub上的公共映像以将根目录安装到容器中,然后在容器内部启动后台进程作为入口点的一部分,将登录名添加到/ etc / passwd,将systemd配置为在下次重新启动时启动比特币矿工,或者在文件系统中搜索信用卡,SSN和私钥以发送到远程站点。

VOLUME系列有什么作用?如前所述,它设置一些图像元数据来表示图像内的目录是一个卷。该元数据如何使用?每次从该映像创建容器时,docker都会将该目录强制为卷。如果您在run命令或撰写文件中未提供卷,则docker的唯一选择是创建一个匿名卷。这是一个本地命名卷,其名称具有唯一的长ID,并且没有其他说明其创建原因或包含哪些数据的信息(匿名卷是数据丢失的原因)。如果覆盖该卷,则指向命名卷或主机卷,则数据将转到该卷。

VOLUME破坏了事情:一旦在Dockerfile中定义了卷,就无法禁用它。更重要的是,RUNdocker中的命令是使用临时容器实现的。这些临时容器将获得一个临时匿名卷。该匿名卷将使用您的图像内容进行初始化。您的RUN命令在容器内进行的任何写入都将写入该卷。当RUN命令完成时,更改图像被保存,并以匿名的体积变化被丢弃。因此,强烈建议您不要VOLUME在Dockerfile 中定义内部。对于希望使用卷位置中的初始数据扩展图像的图像下游用户,这会导致意外的行为。

您应该如何指定音量?要指定要将卷包含在映像中的位置,请提供一个docker-compose.yml。用户可以对其进行修改以将卷的位置调整到其本地环境,并且它可以捕获其他运行时设置,例如发布端口和网络。

有人应该记录下来!他们有。Docker在其关于Dockerfile文档中包括有关VOLUME使用情况的警告以及在运行时指定源的建议:

  • 从Dockerfile内更改卷:如果在声明了卷后有任何构建步骤更改了卷内的数据,则这些更改将被丢弃。

...

  • 主机目录在容器运行时声明:主机目录(挂载点)从本质上说是依赖于主机的。这是为了保留图像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从Dockerfile内挂载主机目录。该VOLUME 指令不支持指定host-dir参数。创建或运行容器时,必须指定安装点。


Luc*_*tto 13

在任何情况下,我都不认为使用 VOLUME 是好的,除非您正在为自己创建图像并且没有其他人会使用它。

由于 VOLUME 在我扩展的基本图像中暴露,我受到了负面影响,并且只有在图像已经运行后才知道这个问题,例如 wordpress 将/var/www/html文件夹声明为VOLUME,这意味着在此期间添加或更改的任何文件不考虑构建阶段,即使您不知道,实时更改也会持续存在。在另一个地方定义 web 目录有一个丑陋的解决方法,但这只是一个更简单的解决方案的糟糕解决方案:只需删除 VOLUME 指令。

您可以使用该-v选项轻松实现卷的意图,这不仅明确了容器的卷是什么(无需查看 Dockerfile 和父 Dockerfile),而且还为消费者提供了选择是否使用音量。

由于以下原因,使用 VOLUMES 也很糟糕,如this answer所述:

但是,VOLUME 指令确实需要付出代价。

  • 用户可能不知道正在创建的未命名卷,并在删除容器后继续占用 Docker 主机上的存储空间。
  • 无法删除在 Dockerfile 中声明的卷。下游映像无法向存在卷的路径添加数据。

后一个问题会导致这些问题。

具有取消声明卷的选项会有所帮助,但前提是您知道生成图像的 dockerfile(以及父 dockerfile!)中定义的卷。此外,可以在较新版本的 Dockerfile 中添加 VOLUME 并意外地破坏图像的使用者。

另一个很好的解释(关于具有 VOLUME 的 oracle 图像,已删除):https : //github.com/oracle/docker-images/issues/640#issuecomment-412647328

VOLUME 为人们破坏东西的更多案例:

一个拉请求添加选项重置属性父图像(包括音量),被关闭,正在讨论在这里(你可以看到几种 情况下,带来了负面影响,由于在dockerfiles定义卷),其中有一个注释具有良好对 VOLUME 的解释:

在 Dockerfile 中使用 VOLUME 毫无价值。如果用户需要持久性,他们将确保在运行指定容器时提供卷映射。很难找到我无法设置目录所有权 (/var/lib/influxdb) 的问题是由于 InfluxDB 的 Dockerfile 中的 VOLUME 声明造成的。如果没有 UNVOLUME 类型的选项,或者完全摆脱它,我将无法更改与指定文件夹相关的任何内容。这不太理想,特别是当您有安全意识并希望指定某个 UID 时,图像应该运行,以避免随机用户具有不必要的权限,在您的主机上运行软件。

我能看到关于 VOLUME 的唯一好处是关于文档,如果它只这样做(没有任何副作用),我会认为它很好。

TL; 博士

我认为最好不要使用 VOLUME。


Li-*_*ian 10

为了更好地理解volumedockerfile中的指令,让我们学习mysql官方docker文件实现中的典型卷用法。

VOLUME /var/lib/mysql
Run Code Online (Sandbox Code Playgroud)

参考:https : //github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

/var/lib/mysql是MySQL存储数据文件的默认位置。

如果仅出于测试目的运行测试容器,则不能指定其安装点,例如

docker run mysql:8
Run Code Online (Sandbox Code Playgroud)

然后mysql容器实例将使用volumedockerfile中的指令指定的默认安装路径。这些卷是在Docker根目录中使用非常长的类似ID的名称创建的,这称为“未命名”或“匿名”卷。在基础主机系统的文件夹/ var / lib / docker / volumes中。

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4
Run Code Online (Sandbox Code Playgroud)

这对于无需指定安装点的快速测试而言非常方便,但仍可以通过将Volume用于数据存储而不是容器层来获得最佳性能。

对于正式使用,您将需要通过覆盖安装点以使用命名卷来指定安装路径,例如

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8
Run Code Online (Sandbox Code Playgroud)

该命令将基础主机系统中的/ my / own / datadir目录作为/ var / lib / mysql装入容器中。数据目录/ my / own / datadir不会被自动删除,即使容器也被删除了。

mysql官方映像的用法:参考:https : //hub.docker.com/_/mysql/

  • 我非常喜欢你的解释。 (4认同)