ZeroMQ在两个Docker容器之间通信失败

12
我试图在macOS上使用ZeroMQ和Docker网络设置一个玩具示例,其中serverd.pyclientd.py发送一条消息,客户端只需使用PUSH/PULL显示它。如果我在容器外部运行它们,它们可以正常工作,但是当我在单独的容器中运行时,我遇到了通信问题。尽管它们在同一桥接网络中,但似乎我的clientd.py无法连接到容器名称。我尝试用serverd_dev_1的分配IP地址替换主机名,但这也不起作用。
以下是我的设置:
1.我使用docker network create -d bridge mynet创建了一个新网络。这是来自docker network inspect mynet的输出:
{
    "Name": "mynet",
    "Id": "cec7f8037c0ef173d9a9a66065bb46cb6a631fea1c0636876ccfe5a792f92412",
    "Created": "2017-08-19T09:52:44.8034344Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": {},
        "Config": [
            {
                "Subnet": "172.18.0.0/16",
                "Gateway": "172.18.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
        "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {
        "5fa8dc2f8059d675dfd3dc4f2e50265be99361cd8a8f2730eb273772c0148742": {
            "Name": "serverd_dev_1",
            "EndpointID": "3a62e82b1b34d5c08f2a9f340ff93aebd65c0f3dfde70e354819befe21422d0b",
            "MacAddress": "02:42:ac:12:00:02",
            "IPv4Address": "172.18.0.2/16",
            "IPv6Address": ""
        },
        "ec1e5f8c525ca8297611e02bcd3a64198fda3a07ce8ed82c0c4298609ba0357f": {
            "Name": "clientd_dev_1",
            "EndpointID": "a8ce6f178a225cb2d39ac0009e16c39abdd2dae02a65ba5fd073b7900f059bb8",
            "MacAddress": "02:42:ac:12:00:03",
            "IPv4Address": "172.18.0.3/16",
            "IPv6Address": ""
        }
    },
    "Options": {},
    "Labels": {}
}
  • 我创建了像这样的serverd.pyclientd.py,并将它们与其Dockerfiles和docker-compose.yml文件放在不同的文件夹中:

  • serverd.py:

    import zmq
    import time
    
    context = zmq.Context()
    socket = context.socket(zmq.PUSH)
    address = "tcp://127.0.0.1:5557"
    socket.bind(address)
    print("Sending to {}...".format(address))
    while True:
        message = socket.send_string("Got it!")
        print("Sent message")
        time.sleep(1)
    

    clientd.py:

    import zmq
    
    context = zmq.Context()
    socket = context.socket(zmq.PULL)
    address = "tcp://serverd_dev_1:5557"
    socket.connect(address)
    print("Listening to {}...".format(address))
    while True:
        message = socket.recv_string()
        print("Client got message! {}".format(message))
    

    我有两个Dockerfile和docker-compose.yml:

    服务器端的Dockerfile:

    FROM python:3.6
    
    RUN mkdir src
    ADD serverd.py /src/
    RUN pip install pyzmq
    WORKDIR /src/
    EXPOSE 5557
    

    clientd.py的Dockerfile:

    FROM python:3.6
    
    RUN mkdir src
    ADD clientd.py /src/
    RUN pip install pyzmq
    WORKDIR /src/
    EXPOSE 5557
    

    serverd.py 的 docker-compose.yml:

    dev:
      build: .
      command: ["python", "-u", "./serverd.py"]
      net: mynet
    

    clientd.py 的 Docker Compose:

    dev:
      build: .
      command: ["python", "-u", "./clientd.py"]
      net: mynet
    
    1. serverd.py 通过 docker-compose up 正常启动:

    发送到 tcp://127.0.0.1:5557...

    1. clientd.py 无法像此方式启动,因为它找不到主机名 tcp://serverd_dev_1:5557

      Attaching to countd_dev_1
      dev_1  | Traceback (most recent call last):
      dev_1  |   File "./countd.py", line 6, in <module>
      dev_1  |     socket.connect(address)
      dev_1  |   File "zmq/backend/cython/socket.pyx", line 528, in zmq.backend.cython.socket.Socket.connect (zmq/backend/cython/socket.c:5971)
      dev_1  |   File "zmq/backend/cython/checkrc.pxd", line 25, in zmq.backend.cython.checkrc._check_rc (zmq/backend/cython/socket.c:10014)
      dev_1  | zmq.error.ZMQError: Invalid argument
      
    2. 如果我用tcp://172.18.0.2:5557替换URI tcp://serverd_dev_1:5557,程序不再崩溃,但是一直空闲而没有接收到任何来自服务器的消息。显然我做错了什么,但是我不确定具体是什么。我觉得我已经尽可能地按照Docker文档进行操作,非常感谢如果您有任何想法。


    我的怀疑是设备的docker隔离。您介意在**.bind()端以IPv4绝对格式设置目标地址,例如address = "tcp://172.18.0.2:5557",以不依赖任何DNS解析,并将其用作客户端端口的.connect()目标吗?EXPOSE**似乎只是在主机0/S资源管理和用于隔离的docker抽象之间进行端口管理,因此内部(容器内部)代码应该使用自己的视角中正确设置的<transport-class>:\\address:port#(从内部人员的角度来看)。 - user3666197
    1个回答

    19
    你的主要问题在于你已经将服务器配置为地址tcp://127.0.0.1:5557。因为它绑定到本地主机(127.0.0.1),所以该套接字不会对容器外部的任何东西可见。因此,你需要解决的第一件事是服务器绑定地址。可以考虑以下内容:
    address = "tcp://0.0.0.0:5557"
    

    第二个问题是你在客户端使用了名称serverd_dev_1,但不清楚这是否实际上是你的服务器容器的名称(这取决于运行docker-compose up时使用的目录名称)。

    使用单个docker-compose.yaml文件更容易管理命名。例如,我将其设置如下:

    version: "2"
    
    services:
      serverd:
        build: serverd
        command: ["python", "-u", "./serverd.py"]
        environment:
          SERVER_LISTEN_URI: tcp://0.0.0.0:5557
    
      clientd:
        build: clientd
        command: ["python", "-u", "./clientd.py"]
        environment:
          SERVER_CONNECT_URI: tcp://serverd:5557
    

    这将启动两个容器,它们将在一个专用网络中运行(因为这是docker-compose的默认设置),所以您不需要显式地创建或引用“mynet”。
    从上面的内容中,您可能可以推断出,我修改了您的代码,从环境变量中获取了ZMQ uri,因为这样更容易进行实验。您可以在以下位置找到上述docker-compose.yaml和修改后的代码:

    更新

    如果您确实希望/需要拥有两个单独的docker-compose.yaml文件,我已经更新了示例以包括每个服务文件。这些示例使用alias选项来提供一个名称,客户端可以使用该名称联系服务器,而不管您的本地目录布局如何:

    version: "2"
    
    services:
      serverd:
        build: .
        command: ["python", "-u", "./serverd.py"]
        environment:
          SERVER_LISTEN_URI: tcp://0.0.0.0:5557
        networks:
          mynet:
            aliases:
              - serverd
    
    networks:
      mynet:
        external: True
    

    在启动容器之前,此配置要求您先创建mynet


    恕我直言,O/P已经从一开始就发布了测试代码(上面的item 2,包括serverd.pyclientd.py)。代码(已经正确修复了<transport-class>、DNS/主机名解析和IPv4-address:port#)可以正常工作,因此零MQ部分存在错误的假设是不正确的。 - user3666197
    @larsks感谢您如此详细的回答!我对两个部分感到困惑:我假设由于使用端口转发到主机时使用localhost的示例,因此在使用docker network时也会使用它。其次,我认为Containers->Name在'docker network inspect mynet'中是要用作主机名的名称。您建议将docker-compose.yml组合在一起使一切变得更好。感谢您抽出时间来! - Jimmy C
    @user3666197 看起来抱怨错误的人已经撤回了他的评论,所以我也会删除我的评论,因为它们与答案没有实质性关联... - larsks
    @larsks 厉害了!作为一个 Docker 新手,您的努力真的在许多方面帮助了我。正是像您这样的人使得 stackoverflow 成为如此惊人的资源。 - Paul O

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