如何在没有 Dockerfile 的情况下设置 Docker Compose? 我想:我不需要:为什么?对docker-compose.yml的逐行解释

问题描述

过去几个小时我一直在尝试设置 nodejs 14 和 rethinkdb 2.3.5 的 2 个认图像,如果语气有点沮丧,很抱歉,但我目前很沮丧。

我的要求似乎非常简单。

我想:

  1. 下载 nodejs 14 和 rethinkdb 2.3.5 的认图像
  2. 将我当前目录中的所有内容复制到 nodejs 14 映像中。
  3. 我希望 nodejs 映像依赖于 RethinkDB 映像。
  4. 在 nodejs 14 镜像中运行 2 个命令; npm cinpm test
  5. 查看测试中的标准输出

我不需要:

  1. 主机可以访问的任何端口。
  2. 自定义任何 Dockerfile 或对认图像进行任何更改。
  3. 从主机文件系统到容器的任何更新。
  4. 将任何数据从主机复制到 RethinkDB 容器。

为什么?

我希望测试可以在所有开发人员机器上重现 - 目前没有 CI。无论开发者将项目放在硬盘上的哪个位置。

我有一个 docker-compose.yml 文件

version: "3"
services:
  tests:
    image: node:14
    ports:
      - "3000:3000"
    # command:
    #  - npm ci
    #  - npm test
    volumes:
      - ".:/cli-app"
    depends_on:
      - rethinkdb

  rethinkdb:
    image: rethinkdb
    ports:
      - "28015:28015"
      - "8080:8080"
    volumes:
      - "./data: /data"
    command: rethinkdb --bindall --data /data

解决方法

这个答案的重点是不是给出必须尽可能简洁的解释(简洁明了地表达),而是强调 docs.docker.com 和 hub 上当前文档的所有混淆。 docker.com 创建。最终我/我们会做对,并且可以写出简洁的答案。

更正后的docker-compose.yml

version: "3"
services:
  tests:
    image: "node:14"
    user: "node"
    working_dir: /home/node/app
    volumes:
      - ./:/home/node/app
    container_name: nodejs
    depends_on:
      - rethinkdb
    command: bash -c "npm ci && npm test"

  rethinkdb:
    image: rethinkdb:2.3.5
    container_name: rethinkdb

令人沮丧的部分!

马上,docs.docker.com 和 hub.docker.com 上的文档可以说是有史以来最糟糕的文档,因为它 a) 错误,b) 假设先验知识

如果以下任何一项是错误的 - 责怪糟糕的文档。

enter image description here

不,除非您计划构建自己的映像,否则您不需要 Dockerfile

因此,在不同的 outdated examples 上浪费了大约一个小时之后,您可能会很幸运地发现您尝试使用 context 来绕过绝对路径示例的一切... 根本没有关系,除非您是从头开始创建自己的映像(90% 的 docker 用户不需要)。

提示使用 docker system prune 删除您通过以下示例创建的所有不幸的无用 docker 容器。

接下来,找到正确的 docker 容器。

展示:nodejs 官方 docker 镜像! enter image description here enter image description here

您看到我们需要的图片名称了吗?

enter image description here

没有一个地方说image。你只需要知道。

docker-compose.yml的逐行解释

version: "3"

使用 3.x 版的语法。语法因 Compose and Docker compatibility matrix 中列出的 Docker 引擎版本而异。

services:

每个容器镜像都是 Docker Compose 术语中的一项服务。 testsrethinkdb 是我对 2 个图像的名称。您可以随意命名它们,但稍后我们将使用此名称在这两个图像之间创建依赖关系(一个需要在另一个之前在线)。

services:
  tests:
    ...
  rethinkdb:
    ...

testsrethinkdb 是我们将让 Docker Compose 为我们运行的两个服务。

修复:1.下载nodejs 14和rethinkdb 2.3.5的默认图片

    image: "node:14"

幸运的是,nodejs Docker 开发人员在 hub.docker.com/_/node 中包含了非常好的文档,而不是适当的标准文档。

映像变体:事实上的映像 node:<version>node:<version>-alpine node:<version>-alpine 映像基于流行的 Alpine Linux 项目,该项目比大多数发行版小得多图像(~5MB),因此通常会导致图像更纤薄..

    image: rethinkdb:2.3.5

不幸的是,资金较少的 RethinkDB 项目,Docker Hub page 没有此类信息。 实际上,如果您一直向下滚动到页面底部,您会发现 图像变体(抱歉,我无法链接到 hub.docker.com 上的标题,因为它们没有 idname 属性)。您要使用的 2 个版本是 rethinkdb:<version>rethinkdb:<version>-slim

问: 那么占主导地位的支持的标签和相应的 Dockerfile 链接部分怎么样?

答: 它们不太经常需要,并且是专门的 docker 镜像。在 RethinkDB 案例中,它是安装在 Debian Buster 和 CentOS 上的 RethinkDB 数据库。还有一些版本的链接,但不是全部。所以这是一个选定的图像列表,您可能不想要。请记住,您必须点击标签或灰色小链接; 查看可用标签(请参见上面带有红色方块的图片 - 抱歉,SO 上也没有锚链接支持)。

修复:2. 将当前目录中的所有内容复制到 nodejs 14 映像中。

    user: "node"
    working_dir: /home/node/app
    volumes:
      - ./:/home/node/app

如果您在 docs.docker.com 上查找 userworking_dir,如果没有先验知识 docker,您会很不走运。它仅说明:

每一个都是一个值,类似于它的 docker run 对应项。请注意,mac_address 是一个旧选项。

此处的目标是将当前目录(docker-compose.yml 所在的位置)复制到提取 tests 映像的 node:14 服务。我们需要在我们的容器中创建一个目录,我们机器上的当前目录将被复制到其中。稍后,我们希望在该目录中执行一些命令,并且我们不想以 sudo 的身份运行内容,因此自然放置在我们的用户主目录 (~/) 中。

我们需要:

  1. 我们 nodejs 容器中的一个用户。
  2. 在我们的 nodejs 容器中的用户主目录中创建一个目录。
  3. 将机器上的当前目录映射到 nodejs 容器中的新目录。

阅读 nodejs 开发者编写的 How to use this image 后,我们可以在示例中看到图像中有 node 用户。 该示例还显示了我们需要哪些 Docker Compose 配置。

    user: "node"

大概使用image: "node:14"中定义的node用户。

    working_dir: /home/node/app

node 用户的 home 目录中创建一个 app 目录。

    volumes:
      - ./:/home/node/app

将我们机器上的当前目录映射到 tests 容器(使用 image: "node:14")内的 /home/node/app 目录。

修复:3. 我希望 nodejs 图像依赖于 RethinkDB 图像。

    container_name: nodejs

事实证明,定义 container_name 纯粹是装饰性的。它不会帮助您链接网络或先启动一个容器。它只是一个名字。当您的容器正在运行时,您可以使用 docker ps 查看它们或使用 docker ps -a 查看所有容器,甚至是关闭的容器。在 NAMES 列中写入 container_name。如果您没有定义 container_name,那么它们将被称为您的目录名和服务名,后缀以一个增量编号。

docker pscontainer_name 运行时:

CONTAINER ID        IMAGE               PORTS                            NAMES
5272576f8555        node:14                                              nodejs
fb11d5ce049b        rethinkdb:2.3.5     8080/tcp,28015/tcp,29015/tcp   rethinkdb

docker ps 没有 container_name 运行时:

CONTAINER ID        IMAGE               PORTS                            NAMES
528e5ee37956        node:14                                              data_access_layer_tests_1
e80682b806fc        rethinkdb:2.3.5     8080/tcp,29015/tcp   data_access_layer_rethinkdb_1
    depends_on:
      - rethinkdb

depends_on 是神奇的!它告诉 Docker Compose rethinkdb 容器必须在 这个 容器之前online 启动。

这在极少数情况下 Docker Compose documentation (depends_on) 实际上是好的。

不幸的是,depends_on 不能保证依赖图像在线,正如您所期望的那样,但在这种情况下,文档也非常清楚,并提供了另一种解决方案。在我们的例子中,这无关紧要,因为 rethinkdb 容器启动得足够快(npm ci 中的 nodejs 将比 rethinkdb 的启动时间更长)。>

修复:4.在nodejs 14镜像中运行2个命令; npm ci 和 npm 测试。

    command: "npm ci && npm test"

现在我们可以在我们的 docker 容器中运行一些命令,就像我们在我们的机器上的项目目录中运行它们一样。 当然不是。 以上只会执行npm ci。请参阅 SO 答案 Using Docker-Compose,how to execute multiple commands。 Docker 使用一种晦涩难懂的 shell 脚本变体,它可以执行一个且仅一个带参数的命令。 documentation 表示它与 the docker CMD key 类似,但两个站点都没有解释为什么某些 POSIX shell 命令可以工作而其他命令不能工作,但我们认为它使 Docker 开发人员的生活更轻松:)

执行多个命令的正确方法是:

    command: bash -c "npm ci && npm test"

请参阅"Using Docker-Compose,how to execute multiple commands"

npm cipackage-lock.json 安装所有 npm 包,npm testpackage.json 中运行我们的 "test" 脚本.

您可能认为您可以在一个 YAML 数组中编写多个命令,但在这种情况下,显然这样做会告诉 node require 它们作为容器内的模块,您会得到如下错误:

nodejs       | internal/modules/cjs/loader.js:883
nodejs       |   throw err;
nodejs       |   ^
nodejs       |
nodejs       | Error: Cannot find module '/home/node/app/npm ci'
nodejs       |     at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
nodejs       |     at Function.Module._load (internal/modules/cjs/loader.js:725:27)
nodejs       |     at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
nodejs       |     at internal/main/run_main_module.js:17:47 {
nodejs       |   code: 'MODULE_NOT_FOUND',nodejs       |   requireStack: []
nodejs       | }
    command:
      - "npm ci"
      - "npm test"

以上将不起作用工作!

改为使用通常的 && 来执行一系列成功的命令。 同样,Using Docker-Compose,how to execute multiple commands 是正确的文档。

实际上,忘记上面的内容并使用 entrypoint 指向您编写所有命令的 bash 文件。

修复:5. 查看测试中的标准输出。

请勿使用docker-compose up -d

您无需在此处执行任何操作。尽管每个 Docker Compose 教程都会告诉您以分离模式运行。您不会看到 stdout 或 stderr。松开 -d

docker-compose up 是要走的路!

如果你真的必须在分离模式下运行,你可以通过 docker logs [container id] 查看输出并通过 docker psdocker ps -a 获取容器 ID。如果您的容器当前未运行,则使用后者。

docker-compose.yml 看起来需要的键

事实证明 Docker 有很多合理的默认值,除非你真的必须,否则你不应该弄乱这些默认值。

    ports:
      - "28015:28015"
      - "8080:8080"

您不需要在 docker-compose.yml 中公开端口,除非您需要向本地机器或外部网络公开端口,即使那里的每个示例都这样做。例如,如果您需要访问 localhost:8080(在 RethinkDB 中即仪表板),则必须添加:

ports:
  - "8080:8080"

您的其他服务/容器将可以访问端口 280158080,使用服务名称作为主机名,而无需在 docker-compose.yml 中指定任何内容。例如。在这种情况下rethinkdb:28015。请参阅下文了解更多信息。

您不需要在 `docker-compose.yml` 中公开端口,即使那里的每个示例都这样做。您使用的映像可能已经公开了您需要的容器化软件使用的默认端口。上面的示例是 RethinkDB 使用的默认端口,它们无需您编写即可公开。
    links:
      - rethinkdb

links 似乎是连接两个不同容器的一种方式,但事实证明所有容器都共享网络,因此您不必这样做。 official documentation 带有一个大红色警告,建议您使用 user-defined networks。反过来说:

默认情况下,Compose 会为您的应用设置一个 network。服务的每个容器都加入默认网络,并且可以被该网络上的其他容器访问,并且可以在与容器名称相同的主机名上被它们发现。

这是一种复杂的说法,即在 Docker Compose 容器内,您可以像在本地机器上一样连接到端口和 IP 地址。

因此,如果容器 A 的图像公开 172.18.0.2:28015,那么您可以使用该确切地址从容器 B 进行连接。 E.i. IP:172.18.0.2 和端口:28015 从头开始​​!公开的 IP 地址不稳定。 Docker 会因为各种(未知的)原因改变它们。

服务的每个容器都加入默认网络,并且可以被该网络上的其他容器访问,并且可以在与容器名称相同的主机名上被它们发现。

表示您的服务名称用作主机名,与您在 /etc/hosts 文件中定义它的方式相同。 或类似于 DNS 链接 stackoverflow 的方式.com 到 151.101.193.69。

因此,如果容器 rethinkdb 公开端口 28015,则可以通过 nodejsrethinkdb:28015 容器访问它。

注意documentation 说:

version: "3.9"
services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres
    ports:
      - "8001:5432"

每个容器现在都可以查找主机名 webdb 并取回相应容器的 IP 地址。例如,web 的应用程序代码可以连接到 URL postgres://db:5432 并开始使用 Postgres 数据库。

example for Networking in Compose中,postgresimagedb是服务名,5432是端口号。根据我的经验,这不起作用。您需要使用服务名称并且从不 image 名称。因此,从 web 连接到 db 的正确方法是使用 URL db:5232 @jonrsharpe 指出协议 ({{ 1}}) 恰好与图像名称匹配。

以下是对我之前对示例的理解的更正

example for Networking in Composepostgres://是协议(类似于postgres),https是服务名,db是端口号。您需要使用服务名称来获取正确的 IP 地址。因此,从 5432 连接到 web 的正确方法是使用 URL db。其中 [protocol]://db:5232 可以是 protocolhttphttps 等。

由于所有容器都在同一个网络内,除非您需要向本地机器(或外部网络)公开服务,否则您不需要progres 键。


ports

每个 RethinkDB volumes: - "./data: /data" 都有这个,但只有在您想将文件从本地机器复制到容器时才需要。在这种情况下,我们不想用任何东西预加载数据库,所以我们没有任何文件来为数据库做种,因此不需要 Dockerfile 键。