Good afternoon. I am brand new to Docker and I'm struggling to come up with a scalable solution for generating network traffic. I want to have many client and server containers talking with each other across a docker bridge network. Right now I have one client and one server container, and they talk to each other but it is incredibly slow. Additionally, I had to hard-code their IP addresses for them to be able to find each other. There has to be a better way to scale this up, but I'm struggling because my client.py code needs the server's IP address and port number to find the server.
Client.py
from pyModbusTCP.client import ModbusClient
import time
c = ModbusClient()
c.host("172.20.0.2")
c.port(502)
while True:
if not c.is_open():
if not c.open():
print("Unable to connect to Server at 172.20.0.2:502")
if c.is_open():
regs = c.read_holding_registers(0, 4)
if regs:
print("Register #0: " + str(regs[0]))
print("Register #1: " + str(regs[1]))
print("Register #2: " + str(regs[2]))
print("Register #3: " + str(regs[3]))
time.sleep(5)
Client DockerFile
FROM python:3
WORKDIR ~/client-server-docker-test/client
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "./client.py"]
Server.py
from pyModbusTCP.server import ModbusServer, DataBank
from time import sleep
from random import uniform
# set up server
server = ModbusServer('localhost',502, no_block=True)
# initialize register 0 with value of 80
DataBank.set_words(0, [80])
try:
print("Start server...")
server.start()
print("Server is online...")
# change register value every 5 seconds.
while True:
# Set Register @ Address 0 to random int. value
DataBank.set_words(0, [int(uniform(0,100))])
# Tank 2
DataBank.set_words(1, [int(uniform(0,100))])
# Tank 3
DataBank.set_words(2, [int(uniform(0,100))])
# Tank 4
DataBank.set_words(3, [int(uniform(0,100))])
sleep(5)
# when hit ctrl+C in CMD line, shut down server
except:
print("Shutdown server....")
server.stop()
print("Server is offline...")
sleep(0.5)
Server DockerFile
FROM python:3
WORKDIR ~/client-server-docker-test/server
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "./server.py"]
Docker-Compose File
version: '2.1'
services:
server:
image: server1
container_name: server1_con
ports:
- 502:502
networks:
test_net:
ipv4_address: 172.20.0.2
client:
image: client1
container_name: client1_con
depends_on:
- server
networks:
test_net:
ipv4_address: 172.20.0.3
networks:
test_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
This runs, but it takes forever for my client to print out register values to the command line. Besides being slow, my current method isn't scalable. I have to use the MODBUS TCP protocol for communications, which is why the python code uses pyModbusTCP. But there has to be a way to have 10 client and server containers talking to each other without having to specify an IP address for each one?
I've looked into docker swarm, but I couldn't see how it would help my problem about assigning static IP addresses for each container. I've also tried to add an environmental variable where docker gets the IP address of the server and passes it into the client.py code, but I couldn't get it to work correctly. I'm very stuck, and any help would be greatly appreciated. Thank you so much for your time.
Edit: Its incredibly slow means when I run "docker-compose up", I get a notification "Attaching to server1_con, client1_con" but then it takes around 10 minutes for my command line to start showing print outs.
There are a number of issues here; firstly with regards to "around 10 minutes for my command line to start showing print outs"; run python in unbuffered mode e.g.:
CMD ["python","-u", "client.py"]
The second issue is with the way you are starting the server ModbusServer('localhost',502, no_block=True)
- this will bind to localhost and not be accessible outside of the host (or container in this case). Use ModbusServer('0.0.0.0',502, no_block=True)
to bind to all addresses on the host.
Third is removing the need to hardcode IP addresses; update your docker-compose.yml
to (note: I have added build
paths):
version: '2.1'
services:
server:
build: ./server
networks:
- test_net
client:
build: ./client
depends_on:
- server
networks:
- test_net
networks:
test_net:
driver: bridge
ipam:
driver: default
and change client.py
to use the service name c.host("server")
. With those changes your application does what I believe you expect when I run it on my machine.
Compose will also provide a
default
network that's configured exactly as you've shown. You can delete all of thenetworks:
blocks in the entire file, and the containers will use thisdefault
network.