多个docker-compose项目之间的通信

615
我有两个分别位于不同目录下的docker-compose.yml文件:
  • ~/front/docker-compose.yml
  • ~/api/docker-compose.yml
如何确保在front中的容器可以向api中的容器发送请求?
我知道可以使用docker run为单个容器设置--default-gateway选项,从而为该容器分配特定的IP地址,但是似乎在使用docker-compose时无法使用此选项。
目前我会运行docker inspect my_api_container_id并查看输出中的网关。这样做是可行的,但问题是该IP是随机分配的,因此我无法依赖它。
换言之,我的问题可以转化为:
  • 是否可以使用docker-compose为特定容器指定固定IP地址?
但最终我想要得到的答案是:
  • 如何让两个不同的docker-compose项目相互通信?

9
我今天又查看了一下这个问题。开发人员最终同意允许任意网络命名。使用版本为3.5的compose文件,您可以在“networks”键下指定默认网络的名称。如果该名称的网络不存在,它将创建一个带有常规项目名称前缀的命名网络。 - cstrutton
请注意您打算建立什么类型的连接。单向连接(A->B或B->A; 在此问题中描述)还是双向连接A <-> B,后者更难实现(需要使用额外的逻辑来包装docker文件,并验证网络是否存在,因为存在相互依赖)。 - Sławomir Lenart
21个回答

716

您只需确保要相互通信的容器位于同一网络上。网络是一种一流的Docker结构,与Compose无关。

# front/docker-compose.yml
version: '2'
services:
  front:
    ...
    networks:
      - some-net
networks:
  some-net:
    driver: bridge

...

# api/docker-compose.yml
version: '2'
services:
  api:
    ...
    networks:
      - front_some-net
networks:
  front_some-net:
    external: true

注意:您的应用程序的网络名称是基于“项目名称”的,该名称基于其所在目录的名称,在此情况下添加了前缀front_

然后它们可以使用服务名称相互通信。从front,您可以执行ping api,反之亦然。


4
只有当你在容器中进行IP地址的修改才能实现Robert Moskal所说的方法。更好的方式是让它们在共同的由docker定义的网络上进行通信。 - johnharris85
16
请注意,网络名称前缀“front_”是从其所在文件夹自动生成的。因此,如果您的第一个docker-compose文件位于“example/docker-compose.yml”,则其名称将为“example_default”。 - AngryUbuntuNerd
65
您可以使用 name 属性为网络提供名称,这将禁用自动在项目名称前添加前缀。然后,任何一个项目都可以使用该网络,并在不存在时自动创建它。 - SteveB
7
请注意,name属性仅适用于docker-compose文件版本3.5及以上。 - kramer65
8
好的,我会尽力进行翻译并保持原意。以下是需要翻译的内容:有点好奇,如果我们在不同的网络上有相同的服务名称,那么调用者容器会发生什么情况?它是否可以明确指定要使用哪个网络来调用特定的服务? - Altiano Gerung
显示剩余11条评论

263

更新:从组合文件版本3.5开始:

现在可以正常工作:

version: "3.5"
services:
  proxy:
    image: hello-world
    ports:
      - "80:80"
    networks:
      - proxynet

networks:
  proxynet:
    name: custom_network

docker-compose up -d 命令将加入名为 'custom_network' 的网络,如果该网络不存在,则会创建此网络!

root@ubuntu-s-1vcpu-1gb-tor1-01:~# docker-compose up -d
Creating network "custom_network" with the default driver
Creating root_proxy_1 ... done

现在,您可以这样做:

version: "2"
services:
  web:
    image: hello-world
    networks:
      - my-proxy-net
networks:
  my-proxy-net:
    external:
      name: custom_network

这将创建一个位于外部网络的容器。

我还没有在文档中找到任何相关参考,但它确实有效!


9
第一个服务(上面的代理)创建了网络。第二个例子中的语法加入了它。 - cstrutton
7
您不能在第二个服务中将网络标记为外部,如果该网络尚不存在,则会创建它。 - SteveB
2
它确实有效。我刚刚使用最新的Docker Compose创建了一个DO droplet。我已将示例编辑为实际可工作的示例。 - cstrutton
4
以下是文档中的参考信息:https://docs.docker.com/compose/networking/#use-a-pre-existing-network。 - jeanggi90
5
就我的情况而言,这种解决方案比被接受的答案更加适合。外部网络的问题是需要按照预定顺序启动容器。对于我的客户来说,这是不可接受的。一个命名网络(自3.5版本以来)成为了完美的解决方案。谢谢。 - ygor
显示剩余9条评论

124

对@johnharris85的杰出回答做一个小补充,当你运行docker compose文件时,一个名为"default"的网络将被创建,所以你可以将它作为外部网络添加到其它的compose文件中:

只需在其他的compose文件中添加以下代码:

networks: default: external: name: default
# front/docker-compose.yml 
version: '2' 
  services:   
    front_service:
    ...

...

# api/docker-compose.yml
version: '2'
services:
  api_service:
    ...
    networks:
      - front_default
networks:
  front_default:
    external: true

对于我来说,这种方法更加适合,因为我不拥有第一个docker-compose文件并且想要与它通信。


只是想知道为这个外部网络分配静态IP的正确方法。我设法在 services: 标记内完成了它,语法应该是 networks:,然后嵌套 front_default:(删除“ - ”),然后我们嵌套一个静态IP:ipv4_address: '172.20.0.44' - Junior Mayhé
1
尽管这是真的,但可能会有这种情况,即此方法不起作用。您正在依赖于实现细节。谁能说他们不会在未来版本中更改默认网络命名方式。明确命名网络已经得到记录,并且很可能在一段时间内得到支持。 - cstrutton
@cstrutton 默认的网络名称也有文档记录。 - undefined

47

所有来自api的容器都可以使用以下配置加入front 默认网络:

# api/docker-compose.yml

...

networks:
  default:
    external:
      name: front_default

查看Docker Compose指南:使用现有网络(请参见底部)


38

大家都解释得很好,我将只添加必要的代码,并用简单的说明。

使用在docker-compose之外创建的网络(即“外部”网络),版本为3.5+docker-compose

这里可以找到更多的解释。

首先docker-compose.yml文件应该如下所示定义名称为giveItANamePlease的网络。

networks:
  my-network:
    name: giveItANamePlease
    driver: bridge

第一个 docker-compose.yml 文件的服务可以按以下方式使用网络:

networks:
  - my-network
在第二个docker-compose文件中,我们需要通过使用在第一个docker-compose文件中使用过的网络名称来代理网络,这种情况下名称为giveItANamePlease
networks:
  my-proxy-net:
    external:
      name: giveItANamePlease

现在,您可以按照以下方式在第二个docker-compose.yml文件的服务中使用my-proxy-net

networks:
  - my-proxy-net

4
终于有现代化/易于使用的答案。 - ciurlaro
2
将第二个文件中的“networks”部分与第一个文件匹配(删除对“external”的引用),这样无论哪个项目先启动都没有关系(允许任何一个自动创建网络)。 - rymo

29
前面的帖子信息是正确的,但是它没有详细说明如何链接容器,应该使用 "external_links" 进行连接。
希望这个例子能更清楚地说明:
假设您有 app1/docker-compose.yml,其中包含两个服务(svc11 和 svc12),以及 app2/docker-compose.yml,其中还有两个服务(svc21 和 svc22),并且假设您需要以交叉方式连接:
- svc11 需要连接到 svc22 的容器。 - svc21 需要连接到 svc11 的容器。
因此,配置如下所示:
此为 app1/docker-compose.yml:

version: '2'
services:
    svc11:
        container_name: container11
        [..]
        networks:
            - default # this network
            - app2_default # external network
        external_links:
            - container22:container22
        [..]
    svc12:
       container_name: container12
       [..]

networks:
    default: # this network (app1)
        driver: bridge
    app2_default: # external network (app2)
        external: true

这是 app2/docker-compose.yml 文件:


version: '2'
services:
    svc21:
        container_name: container21
        [..]
        networks:
            - default # this network (app2)
            - app1_default # external network (app1)
        external_links:
            - container11:container11
        [..]
    svc22:
       container_name: container22
       [..]

networks:
    default: # this network (app2)
        driver: bridge
    app1_default: # external network (app1)
        external: true

感谢您对Docker网络的详细解释,这非常有用。整天我都在苦苦寻找适当的解释和解决方案,但现在我已经理解了这个概念。 - Kinjal Akhani
是的,是这样的。我正在使用这个配置从2到3.7。 - Daniel Blanco
"Links"一旦引入Docker网络和Compose文件版本2后就成为了过时的概念。如果你在这个回答中确实有external_links:选项,那么删除它可能是安全的。 - David Maze

15

从Compose 1.18(规范3.5)开始,您可以使用自己的自定义名称覆盖默认网络来为所有需要的Compose YAML文件。只需将以下内容附加到它们即可:

networks:
  default:
    name: my-app

以上假定您将version设置为3.5(如果在4+中不废弃,则设置为更高版本)。

其他答案也指出了这一点;这是一个简化的摘要。


1
这里需要注意的是,网络的标识符仍然是“default”。您无法将“别名”或类似内容设置为网络“my-app”。您需要改用“default”。 - gustavz
1
补充@gustavz的评论,使用default作为网络标识符可以不需要在服务中指定要使用的网络。默认网络的名称my-app可以是任何内容,以帮助对多个Compose服务进行分组。 - emyller
请注意,所有服务在同一网络上也可以通过它们的键作为别名访问,并且不能保证哪个服务将解析特定请求。请参阅此处“别名”部分中的注释:https://docs.docker.com/compose/compose-file/#networks - rymo

14

这里有很多答案!

首先,避免在服务和网络等实体名称中使用连字符。它们会导致名称解析问题。

例如:my-api 将不起作用。 myapiapi 可以正常工作。

对我有用的是:

# api/docker-compose.yml
version: '3'

services:
  api:
    container_name: api
    ...
    ports:
      - 8081:8080
    networks:
      - mynetwork

networks:
  mynetwork:
    name: mynetwork

# front/docker-compose.yml
version: '3'

services:
  front:
    container_name: front
    ...
    ports:
      - 81:80
    networks:
      - mynetwork

networks:
  mynetwork:
    name: mynetwork

注意: 我添加了端口以展示服务如何相互访问,以及它们从主机上如何访问。

重要: 如果您不指定网络的 namedocker-compose 将为您创建一个。它使用 docker_compose.yml 文件所在文件夹的名称。在本例中: api_mynetworkfront_mynetwork。这将防止容器之间的通信,因为它们将位于不同的网络中,具有非常相似的名称。

请注意,网络在两个文件中完全相同,因此您可以先启动任何一个服务,它都可以正常工作。无需指定哪一个是外部的,在这方面 docker-compose 将为您提供管理服务。

来自主机

您可以使用 docker-compose.yml 中定义的发布端口来访问任何容器。

您可以访问 Front 容器: curl http://localhost:81

您可以访问 API 容器: curl http://localhost:8081

来自 API 容器

您可以使用原始端口而不是在 docker-compose.yml 中发布的端口访问 Front 容器。

例如: curl http://front:80

来自 Front 容器

您可以使用原始端口而不是在 docker-compose.yml 中发布的端口访问 API 容器。

例如: curl http://api:8080


似乎从版本3.5开始添加了name属性。执行docker network list会显示正确的网络名称。谢谢! - Lukenzo
@Gael 我刚试图测试您的解决方案,将Laravel(sail up)作为API和Vue作为前端项目连接起来。但是看起来仍然存在两个不同的容器,并且它们之间没有连接。您是否测试过这些框架的集成? - Andrew
@Andrew 我从未尝试过那些。尝试使用 docker ps 命令查看正在运行的容器和打开的端口,以确保您已正确设置端口。 - Gael
连接两个(或更多)Docker Compose 项目的最简单配置。 - undefined

11

更新: 截至docker-compose文件版本3.5:

我遇到了类似的问题,通过在我的docker-compose.yml项目中进行一些小改动来解决它。

例如,我们有两个API:scoring和ner。Scoring API需要向ner API发送请求以处理输入请求。为了做到这一点,它们都应该共享同一个网络。

注意:每个容器都有自己的网络,在运行docker内部应用程序时会自动创建。例如,ner API网络将被创建为ner_default,而scoring API网络将被命名为scoring_default。此解决方案适用于版本:'3'。

在上述场景中,如果我的scoring API想要与ner API通信,那么我将添加以下行。这意味着每当我为ner API创建容器时,它就会自动添加到scoring_default网络中。

networks:
  default:
      external:
        name: scoring_default

ner/docker-compose.yml

version: '3'
services:
  ner:
    container_name: "ner_api"
    build: .
    ...

networks:
  default:
      external:
        name: scoring_default

scoring/docker-compose.yml

version: '3'
services:
  api:
    build: .
    ...

我们可以通过以下命令看到上述容器现在都属于同一网络,该网络名为scoring_default:

docker inspect scoring_default

{
    "Name": "scoring_default",
        ....
    "Containers": {
    "14a6...28bf": {
        "Name": "ner_api",
        "EndpointID": "83b7...d6291",
        "MacAddress": "0....",
        "IPv4Address": "0.0....",
        "IPv6Address": ""
    },
    "7b32...90d1": {
        "Name": "scoring_api",
        "EndpointID": "311...280d",
        "MacAddress": "0.....3",
        "IPv4Address": "1...0",
        "IPv6Address": ""
    },
    ...
}

1
如果你想让scoring_api与ner_api通信,它是http://scoring_api:port还是仍然是localhost呢? - Kay
2
两个应用程序可以使用它们的名称相互通信。例如,计分应用程序可以使用"http://ner_api:port/extract"调用ner应用程序,反之亦然。 - Nomiluks
非常感谢您使用docker inspect命令并指出应该通过容器名称而非本地主机来访问端点! - Egor B Eremeev

6
你可以在所有项目中添加一个 .env 文件,其中包含 COMPOSE_PROJECT_NAME=somenameCOMPOSE_PROJECT_NAME 会覆盖用于命名资源的前缀,因此所有项目都将使用somename_default作为其网络名称,这使得服务之间可以像在同一项目中一样相互通信。
注意:你将会收到有关从其他项目创建的“孤立”容器的警告消息。

这是一个非常棒的提示,非常感谢! - Gavin Ray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接