理解" VOLUME" DockerFile中的指令

时间:2017-01-30 12:01:40

标签: 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" ]

在这个文件中我期待" VOLUME。的/ usr / SRC /应用"在主机上挂载当前工作目录内容的指令,安装在容器的/ usr / src / app文件夹中。

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

6 个答案:

答案 0 :(得分:156)

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

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

构建它:

docker build -t my-openjdk

执行命令

docker run --rm -it my-openjdk

在容器内部,在命令行中运行ls,您会注意到存在两个目录; /vol1/vol2

运行容器还会在主机端创建两个目录,或者#34;卷&#34 ;.

在容器运行的同时,在主机上执行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选项。有人可能记得要移除容器但忘记移除卷。此外,即使拥有最好的人类记忆,也可能需要确定哪些匿名卷可以安全删除。

答案 1 :(得分:34)

官方码头工程教程说:

  

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

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

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

  •   
  • 更新图像时,不会包含对数据卷的更改。

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

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

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

答案 2 :(得分:8)

在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都会将该目录强制为卷。如果您在run命令或撰写文件中未提供卷,则docker的唯一选择是创建一个匿名卷。这是一个本地命名卷,该名称的名称具有唯一的长ID,并且没有其他说明其创建原因或包含哪些数据的信息(匿名卷是数据丢失的原因)。如果您覆盖该卷,并指向一个命名卷或主机卷,则数据将转到该卷。

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

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

有人应对此进行记录!。 Docker在其documentation on the Dockerfile中包含有关VOLUME使用情况的警告,以及在运行时指定源的建议:

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

...

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

答案 3 :(得分:7)

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

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

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

如果安装在VOLUME命令指定的文件夹上,该命令将永远不会运行,因为VOLUME仅在容器启动时才执行,就像ENV一样。

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

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

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

答案 4 :(得分:5)

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

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

您可以使用-v选项轻松实现卷的目的,这不仅使容器的卷变得清晰(无需查看Dockerfile和父Dockerfile),而且这也使消费者可以选择是否使用音量。

由于VOLUME所说的以下原因,使用VOLUMES也很糟糕:

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

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

后一个问题会导致类似的问题。

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

另一个很好的解释(GitLab on Docker: how to persist user data between deployments?,原为about the oracle image having VOLUME):removed

VOLUME为人们弄碎东西的更多案例:

https://github.com/samos123/docker-drupal/issues/10添加了用于重置父图像(包括VOLUME)的属性的选项,该图像已关闭并且正在讨论中pull request(您可以看到here several cases受dockerfiles中定义的卷的影响而受到不利影响),其中有一个people,对VOLUME有很好的解释:

在Dockerfile中使用VOLUME是毫无价值的。如果用户需要 持久性,他们将确保在以下情况下提供卷映射: 运行指定的容器。很难追踪到 我的问题是无法设置目录的所有权 (/ var / lib / influxdb)是由于InfluxDB的VOLUME声明 Dockerfile。没有UNVOLUME类型的选项,或者摆脱它 总计,我无法更改与指定内容相关的任何内容 夹。这不理想,尤其是当您 具有安全意识并希望指定图像的特定UID 为了避免产生随机用户,其运行方式为 必要,在主机上运行软件。

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

TL; DR

我认为不建议使用VOLUME。

答案 5 :(得分: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容器实例将使用dockerfile中volume指令指定的默认安装路径。这些卷是在Docker根目录中使用非常长的类似ID的名称创建的,这称为“未命名”或“匿名”卷。在基础主机系统的文件夹/ var / lib / docker / volumes中。

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

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

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

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/