理解“VOLUME"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/app指令挂载 主机当前工作目录的内容将被挂载在/usr/src/app

.容器的文件夹

请告诉我这是正确的方式吗?

353319 次浏览

官方docker教程说:

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

  • 卷在创建容器时初始化。如果容器的基本映像包含指定挂载点上的数据,则
    在卷
    时,将现有数据复制到新卷中 初始化。(请注意,这不适用于挂载
    的主机 目录。)< / p >

  • 数据卷可以在容器之间共享和重用。

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

  • 当您更新映像时,对数据卷的更改将不包括在内。

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

Dockerfile中,你只能将卷内部指定为容器的目的地。例如/usr/src/app

当你运行一个容器时,例如docker run --volume=/opt:/usr/src/app my_image,你五月但不必指定它在主机上的挂载点(/opt)。如果你没有指定--volume参数,那么挂载点将被自动选择,通常在/var/lib/docker/volumes/下。

简而言之:不,你的VOLUME指令不正确。

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

注意:由于自动生成的名称非常长,从人类的角度来看没有任何意义,这些卷通常被称为“未命名”卷。或“;anonymous"。

你的例子中使用了a '。'字符甚至不会在我的机器上运行,无论我将圆点作为第一个参数还是第二个参数。我得到这个错误消息:

docker:来自daemon的错误响应:oci运行时错误:container_linux。Go:265:启动容器进程。Go:368: container init导致“open /dev/ptmx: no such file or directory”;

我知道在这一点上所说的可能对试图理解VOLUME-v的人来说不是很有价值,而且它肯定不能为你试图实现的目标提供解决方案。所以,希望下面的例子能更清楚地说明这些问题。

Minitutorial:指定卷

给定这个Dockerfile:

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

(对于这个小教程的结果,如果我们指定vol1 vol2/vol1 /vol2没有区别——这是因为Dockerfile中的默认工作目录是/)

构建:

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

这印着“你好,世界!”

最好的部分是,我们完全可以在第二次运行时使用新消息修改.java文件,以获得另一个输出-而不必重新构建image =)

最后的评论

我对Docker很陌生,前面提到的“教程”;反映了我从为期3天的命令行黑客马拉松中收集到的信息。我几乎感到羞愧,我不能提供清晰的英文文档链接来支持我的声明,但我真的认为这是由于缺乏文档,而不是个人努力。我知道这些例子就像广告中宣传的那样,使用我当前的设置,即“Windows 10 ->流浪者2.0.0 ->码头工人17.09.0-ce"。

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

最后,我有一种直觉,在Dockerfile中指定VOLUME并不少见,但最好不要使用VOLUME。有两个原因。我们已经指出的第一个原因是:我们不能指定主机路径——这是一件好事,因为Dockerfiles应该不知道主机的具体情况。但第二个原因是人们在运行容器时可能会忘记使用--rm选项。人们可能会记得删除容器,但忘记删除卷。此外,即使有最好的人类记忆,找出所有匿名卷中哪些是安全的也可能是一项艰巨的任务。

Dockerfile中的VOLUME命令是非常合法的,完全常规的,绝对可以使用,并且无论如何都没有弃用。只需要理解它。

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

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

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

基本上,使用VOLUME命令可以在不外部挂载任何卷的情况下获得性能。数据将保存跨容器运行也没有任何外部挂载。然后当准备好时,简单地安装在它上面的东西。

一些好的用例:
< br > -日志 - temp文件夹

一些不好的用例:
- static files
. txt ——配置< br > ——代码< / p >

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

首先,这两行做了什么:

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

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

不能在Dockerfile中指定卷源:在Dockerfile中指定卷时,一个常见的混淆来源是试图在映像构建时匹配源和目标的运行时语法,这行不通。Dockerfile只能指定卷的目的地。这将是一个微不足道的安全利用如果有人可以定义一个卷的来源,因为他们可以在码头工人更新常见图像中心安装根目录到容器内,然后启动后台进程容器的入口点,将登录添加到/ etc / passwd,配置systemd推出一个比特币商下重启,或者搜索文件系统信用卡,ssn和私钥发送一个远程站点。

VOLUME线是做什么的?如前所述,它设置了一些图像元数据,说图像中的目录是一个卷。如何使用此元数据?每次从这个映像创建容器时,docker都会强制该目录为一个卷。如果你没有在你的运行命令或撰写文件中提供卷,docker唯一的选择就是创建一个匿名卷。这是一个本地命名卷,名称有一个长且唯一的id,并且没有其他指示来说明为什么创建它或它包含什么数据(匿名卷是数据丢失的情况)。如果覆盖卷,指向命名卷或主机卷,则数据将转到该卷。

体积破坏事物:不能禁用Dockerfile中定义的卷。更重要的是,docker中的RUN命令是用经典构建器中的临时容器实现的。这些临时容器将获得一个临时匿名卷。该匿名卷将使用映像的内容进行初始化。容器内RUN命令的任何写入都将被写入该卷。当RUN命令完成时,对图像的更改将被保存,并且匿名卷的修改将被丢弃。由于这个原因,我强烈建议不要在Dockerfile中定义VOLUME。如果映像的下游用户希望使用卷位置中的初始数据扩展映像,这会导致意想不到的行为。

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

他们有。Docker在他们的Dockerfile文档中包含了关于VOLUME使用的警告,以及在运行时指定源的建议:

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

...

    主机目录(挂载点)本质上是依赖于主机的。这是 以保持映像的可移植性,因为给定的主机目录不能 保证在所有主机上可用。因为这个原因,你不能 从Dockerfile中挂载一个主机目录。的VOLUME 指令不支持指定host-dir形参。你 在创建或运行容器时必须指定挂载点

随着buildkit的引入,Dockerfile中定义VOLUME和RUN步骤的行为发生了变化。这里有两个例子。首先是Dockerfile:

$ cat df.vol-run
FROM busybox


WORKDIR /test
VOLUME /test
RUN echo "hello" >/test/hello.txt \
&& chown -R nobody:nobody /test

其次,不使用buildkit进行构建。注意RUN步骤的更改是如何丢失的:

$ DOCKER_BUILDKIT=0 docker build -t test-vol-run -f df.vol-run .
Sending build context to Docker daemon  23.04kB
Step 1/4 : FROM busybox
---> beae173ccac6
Step 2/4 : WORKDIR /test
---> Running in aaf2c2920ebd
Removing intermediate container aaf2c2920ebd
---> 7960bec5b546
Step 3/4 : VOLUME /test
---> Running in 9e2fbe3e594b
Removing intermediate container 9e2fbe3e594b
---> 5895ddaede1f
Step 4/4 : RUN echo "hello" >/test/hello.txt  && chown -R nobody:nobody /test
---> Running in 2c6adff98c70
Removing intermediate container 2c6adff98c70
---> ef2c30f207b6
Successfully built ef2c30f207b6
Successfully tagged test-vol-run:latest


$ docker run -it test-vol-run /bin/sh
/test # ls -al
total 8
drwxr-xr-x    2 root     root          4096 Mar  6 14:35 .
drwxr-xr-x    1 root     root          4096 Mar  6 14:35 ..
/test # exit

然后用buildkit进行构建。注意RUN步骤的更改是如何保存的:

$ docker build -t test-vol-run -f df.vol-run .
[+] Building 0.5s (7/7) FINISHED
=> [internal] load build definition from df.vol-run                                                0.0s
=> => transferring dockerfile: 154B                                                                0.0s
=> [internal] load .dockerignore                                                                   0.0s
=> => transferring context: 34B                                                                    0.0s
=> [internal] load metadata for docker.io/library/busybox:latest                                   0.0s
=> CACHED [1/3] FROM docker.io/library/busybox                                                     0.0s
=> [2/3] WORKDIR /test                                                                             0.0s
=> [3/3] RUN echo "hello" >/test/hello.txt  && chown -R nobody:nobody /test                        0.4s
=> exporting to image                                                                              0.0s
=> => exporting layers                                                                             0.0s
=> => writing image sha256:8cb3220e3593b033778f47e7a3cb7581235e4c6fa921c5d8ce1ab329ebd446b6        0.0s
=> => naming to docker.io/library/test-vol-run                                                     0.0s


$ docker run -it test-vol-run /bin/sh
/test # ls -al
total 12
drwxr-xr-x    2 nobody   nobody        4096 Mar  6 14:34 .
drwxr-xr-x    1 root     root          4096 Mar  6 14:34 ..
-rw-r--r--    1 nobody   nobody           6 Mar  6 14:34 hello.txt
/test # exit

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

VOLUME /var/lib/mysql
< p >参考: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile < / p >

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

当您仅为测试目的运行测试容器时,您可以不指定它的挂载点,例如。

docker run mysql:8

那么mysql容器实例将使用dockerfile中的volume指令指定的默认挂载路径。卷是在Docker根目录下用一个很长的类似id的名字创建的,这个名字叫做“unnamed”。或“;anonymous"体积。在底层主机系统/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官方镜像的使用(请检查&;Where to Store data &;部分):

< p >参考: https://hub.docker.com/_/mysql/ < / p >

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

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

你可以使用-v选项轻松地实现卷的目的,这不仅可以明确容器的卷是什么(无需查看Dockerfile和父Dockerfiles),而且还可以让使用者选择是否使用卷。

由于以下原因,使用卷也是不好的,如这个答案所述:

然而,VOLUME指令是有代价的。

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

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

拥有未声明卷的选项会有所帮助,但前提是你知道生成映像的dockerfile(以及父dockerfiles!)中定义的卷。此外,VOLUME可以添加到新版本的Dockerfile中,从而意外地破坏映像消费者的工作。

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

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

一个把请求添加选项来重置父映像的属性(包括VOLUME),被关闭并正在讨论在这里(并且你可以看到几个 情况下 of 由于dockerfiles中定义的卷而受到不利影响),它有一个评论,对VOLUME有一个很好的解释:

在Dockerfile中使用VOLUME毫无价值。如果用户需要 持久性,它们一定会在提供卷映射时 运行指定的容器。很难找到它 无法设置目录的所有权的问题 (/var/lib/influxdb)是由于在influxdb的VOLUME声明 Dockerfile。没有UNVOLUME类型的选项,或者去掉它 总之,我无法改变任何有关指定的 文件夹中。这并不理想,尤其是当你 安全意识,并希望指定映像应该是的某个UID 运行作为,以避免随机用户,拥有更多的权限比 必须的,在你的主机上运行软件

VOLUME唯一的优点是文档,如果它只做文档(没有任何副作用),我就会认为它很好。

更新(2021-10-19)

mysql官方映像的另一个相关问题:https://github.com/docker-library/mysql/issues/255

更新(2022-01-26)

我发现了一篇很好的文章解释了VOLUME的问题。它已经有好几年了,但同样的问题仍然存在:

https://boxboat.com/2017/01/23/volumes-and-dockerfiles-dont-mix/

博士TL;

我认为VOLUME的最佳用途是弃用。

虽然这是一个非常老的帖子,但如果你在volumebind mounts之间有一些困惑,我仍然希望你可以查看最新的docker官方文档

Bind mounts在Docker早期就已经存在了,我认为它也不应该是一个完美的设计,例如“绑定安装允许访问敏感文件”;, 你可以得到docker官方更喜欢使用VOLUME而不是bind mounts.

你可以从在这里中获得卷的良好用例

参考