docker-compose 中卷挂载的工作原理

How volume mounting works in docker-compose

我有一个非常简单的 node js 应用程序,项目结构如下所示。

index.js

package.json

package-lock.json

Dockerfile

FROM node:12.18.2-alpine
WORKDIR /test-app
COPY package.json package-lock.json ./
RUN npm i
COPY . ./
EXPOSE 3000
ENTRYPOINT [ "node", "index.js" ]

docker-compose.yml

version: '3.2'

services:
  test-app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/test-app
      - "test_app_node_modules:/test-app/node_modules"
volumes:
  test_app_node_modules:
    driver: local

如果您查看 docker-compose.yml 文件中的 volumes 部分,首先我 bind mounting 我在主机上的当前目录到 test-app 目录容器。这意味着:

  1. 我当前目录中的任何文件或目录都将反映在容器目录中,对容器目录所做的任何更改也将反映回主机目录。
  2. 这意味着在 docker 构建期间安装在容器 test-app 目录中的 node_modules 也被覆盖。

volumes 部分的下一步是 named volumes。这意味着:

  1. 它应该将容器内 test-app/node_modules 的所有内容复制到 test_app_node_modules 卷。但是 test-app/node_modules 是空的,因为步骤 1 覆盖了它。
  2. 这意味着我们创建了一个空卷并将其安装到容器中。

如果是这样,它应该会导致缺少依赖项错误,但我的应用 运行 正常。我不确定我从哪里得到 node_modules。

此外,我在主机目录中看到一个空的 node_modules 文件夹。我假设这背后的原因是 "test_app_node_modules:/test-app/node_modules" 在容器中寻找 node_modules 但它不存在所以它创建了一个结果,它被反射回主机目录。

我无法理解批量安装的概念。这里的流量是多少?当有 none 时,node_modules 如何开始存储到卷中?

在您的 docker 文件中您首先创建了 WORKDIR /test-app 在其中您添加了一个 package.json 文件并安装了依赖项 RUN npm i 所以现在已经有 node_module 存在于 docker 图像本身中。

在使用 COPY . ./ 之后,您将向 docker 图像添加额外的文件,例如 index 和其他所有文件。

如果您要删除整个 volume 部分,那么它也可以工作,因为您的 docker 图像包含代码及其依赖项。

version: '3.2'

services:
  test-app:
    build: .
    ports:
      - "3000:3000"

我不太确定您为什么要以这种方式设置 docker 容器,但它不起作用的原因是对卷和绑定安装的方向有误解工作。你说:

It should copy everything from test-app/node_modules inside container to test_app_node_modules volume. But the test-app/node_modules is empty because step 1 overwrote it.

这是从前到后。当您使用卷时, 被复制到目标。这就是卷的全部意义所在——它们旨在让您即使重建容器也能持久保存数据。如果您使用绑定安装的卷,则 the host directory is copied into the target in the docker container。因此,您在主机上的 test_app_node_modules 目录被复制到容器中的 /test-app/node_modules 中。据推测 test_app_node_modules 包含您所有的节点模块,因此您不会收到有关缺少模块的错误。

只有当您的容器实际上 运行 时,容器中的代码 运行 才能 update/delete 卷中的数据 - 而不是在您构建容器时。

在纯机械层面上,这里会发生一些事情。

Docker 对卷安装进行排序。它知道 /test-app/node_modules/test-app 的子目录。所以Docker会

  1. 创建从主机目录到容器目录的绑定挂载
  2. 如果需要作为挂载点,请创建一个空的 node_modules 目录
  3. 在该安装点上安装 Docker 卷

这是主机上空 node_modules 目录的来源:Docker 将其创建为挂载点,完成绑定挂载后,因此那里的更改会反映在主机内容。

当命名卷挂载到容器中时,当且仅当命名卷为空时,Docker will copy the content from the image into the volume;这个特定的步骤忽略了容器中已经安装了一个卷。 (如果该卷是匿名卷,这也适用,如您在其他 Node 示例中看到的那样;它不适用于主机绑定挂载,如果旧内容在卷中或在 Kubernetes 上。)

这就是为什么这显然有效的原因。正如这个问题的其他答案一样,这并不是 Docker 的特别有效使用。如果您需要实时开发设置,我还建议只删除这些 volumes:,并直接在主机上 运行 node index.js