了解DockerFile中的“ VOLUME”指令


136

以下是我的“ 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文件夹中。

请让我知道这是正确的方法吗?

Answers:


88

官方Docker教程说:

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

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

  • 直接更改数据量。

  • 更新映像时将不包括对数据量的更改。

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

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

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


274

简而言之:不,您的VOLUME指示不正确。

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

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

您的示例使用“。” 无论我将点设为第一个还是第二个参数,该字符甚至都不会在我的机器上运行。我收到此错误消息:

docker:来自守护程序的错误响应:oci运行时错误:container_linux.go:265:启动容器进程导致“ process_linux.go:368:容器初始化引起了\“ open / dev / ptmx:没有此类文件或目录\”。

我知道,到目前为止,对于试图理解的人来说,所说的内容可能不是很有价值VOLUME-v并且它当然不能为您要实现的目标提供解决方案。因此,希望以下示例可以为这些问题提供更多的启示。

教程:指定卷

鉴于此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

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

在运行容器的同时,docker volume ls主机上执行操作,您将看到类似这样的内容(为简便起见,我用三个点代替了名称的中间部分):

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

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

现在,该文件在主机上的未命名卷之一中可用。我进行了两次尝试,因为我首先尝试了第一个列出的卷,但是最终我在主机上使用此命令在第二个列出的卷中找到了我的文件:

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是“打印当前/工作目录”的命令。将该命令放入$()括号中,将其运行在子外壳中,并返回到项目目录的绝对路径。

放在一起,假设我们./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

打印“你好,世界!”。

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

结束语

我刚接触Docker,前面提到的“教程”反映了我从为期3天的命令行黑客马拉松中收集的信息。令我感到羞耻的是,我还无法提供链接来清除清晰的类似英语的文档来支持我的陈述,但是老实说,这是由于缺少文档而不是个人的努力。我确实知道示例使用我当前的设置“ Windows 10-> Vagrant 2.0.0-> Docker 17.09.0-ce”进行宣传。

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

最后,我有一种直觉,认为VOLUME在Dockerfile 中指定不仅是不常见的,而且它是从不使用的最佳实践VOLUME。有两个原因。我们已经确定的第一个原因:我们无法指定主机路径-这是一件好事,因为Dockerfiles应该与主机的具体情况无关。但是第二个原因是,人们可能会--rm在运行容器时忘记使用该选项。一个人可能记得删除容器,但忘记删除卷。另外,即使拥有最好的人类记忆力,找出所有匿名卷中哪些可以安全删除也是一项艰巨的任务。


2
我们什么时候应该使用未命名/匿名卷?
Searene '18

10
@马丁非常感谢你。非常感谢您的骇客马拉松及其相关教程。
Beezer

6
“我无法提供指向清晰的类似英语的文档的链接……老实说,这是由于缺少文档所致”。我可以确认。这是我找到的最详尽,最新的文档,并且我一直在寻找几个小时。
user697576

4
docker volume prune可用于清除未附加到正在运行的容器的剩余卷。并不是说仅凭id就能分辨出潜在的重要信息就很容易了
杰里米(Jeremy)

4
“对于本教程的结果,如果我们指定vol1 vol2或/ vol1 / vol2没什么区别-不要问我为什么。” @MartinAndersson这是因为当前的工作目录是/,所以vol1相对于/,解析为/vol1。如果使用WORKDIR指定以外的工作目录/vol1并且/vol1不再将指向同一个目录。
sebastian

41

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

首先,这两行做了什么:

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

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

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

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

...

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

36

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

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

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

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

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

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

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


2
关于示例用例的好坏,Docker的“ dockerfile最佳实践”页面说:“强烈建议您将VOLUME用于映像的任何可变和/或用户可维护的部分。” 我认为配置在那里。
OmerSch

2
明确说明VOLUME配置目录是可以的。但是,一旦实际挂载配置,您将必须挂载该目录,因此该VOLUME命令将无法运行。因此,VOLUME在为配置指定的目录上使用命令毫无意义。用单个静态只读文件初始化体积图也是严重的过大杀伤力。因此,我坚持我所说的,无需VOLUME在配置上执行命令。
避风港先生

由于实现细节,卷可能带来不同的性能特征。数据库数据文件适合此用例,但是将数据与(临时)容器存储一起存储的意义何在?即,将卷存在归因于性能是不正确的。
安德烈Werlang

33

为了更好地理解volumedockerfile中的指令,让我们学习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容器实例将使用volumedockerfile中的指令指定的默认安装路径。这些卷是在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/


2
我很喜欢你的解释。
卢卡斯·塔拉斯卡(LukaszTaraszka),

但是码头工人仍然保存更改。您也可以设置安装路径-v,而无需在Dockerfile中设置卷即可使用它
Alex78191

1

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

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

您可以使用该-v选项轻松实现卷的意图,这不仅使容器的体积变得清晰(无需查看Dockerfile和父Dockerfile),而且还为用户提供了选择是否使用音量。

如以下答案所述,由于以下原因,使用VOLUMES基本上是不好的:

但是,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时,应以运行映像的方式运行,以避免随机用户拥有比必要更多的权限,并在主机上运行软件。

我也认为EXPOSE不好,但副作用较少。关于VOLUME和EXPOSE,我唯一能看到的就是关于文档的知识,如果它们仅用于文档(没有任何副作用),我认为它们很好。

TL; DR

我认为最好不要使用VOLUME。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.