2

I have a Django REST service and another Flask service that work as a broker for the application. Both are different projects that run with their own Docker container. I'm able to POST a product on the Django service that is consumed by the Flask service, however, I cannot reach the Django service via Flask. These containers are running on the same network, and I already tried Thomasleveil's suggestions, including docker-host by qoomon. The error received by the request is the same as before I tried to forward the traffic. The difference is that now when I do the request it keeps hanging for a while until it fails.

The error is as follows: requests.exceptions.ConnectionError: HTTPConnectionPool(host='172.17.0.1', port=8000): Max retries exceeded with url: /api/user (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f0039388340>: Failed to establish a new connection: [Errno 110] Connection timed out'))

The request I'm trying to make is a POST at /api/products/1/like. At the moment, no body is required.

Here is how I'm doing the POST with Flask, where the IP is the Docker IP:

@app.route("/api/products/<int:id>/like", methods=["POST"])
def like(id):
    req = requests.get("http://172.17.0.1:8000/api/user")
    json = req.json()

    try:
        product_user = ProductUser(user_id=json["id"], product=id)
        db.session.add(product_user)
        db.session.commit()

        publish("product_liked", id)
    except:
        abort(400, "You already liked this product")

    return jsonify({
        "message": "success"
    })

Django's docker compose file (please ignore the service tcp_message_emitter):

version: '3.8'
services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python manage.py runserver 0.0.0.0:8000"
    ports:
      - 8000:8000
    volumes:
      - .:/app
    depends_on:
      - db

  queue:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python consumer.py"
    depends_on:
      - db

  db:
    image: mysql:5.7.22
    restart: always
    environment:
      MYSQL_DATABASE: admin
      MYSQL_USER: root
      MYSQL_PASSWORD: root
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - .dbdata:/var/lib/mysql
    ports:
      - 33066:3306

  dockerhost:
    image: qoomon/docker-host
    cap_add:
      - NET_ADMIN
      - NET_RAW
    restart: on-failure
    networks:
      - backend

  tcp_message_emitter:
    image: alpine
    depends_on:
      - dockerhost
    command: [ "sh", "-c", "while :; do date; sleep 1; done | nc 'dockerhost' 2323 -v"]
    networks:
      - backend

networks:
  backend:
    driver: bridge

Flask's docker compose file:

version: '3.8'
services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python main.py"
    ports:
      - 8001:5000
    volumes:
      - .:/app
    depends_on:
      - db

  queue:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python consumer.py"
    depends_on:
      - db

  db:
    image: mysql:5.7.22
    restart: always
    environment:
      MYSQL_DATABASE: main
      MYSQL_USER: root
      MYSQL_PASSWORD: root
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - .dbdata:/var/lib/mysql
    ports:
      - 33067:3306

At this point, I know that I am missing some detail or that I've a misconfiguration.

You can have a look at the repo here: https://github.com/lfbatista/ms-ecommerce

Any help would be appreciated.

Luis Batista
  • 49
  • 2
  • 2
  • 15
  • Where does the IP address 172.17.0.1 come from? Can you run both Compose files on the same network, as described in [Communication between multiple docker-compose projects](https://stackoverflow.com/questions/38088279/communication-between-multiple-docker-compose-projects)? – David Maze Jan 09 '21 at 12:33
  • 172.17.0.1 is Django's Docker IP that I get when using the image docker-host. You can get the IP running `docker network inspect bridge --format='{{( index .IPAM.Config 0).Gateway}}'`. I'm going to try your suggestion. – Luis Batista Jan 09 '21 at 12:46
  • 2
    You might need to look at the `django_default` network instead (that is, the `default` network Compose creates for the Django-application Compose file). It will be different from the "default bridge network" (and won't be constant if you tear down and recreate these sets of containers). – David Maze Jan 09 '21 at 12:52
  • 1
    You should never hardcode container ip addresses in your code. That's what name resolution is for. If you place all your containers on the same network, they will be able to refer to each other by name. – larsks Jan 09 '21 at 13:10
  • @DavidMaze If I set a new network on my Django docker compose file, as it is in [ Communication between multiple docker-compose projects](https://stackoverflow.com/questions/38088279/communication-between-multiple-docker-compose-projects), I will lose the connection to MySQL. Indeed, using `django_default` network seems the right path. The issue is that I can't have a way to get the container's IP. The IP that I get from the above command is the IP from the `default` network. – Luis Batista Jan 09 '21 at 13:14
  • @larsks I'm aware of that, and I don't intend to leave the IP as it is. Can you point me to an example where I can place my containers on the same network? – Luis Batista Jan 09 '21 at 13:21
  • Read through all of the answers to that question: you can configure the `default` network of one file to be the `default` network of the other. – David Maze Jan 09 '21 at 14:40
  • You can place containers in multiple compose on the same network by creating the network outside of docker-compose (`docker network create mynetwork`), and then setting that up as an `external` network in your compose files. See e.g. [this answer](https://stackoverflow.com/a/65629926/147356). – larsks Jan 09 '21 at 17:39

1 Answers1

1

These containers are not actually on the same network. To put two containers from different docker-compose projects into one network you need to 'import' an existing network in one of the files. Here's how you can do it:

# first project
networks:
  internal:
  shared:
---
# second project
networks:
  internal:
  shared:
    # This is where all the magic happens:
    external: true  # Means do not create a network, import existing.
    name: admin_shared  # Name of the existing network. It's usually made of <folder_name>_<network_name> .

Do not forget to put all services into the same internal network or they will not be able to communicate with each other. If you forget to do that Docker will create a <folder_name>-default network and put any container with no explicitly assigned network there. You can assign networks like this:

services:
  backend:
    ...
    networks:
      internal:
      # Since this service needs access to the service in another project
      # you put here two networks.
      shared:
        # This part is relevant for this specific question because
        # both projects has services with identical names. To avoid
        # mess with DNS names you can add an additional name to the
        # service using 'alias'. This particular service will be
        # available in shared network as 'flask-backend'.
        aliases:
        - flask-backend

  db:
    ...
    # You can also assign networks as an array if you need no extra configuration:
    networks:
      - internal

And here are the files from your repository. Instead of IP-address one service can reach the other via flask-backend or django-backend respectively. Note that I cut out those strange 'host network containers'.

admin/docker-compose.yml:

version: '3.8'
services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python manage.py runserver 0.0.0.0:8000"
    ports:
      - 8000:8000
    volumes:
      - .:/app
    depends_on:
      - db
    networks:
      internal:
      shared:
        aliases:
        - django-backend

  queue:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python consumer.py"
    depends_on:
      - db
    networks:
      - internal

  db:
    image: mysql:5.7.22
    restart: always
    environment:
      MYSQL_DATABASE: admin
      MYSQL_USER: root
      MYSQL_PASSWORD: root
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - .dbdata:/var/lib/mysql
    ports:
      - 33066:3306
    networks:
      - internal

networks:
  internal:
  shared:

main/docker-compose.yml:

version: '3.8'
services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    command: "python main.py"
    networks:
      internal:
      shared:
        aliases:
        - flask-backend
    ports:
      - 8001:5000
    volumes:
      - .:/app
    depends_on:
      - db

  queue:
    networks:
      - internal
    build:
      context: .
      dockerfile: Dockerfile
    command: "python consumer.py"
    depends_on:
      - db

  db:
    image: mysql:5.7.22
    restart: always
    networks:
      - internal
    environment:
      MYSQL_DATABASE: main
      MYSQL_USER: root
      MYSQL_PASSWORD: root
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - .dbdata:/var/lib/mysql
    ports:
      - 33067:3306

networks:
  internal:
  shared:
    external: true
    name: admin_shared
anemyte
  • 17,618
  • 1
  • 24
  • 45
  • Still no luck. Which IP are you setting on main.py? – Luis Batista Jan 12 '21 at 08:49
  • 1
    @LuisBatista I used `django-backend`. An IP-address is something highly unreliable with Docker. Try using `ping` or `curl` inside backend containers - you will see they can reach one another. From a directory with `docker-compose.yml`: `docker-compose exec backend ping ` replace with `django-backend` or `flask-backend`. For curl add port to target: `django-backend:8000`, `flask-backend:5000`. – anemyte Jan 12 '21 at 09:03
  • 1
    @LuisBatista Also note the line in django container output: `backend_1 | Invalid HTTP_HOST header: 'django-backend:8000'. You may need to add 'django-backend' to ALLOWED_HOSTS.` – anemyte Jan 12 '21 at 09:09
  • I confirm that each container can reach each other, but making a request to port `5000` on `flask-backend` I get the error: `sqlalchemy.exc.OperationalError: (MySQLdb._exceptions.OperationalError) (2005, "Unknown MySQL server host 'db' (-3)")` – Luis Batista Jan 12 '21 at 09:35
  • 1
    @LuisBatista I have no such error, I have another instead: "Table 'main.product' doesn't exist" so connection works for me. You didn't forget to put the `db` on `internal` network and restart the container, did you? – anemyte Jan 12 '21 at 09:44
  • Indeed, I was missing that parameter :). Although, I can not reach the containers from my local network. Do you know how can I do that? I'm giving the bounty regardless. – Luis Batista Jan 12 '21 at 10:52
  • @LuisBatista you have forwarded ports with `ports: - 8000:8000`. The left port is the port on the host, another one is in the container. That is you can access a container with `:`. – anemyte Jan 12 '21 at 10:58