了解DockerFile中的“VOLUME”指令

问题描述 投票:67回答:5

以下是我的“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" ]

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

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

docker dockerfile
5个回答
42
投票

官方码头工程教程说:

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

  • 创建容器时初始化卷。如果容器的基本映像包含指定安装点的数据, 现有数据按卷复制到新卷中 初始化。 (请注意,安装主机时不适用 目录。)
  • 可以在容器之间共享和重用数据卷。
  • 直接对数据卷进行更改。
  • 更新映像时,不会包括对数据卷的更改。
  • 即使删除容器本身,数据量仍然存在。

Dockerfile中,您只能指定容器内卷的目标。例如/usr/src/app

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


172
投票

简而言之:不,你的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: Specifying volumes

鉴于此Dockerfile:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(对于这个minitutorial的结果,如果我们指定vol1 vol2/vol1 /vol2没有区别 - 不要问我为什么)

建立它:

docker build -t my-openjdk

跑:

docker run --rm -it my-openjdk

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

运行容器还会在主机端创建两个目录或“卷”。

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

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

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

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

sudo ls /var/lib/docker/volumes/f670...49f0/_data

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

注意:_data文件夹也称为“挂载点”。

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

运行新容器,但使用-v指定卷:

docker run --rm -it -v /vol3 my-openjdk

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

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

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

docker run -it -v $(pwd)/src:/src my-openjdk

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

总而言之,假设我们在主机上的项目文件夹中有./src/Hello.java,其中包含以下内容:

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

我们构建这个Dockerfile:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

我们运行这个命令:

docker run -v $(pwd)/src:/src my-openjdk

这打印出“Hello,World!”。

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

最后的评论

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

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

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


10
投票

在Dockerfile中指定VOLUME行会在图像上配置一些元数据,但这些元数据的使用方式非常重要。

首先,这两行做了什么:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

WORKDIR行在那里创建目录(如果它不存在),并更新一些图像元数据以指定所有相对路径,以及RUN等命令的当前目录将在该位置。那里的VOLUME线指定了两个卷,一个是相对路径.,另一个是/usr/src/app,两者恰好都是同一个目录。大多数情况下,VOLUME行只包含一个目录,但它可以包含多个,如你所做的,或者它可以是一个json格式的数组。

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

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

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

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

有人应该记录下来!他们有。 Docker包含有关其documentation on the Dockerfile中VOLUME用法的警告以及在运行时指定源的建议:

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

...

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

9
投票

VOLUME中的Dockerfile命令是非常合法的,完全是传统的,绝对可以使用,并且它不会被弃用。只需要了解它。

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

命令只需要一个参数;从容器内部到文件夹的路径,相对于WORKDIR(如果已设置)。然后,docker将在其图形(/ var / lib / docker)中创建一个卷,并将其挂载到容器中的文件夹中。现在容器将具有高性能的写入位置。没有VOLUME命令,指定文件夹的写入速度将非常慢,因为现在容器正在容器本身使用它的copy on write策略。 copy on write策略是卷存在的主要原因。

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

基本上使用VOLUME命令可以在不外部安装任何卷的情况下获得性能。数据将在容器运行中保存,无需任何外部安装。然后当准备就绪时,只需在它上面装一些

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

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


1
投票

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

VOLUME /var/lib/mysql

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

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

当您运行测试容器仅用于测试目的时,您可能不会指定其安装点,例如。

docker run mysql:8

那么mysql容器实例将使用默认的mount路径,该路径由dockerfile中的volume指令指定。在Docker根目录中创建的卷具有一个非常长的ID名称,这称为“未命名”或“匿名”卷。在底层主机系统/ var / lib / docker / volumes的文件夹中。

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

这对于快速测试非常方便,无需指定安装点,但仍然可以通过使用Volume for data store而不是容器层来获得最佳性能。

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

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

该命令将底层主机系统中的/ my / own / datadir目录挂载到容器内的/ var / lib / mysql。数据目录/ my / own / datadir不会被自动删除,即使容器被删除也是如此。

使用mysql官方图片:参考:https://hub.docker.com/_/mysql/

© www.soinside.com 2019 - 2024. All rights reserved.