Using Pyro4 to connect python scripts in separate containers

The Problem

I want to use Pyro4 for remote procedure calls across multiple containers using docker-compose. Currently, I am just trying to implement a simplified version of the Pyro4 warehouse example that I have setup to run on different machines, instead of the default localhost, since I am using multiple containers.

I can successfully start the Pyro name server in its own container, but in another container I can not publish the Warehouse class and start Pyro’s request loop. I am get the error OSError: [Errno 99] Cannot assign requested address

I imagine I am simply missing something in my docker-compose.yml file.
.

My attempt and additional information

I am using Host OS version balenaOS 2.46.1+rev1 and supervisor version 10.6.27, and deploying this to a Raspberry Pi 3 B+. I have a device variable “PYRO_HOST=pyro-ns” to set the address of the pyro name server.

I see the pyro name server get created

    05.02.20 15:27:33 (-0500)  pyro-ns  Broadcast server running on 0.0.0.0:9091 05.02.20 15:27:33 (-0500)  pyro-ns  NS running on pyro-ns:9090 (172.17.0.3) 
    05.02.20 15:27:33 (-0500)  pyro-ns  Warning: HMAC key not set. Anyone can connect to this server! 
    05.02.20 15:27:33 (-0500)  pyro-ns  URI = PYRO:Pyro.NameServer@pyro-ns:9090

However, I am get the error OSError: [Errno 99] Cannot assign requested address
when I try to publish the Warehouse class and start Pyro’s request loop using

    Pyro4.Daemon.serveSimple( 
    { 
        Warehouse: "example.warehouse" 
    },         
    ns=True, verbose=True)

I get the following

    05.02.20 16:52:00 (-0500)  container_B  Traceback (most recent call last): 
    05.02.20 16:52:00 (-0500)  container_B    File "src/container_B_main.py", line 33, in <module> 
    05.02.20 16:52:00 (-0500)  container_B      main() 
    05.02.20 16:52:00 (-0500)  container_B    File "src/container_B_main.py", line 30, in main 
    05.02.20 16:52:00 (-0500)  container_B      ns=True, verbose=True) 
    05.02.20 16:52:00 (-0500)  container_B    File "/usr/local/lib/python3.5/site-packages/Pyro4/core.py", line 1204, in serveSimple 
    05.02.20 16:52:00 (-0500)  container_B      daemon = Daemon(host, port) 
    05.02.20 16:52:00 (-0500)  container_B    File "/usr/local/lib/python3.5/site-packages/Pyro4/core.py", line 1141, in __init__ 
    05.02.20 16:52:00 (-0500)  container_B      self.transportServer.init(self, host, port, unixsocket) 
    05.02.20 16:52:00 (-0500)  container_B    File "/usr/local/lib/python3.5/site-packages/Pyro4/socketserver/threadpoolserver.py", line 134, in init 
    05.02.20 16:52:00 (-0500)  container_B      sslContext=sslContext) 
    05.02.20 16:52:00 (-0500)  container_B    File "/usr/local/lib/python3.5/site-packages/Pyro4/socketutil.py", line 298, in createSocket 
    05.02.20 16:52:00 (-0500)  container_B      bindOnUnusedPort(sock, bind[0]) 
    05.02.20 16:52:00 (-0500)  container_B    File "/usr/local/lib/python3.5/site-packages/Pyro4/socketutil.py", line 542, in bindOnUnusedPort 
    05.02.20 16:52:00 (-0500)  container_B      sock.bind((host, 0)) 
    05.02.20 16:52:00 (-0500)  container_B  OSError: [Errno 99] Cannot assign requested address

What am I missing that will allow Pyro to work across the multiple containers using docker-compose?

Following is my code:


docker-compose.yml

    version: '2' services:
         pyro-ns:
             privileged: true
             restart: always
             build: ./pyro-ns
             ports: - "9090:9090"
         container_A:
             privileged: true
             restart: always
             build: ./container_A
             depends_on: 
                 - pyro-ns
                 - container_B
         container_B:
             privileged: true
             restart: always
             build: ./container_B
             depends_on: 
                 - pyro-ns

pyro-ns

Dockerfile.template

    FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

    # enable container init system.
    ENV INITSYSTEM on

    # use `install_packages` if you need to install dependencies,
    # for instance if you need git, just uncomment the line below.
    # RUN install_packages git
    RUN pip install --upgrade pip
    RUN pip install Pyro4 dill

    ENV PYRO_SERIALIZERS_ACCEPTED=serpent,json,marshal,pickle,dill
    ENV PYTHONUNBUFFERED=0

    CMD ["python", "-m", "Pyro4.naming"]

    EXPOSE 9090

container_A

Dockerfile.template

    FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

    # enable container init system.
    ENV INITSYSTEM on

    # use `install_packages` if you need to install dependencies,
    # for instance if you need git, just uncomment the line below.
    # RUN install_packages git
    RUN pip install --upgrade pip
    RUN pip install Pyro4 dill

    # Set our working directory
    WORKDIR /usr/src/container_A

    # Copy requirements.txt first for better cache on later pushes
    COPY requirements.txt requirements.txt

    # pip install python deps from requirements.txt on the resin.io build server
    RUN pip install -r requirements.txt

    # This will copy all files in our root to the working  directory in the container
    COPY . ./

    # main.py will run when container starts up on the device
    CMD ["python","-u","src/container_A_main.py"]

container_A_main.py

    import Pyro4
    import Pyro4.util
    import sys

    sys.excepthook = Pyro4.util.excepthook



    try:
        print('Top of container A')
        
        warehouse = Pyro4.Proxy("PYRONAME:example.warehouse")
        
        print('The warehouse contains: ', warehouse.list_contents())
        
    except Exception as ex:
        template = "An exception of type {0} occurred. Arguments:\n{1!r}"
        message = template.format(type(ex).__name__, ex.args)
        print(message)

container_B

Dockerfile.template

    FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

    # enable container init system.
    ENV INITSYSTEM on

    # use `install_packages` if you need to install dependencies,
    # for instance if you need git, just uncomment the line below.
    # RUN install_packages git
    RUN pip install --upgrade pip

    # Set our working directory
    WORKDIR /usr/src/container_B

    # Copy requirements.txt first for better cache on later pushes
    COPY requirements.txt requirements.txt

    # pip install python deps from requirements.txt on the resin.io build server
    RUN pip install -r requirements.txt

    # This will copy all files in our root to the working  directory in the container
    COPY . ./

    # main.py will run when container starts up on the device
    CMD ["python","-u","src/container_B_main.py"]

container_B_main.py

    from __future__ import print_function
    import Pyro4



    @Pyro4.expose
    @Pyro4.behavior(instance_mode="single")
    class Warehouse(object):
        def __init__(self):
            self.contents = ["chair", "bike", "flashlight", "laptop", "couch"]

        def list_contents(self):
            return self.contents

        def take(self, name, item):
            self.contents.remove(item)
            print("{0} took the {1}.".format(name, item))

        def store(self, name, item):
            self.contents.append(item)
            print("{0} stored the {1}.".format(name, item))


    def main():

        Pyro4.Daemon.serveSimple(
            {
                Warehouse: "example.warehouse"
            },
            ns=True, verbose=True)

    if __name__ == "__main__":
        main()

For both container_A and container_B the requirements.txt file is the same.

requirements.txt

`Pyro4`

Hi Matt, I don’t have any knowledge of pyro4 or what it is, but it seems this project https://github.com/linksmart/dpa-pyro-boilerplate/blob/master/docker-compose.yml manages to run pyro in a docker-compose setup, so what I would do is try pulling out the parts of their docker-compose and testing them in your setup. The only thing that wont work is the volume: mounting as that is not supported in balena’s compose implementation. So I would focus on their networking setup and the way they initiate the command

Hi shaunmuligan, thanks for pointing me to that project’s setup. I was able to successfully get my Pyro example to run.

Following are my updated and working files for reference.



docker-compose.yml

version: '2'
services:
    pyro-ns:
        privileged: true
        restart: always
        build: ./pyro-ns
        command: ["--host=pyro-ns"]
    container_A:
        privileged: true
        restart: always
        build: ./container_A
        depends_on:
            - pyro-ns
            - container_B
    container_B:
        privileged: true
        restart: always
        build: ./container_B
        depends_on:
            - pyro-ns

pyro-ns

Dockerfile.template

FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

# enable container init system.
ENV INITSYSTEM on

# use `install_packages` if you need to install dependencies,
# for instance if you need git, just uncomment the line below.
# RUN install_packages git
RUN pip install --upgrade pip
RUN pip install Pyro4 dill

ENV PYRO_SERIALIZERS_ACCEPTED=serpent,json,marshal,pickle,dill
ENV PYTHONUNBUFFERED=0

ENTRYPOINT ["pyro4-ns"]

EXPOSE 9090

container_A

Dockerfile.template

FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

# enable container init system.
ENV INITSYSTEM on

# use `install_packages` if you need to install dependencies,
# for instance if you need git, just uncomment the line below.
# RUN install_packages git
RUN pip install --upgrade pip
RUN pip install Pyro4 dill

# Set our working directory
WORKDIR /usr/src/container_A

# Copy requirements.txt first for better cache on later pushes
COPY requirements.txt requirements.txt

# pip install python deps from requirements.txt on the resin.io build server
RUN pip install -r requirements.txt

# This will copy all files in our root to the working  directory in the container
COPY . ./

# main.py will run when container starts up on the device
CMD ["python","-u","src/container_A_main.py"]

container_A_main.py

import Pyro4
import Pyro4.util
import sys

sys.excepthook = Pyro4.util.excepthook

try:
    print('Top of container A')
    
    warehouse = Pyro4.Proxy("PYRONAME:example.warehouse")
    
    print('The warehouse contains: ', warehouse.list_contents())
    
    print('Inifite loop after running the Warehouse class via Pyro4')
    while True:
        # Infinite loop.
        pass
    
except Exception as ex:
    template = "An exception of type {0} occurred. Arguments:\n{1!r}"
    message = template.format(type(ex).__name__, ex.args)
    print(message)

container_B

Dockerfile.template

FROM balenalib/%%BALENA_MACHINE_NAME%%-python:3-stretch-run

# enable container init system.
ENV INITSYSTEM on

# use `install_packages` if you need to install dependencies,
# for instance if you need git, just uncomment the line below.
# RUN install_packages git
RUN pip install --upgrade pip

# Set our working directory
WORKDIR /usr/src/container_B

# Copy requirements.txt first for better cache on later pushes
COPY requirements.txt requirements.txt

# pip install python deps from requirements.txt on the resin.io build server
RUN pip install -r requirements.txt

# This will copy all files in our root to the working  directory in the container
COPY . ./

# main.py will run when container starts up on the device
CMD ["python","-u","src/container_B_main.py"]

EXPOSE 9100

container_B_main.py

from __future__ import print_function
import Pyro4


@Pyro4.expose
@Pyro4.behavior(instance_mode="single")
class Warehouse(object):
    def __init__(self):
        self.contents = ["chair", "bike", "flashlight", "laptop", "couch"]

    def list_contents(self):
        return self.contents

    def take(self, name, item):
        self.contents.remove(item)
        print("{0} took the {1}.".format(name, item))

    def store(self, name, item):
        self.contents.append(item)
        print("{0} stored the {1}.".format(name, item))


def main():
    Pyro4.config.SERIALIZER = 'pickle'
    daemon = Pyro4.Daemon(host="container_B", port=9100)
    uri = daemon.register(Warehouse)
    print('The uri of the registered Warehouse class')
    print(uri)
    
    try:
        ns = Pyro4.locateNS(host="pyro-ns", port=9090)
        print('The located name server')
        print(ns)
        ns.register("example.warehouse", uri)
    except Exception as ex:
        template = "An exception of type {0} occurred. Arguments:\n{1!r}"
        message = template.format(type(ex).__name__, ex.args)
        print(message)
    
    daemon.requestLoop()
   
if __name__ == "__main__":
    main()

For both container_A and container_B the requirements.txt file is the same.

requirements.txt

`Pyro4`

Good to know that it is working now and thank you for sharing :slight_smile: