PyZMQ Dockerized pub sub - sub 不会接收消息
PyZMQ Dockerized pub sub - sub won't receive messages
我想构建一个模块化系统,其中的模块通过 ZeroMQ 进行通信。为了提高可用性,我想 Docker 化这些模块中的(一些),以便用户不必设置环境。
但是,我无法让 docker 化的发布者让非 docker 化的订阅者接收其消息。
系统
- 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'
)
Docker文件
我创建了以下 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"]
然后我使用以下命令成功构建了一个 Docker 化的发布者:sudo docker build . -t foo/bar
尝试次数
尝试 1
现在我有了带有发布者的 docker 容器,我正在尝试让我的非 docker 化订阅者接收数据。我 运行 以下 2 个命令:
python zmq_sub.py
sudo docker run -it foo/bar
我看到我的发布者在容器中发布数据,但我的订阅者什么也没收到。
尝试 2
考虑到我必须将 docker 化发布者的内部端口映射到我机器的端口,我 运行 以下 2 个命令:
python zmq_sub.py
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.
相同
问题
如何让 docker 化的发布者向我的非 docker 化的订阅者发布数据?
代码
编辑 1: 代码已更新,还给出了如何使用 docker-compose
进行自动 IP 推断的示例。
这主要是一个 docker 网络问题,并不特定于 pyzmq 或 zeromq。任何尝试从容器连接到主机的东西都会遇到同样的问题。
需要说明的是,在此示例中,您在主机(zmq_sub.py,它调用绑定)上有一个 服务器 运行,并且您想要从 docker 容器 (zmq_pub.py) 中的客户端 运行 连接到它。
由于 docker 容器是连接的容器,因此您不需要进行任何 docker 端口公开或转发。 EXPOSE 和转发端口仅用于使 连接到 容器成为可能(即在容器中调用 bind
),而不是从 建立出站连接 一个容器,这就是这里发生的事情。
这里最主要的是,当涉及到与 docker 的网络时,您应该将每个容器视为本地网络上的独立机器。为了可以从其他容器或主机连接,服务应该绑定到所有接口(或至少是一个可访问的接口)。在容器中绑定本地主机意味着只有该容器 中的其他进程 应该能够与其通信。同样,在主机上绑定 localhost 意味着 docker 容器不应该能够连接。
所以第一个变化是你的绑定 url 应该是:
url = 'tcp://0.0.0.0:5550'
...
socket.bind(url)
或选择与您的 docker 虚拟网络相对应的 IP 地址。
然后,您的 connect
url 需要是从容器中看到的主机的 ip。这可以通过 ifconfig
找到。通常任何 IP 地址都可以,但如果您有 docker0
网络,那将是合乎逻辑的选择。
我在 遇到了同样的问题。
您的问题是容器 localhost (127.0.0.1
) 与其他容器或主机 localhost 不同。
所以要克服这个问题,请在 .bind()
中使用 tcp://*:5550
而不是 127.0.0.1
或机器 IP。
然后,您应该公开 IP 并声明在容器和主机之间分配 IP(我使用 docker-compose 在上面提到的 SO post 上执行此操作) .我认为在您的情况下将如下所示:
EXPOSE 5550
和
sudo docker run -p 5550:5550 -it foo/bar
我想构建一个模块化系统,其中的模块通过 ZeroMQ 进行通信。为了提高可用性,我想 Docker 化这些模块中的(一些),以便用户不必设置环境。 但是,我无法让 docker 化的发布者让非 docker 化的订阅者接收其消息。
系统
- 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'
)
Docker文件
我创建了以下 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"]
然后我使用以下命令成功构建了一个 Docker 化的发布者:sudo docker build . -t foo/bar
尝试次数
尝试 1
现在我有了带有发布者的 docker 容器,我正在尝试让我的非 docker 化订阅者接收数据。我 运行 以下 2 个命令:
python zmq_sub.py
sudo docker run -it foo/bar
我看到我的发布者在容器中发布数据,但我的订阅者什么也没收到。
尝试 2
考虑到我必须将 docker 化发布者的内部端口映射到我机器的端口,我 运行 以下 2 个命令:
python zmq_sub.py
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.
问题
如何让 docker 化的发布者向我的非 docker 化的订阅者发布数据?
代码
编辑 1: 代码已更新,还给出了如何使用 docker-compose
进行自动 IP 推断的示例。
这主要是一个 docker 网络问题,并不特定于 pyzmq 或 zeromq。任何尝试从容器连接到主机的东西都会遇到同样的问题。
需要说明的是,在此示例中,您在主机(zmq_sub.py,它调用绑定)上有一个 服务器 运行,并且您想要从 docker 容器 (zmq_pub.py) 中的客户端 运行 连接到它。
由于 docker 容器是连接的容器,因此您不需要进行任何 docker 端口公开或转发。 EXPOSE 和转发端口仅用于使 连接到 容器成为可能(即在容器中调用 bind
),而不是从 建立出站连接 一个容器,这就是这里发生的事情。
这里最主要的是,当涉及到与 docker 的网络时,您应该将每个容器视为本地网络上的独立机器。为了可以从其他容器或主机连接,服务应该绑定到所有接口(或至少是一个可访问的接口)。在容器中绑定本地主机意味着只有该容器 中的其他进程 应该能够与其通信。同样,在主机上绑定 localhost 意味着 docker 容器不应该能够连接。
所以第一个变化是你的绑定 url 应该是:
url = 'tcp://0.0.0.0:5550'
...
socket.bind(url)
或选择与您的 docker 虚拟网络相对应的 IP 地址。
然后,您的 connect
url 需要是从容器中看到的主机的 ip。这可以通过 ifconfig
找到。通常任何 IP 地址都可以,但如果您有 docker0
网络,那将是合乎逻辑的选择。
我在
您的问题是容器 localhost (127.0.0.1
) 与其他容器或主机 localhost 不同。
所以要克服这个问题,请在 .bind()
中使用 tcp://*:5550
而不是 127.0.0.1
或机器 IP。
然后,您应该公开 IP 并声明在容器和主机之间分配 IP(我使用 docker-compose 在上面提到的 SO post 上执行此操作) .我认为在您的情况下将如下所示:
EXPOSE 5550
和
sudo docker run -p 5550:5550 -it foo/bar