Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ZeroMQ failing to communicate between two Docker containers

I'm trying to set up a toy example of Docker networking with ZeroMQ in macOS, where the serverd.py sends a message to the clientd.py and the client simply displays it using PUSH/PULL. If I run them outside of the container they work fine, but I'm having trouble getting them to communicate when running within separate containers. It seems like my clientd.py cannot connect to the container name despite them being within the same bridge network. I tried replacing the hostname with the assigned ip address for serverd_dev_1, but this doesn't work either.

Here's my setup:

  1. I created a new network with docker network create -d bridge mynet. Here is the output from docker network inpsect 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": {}
    }
    
  2. I created serverd.py and clientd.py like this and put them in separate folders along with their Dockerfiles and 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))

I have two Dockerfiles and docker-compose.yml:

Dockerfile for serverd.py:

FROM python:3.6

RUN mkdir src
ADD serverd.py /src/
RUN pip install pyzmq
WORKDIR /src/
EXPOSE 5557

Dockerfile for clientd.py:

FROM python:3.6

RUN mkdir src
ADD clientd.py /src/
RUN pip install pyzmq
WORKDIR /src/
EXPOSE 5557

docker-compose.yml for serverd.py:

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

docker-compose for clientd.py:

dev:
  build: .
  command: ["python", "-u", "./clientd.py"]
  net: mynet
  1. serverd.py starts up as expected with docker-compose up:

Sending to tcp://127.0.0.1:5557...

  1. clientd.py won't start up like this due to it not finding the hostname 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. If I replace the URI tcp://serverd_dev_1:5557 with tcp://172.18.0.2:5557 it does not crash anymore, but it is simply idling without receiving any of the messages from the server. Obviously I'm doing something wrong, but I'm not entirely sure what exactly. I feel like I have been following the Docker documentation as closely as possible, and would very much be grateful if you had any ideas.

like image 759
Jimmy C Avatar asked Aug 19 '17 13:08

Jimmy C


People also ask

Why is it difficult for Docker containers to communicate with each other?

Containers can only communicate with each other if they share a network. Containers that don't share a network cannot communicate with one another. That's one of the isolation features provided by Docker. A container can belong to more than one network, and a network can have multiple containers inside.

How do you communicate between Docker containers via hostname?

When you connect an existing container to a different network using docker network connect , you can use the --ip or --ip6 flags on that command to specify the container's IP address on the additional network. In the same way, a container's hostname defaults to be the container's ID in Docker.

What is IPC mode in Docker?

The ipcMode parameter allows you to configure your containers to share their inter-process communication (IPC) namespace with the other containers in the task, or with the host. The IPC namespace allows containers to communicate directly through shared-memory with other containers running in the same task or host.


1 Answers

Your primary problem is that you have configured your server with the address tcp://127.0.0.1:5557. Because it's bound to localhost (127.0.0.1), that socket isn't going to be visible to anything outside of that container. So the first thing you need to fix is the server bind address. Consider:

address = "tcp://0.0.0.0:5557"

A second problem is that you're using the name serverd_dev_1 in the client, but it's not clear this would actually be the name of your serverd container(that would depend on the directory names in use when you run docker-compose up).

Naming is easier to manage with a single docker-compose.yaml file. For example, I set things up like this:

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

This will launch both containers in a dedicated network (because this is what docker-compose does by default), so you don't need to explicitly create or reference mynet.

As you can probably infer from the above, I modified your code to get the ZMQ uri from an environment variable, because this made it easier to experiment. You can find the above docker-compose.yaml and the modified code at:

  • https://github.com/larsks/so-example-jimmyc

Update

In case you really want/need to have two separate docker-compose.yaml files, I've updated the example to include per-service files. These examples use the alias option to provide a name by which the client can contact the server, regardless of your local directory layout:

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

This configuration requires that you create mynet before bringing up the containers.

like image 144
larsks Avatar answered Oct 17 '22 01:10

larsks