PyZMQ Dockerized pub sub子将不会收到消息

时间:2018-12-16 13:37:45

标签: python-3.x docker docker-compose zeromq pyzmq

我想构建一个模块化的系统,其中包含通过ZeroMQ进行通信的模块。为了提高可用性,我想对其中一些模块进行Dockerize,以便用户不必设置环境。 但是,我无法让未授权的订阅服务器接收到未授权的发布者的消息。

系统

  • Ubuntu 18.04
  • Python 3.7
  • libzmq版本4.2.5
  • pyzmq版本为17.1.2
  • Docker版本18.09.0,构建4d60db4

最小测试用例

zmq_sub.py

# CC0

import zmq


def main():
    # ZMQ connection
    url = "tcp://127.0.0.1:5550"
    ctx = zmq.Context()
    socket = ctx.socket(zmq.SUB)
    socket.bind(url)  # subscriber creates ZeroMQ socket
    socket.setsockopt(zmq.SUBSCRIBE, ''.encode('ascii'))  # any topic
    print("Sub bound to: {}\nWaiting for data...".format(url))

    while True:
        # wait for publisher data
        topic, msg = socket.recv_multipart()
        print("On topic {}, received data: {}".format(topic, msg))


if __name__ == "__main__":
    main()

zmq_pub.py

# CC0

import zmq
import time


def main():
    # ZMQ connection
    url = "tcp://127.0.0.1:5550"
    ctx = zmq.Context()
    socket = ctx.socket(zmq.PUB)
    socket.connect(url)  # publisher connects to subscriber
    print("Pub connected to: {}\nSending data...".format(url))

    i = 0

    while True:
        topic = 'foo'.encode('ascii')
        msg = 'test {}'.format(i).encode('ascii')
        # publish data
        socket.send_multipart([topic, msg])  # 'test'.format(i)
        print("On topic {}, send data: {}".format(topic, msg))
        time.sleep(.5)

        i += 1


if __name__ == "__main__":
    main()

当我打开2个终端并运行时:

  • python zmq_sub.py
  • python zmq_pub.py

订户无误地接收到数据(On topic b'foo', received data: b'test 1'

Dockerfile

我创建了以下Dockerfile

FROM python:3.7.1-slim

MAINTAINER foo bar <foo@spam.eggs>

RUN apt-get update && \
  apt-get install -y --no-install-recommends \
  gcc

WORKDIR /app
COPY requirements.txt /app
RUN pip install -r requirements.txt

COPY zmq_pub.py /app/zmq_pub.py

EXPOSE 5550

CMD ["python", "zmq_pub.py"]

然后我使用以下命令成功构建了一个Dockerized发布者:sudo docker build . -t foo/bar

尝试

尝试1

现在,我将Docker容器与发布者一起使用,我正在尝试让非dockerized订阅者接收数据。我运行以下2条命令:

  1. python zmq_sub.py
  2. sudo docker run -it foo/bar

我看到我的发布者在容器中发布数据,但是我的订阅者什么也没收到。

尝试2

出于这个想法,我必须将dockerized发布者的内部端口映射到机器的端口,我运行以下2条命令:

  1. python zmq_sub.py
  2. sudo docker run -p 5550:5550 -it foo/bar

但是,我收到以下错误:docker: Error response from daemon: driver failed programming external connectivity on endpoint objective_shaw (09b5226d89a815ce5d29842df775836766471aba90b95f2e593cf5ceae0cf174): Error starting userland proxy: listen tcp 0.0.0.0:5550: bind: address already in use.

在我看来,我的订户已经绑定到127.0.0.1:5550,因此,当我尝试映射它时,Docker无法再执行此操作。如果将其更改为-p 5549:5550,则Docker不会给出错误,但情况与尝试1相同。

问题

我如何让我的dockerized发布者将数据发布给我的非dockerized订户?

代码

编辑1:代码已更新,还提供了有关如何使用docker-compose进行自动IP推断的示例。

GitHub:https://github.com/NumesSanguis/pyzmq-docker

2 个答案:

答案 0 :(得分:2)

这主要是一个docker网络问题,并不特定于pyzmq或zeromq。尝试从容器连接到主机的任何事情都会遇到相同的问题。

为清楚起见,在此示例中,您在主机上运行了一个 server (zmq_sub.py,它调用bind),并且您想从运行在docker内部的客户端连接到该服务器容器(zmq_pub.py)。

由于docker容器是一个连接容器,因此您无需进行任何docker端口公开或转发。 EXPOSE和转发端口仅用于将连接到容器(即在容器中调用bind),而不能从 建立出站连接容器,这就是这里发生的事情。

这里的主要内容是,在与docker联网时,您应该将每个容器视为本地网络上的一台单独的计算机。为了可以从其他容器或主机进行连接,服务应 bind 绑定到所有接口(或至少一个可访问的接口)上。在容器中的localhost上绑定意味着只有该容器中的其他进程应该能够与之对话。同样,在主机上绑定本地主机意味着docker容器不应该能够连接。

因此,第一个更改是您的绑定网址应为:

url = 'tcp://0.0.0.0:5550'
...
socket.bind(url)

或选择与您的docker虚拟网络相对应的IP地址。

然后,您的connect url必须是从容器中看到的主机的ip。可以通过ifconfig找到。通常,任何IP地址都可以,但是如果您有docker0网络,那将是合理的选择。

答案 1 :(得分:1)

我在this post中遇到了同样的问题。

您的问题是容器localhost(127.0.0.1)与其他容器或主机localhost有区别。

因此,要解决此问题,请在tcp://*:5550中使用.bind()而不是127.0.0.1或计算机IP。

然后,您应该设置一个公开IP,并在容器和主机之间声明分配IP(我在docker-compose中使用的是在上面提到的SO帖子上执行的操作)。我认为您的情况如下所示:

EXPOSE 5550

sudo docker run -p 5550:5550 -it foo/bar