Search code examples
dockerdocker-composedocker-swarmhaproxy

Migrate docker-compose to a single node docker-swarm cluster


At the moment I have implemented a flask application, connected with mysql database, and the entire implementation is running on a single webserver.

In order to avoid exposing my app publicly, I am running it on the localhost interface of the server, and I am only exposing the public interface (port 443), via a haproxy that redirects the traffic to localhost interface.

The configuration of docker-compose and haproxy can be found below

docker-compose:

version: '3.1'

services:

  db:
    image: mysql:latest
    volumes:
      - mysql-volume:/var/lib/mysql
    container_name: mysql
    ports:
      - 127.0.0.1:3306:3306
    environment:
            MYSQL_ROOT_PASSWORD: xxxxxx

  app:
    #environment:
    #  - ENVIRONMENT=stage
    #  - BUILD_DATETIME=$(date +'%Y-%m-%d %H:%M:%S')
    build:
      context: .
      dockerfile: Dockerfile
      #labels:
      #  - "build_datetime=${BUILD_DATETIME}"
    container_name: stage_backend
    ports:
      - 127.0.0.1:5000:5000

volumes:
  mysql-volume:
    driver: local

sample haproxy configuration:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 10s
        timeout client  30s 
        timeout server  30s

frontend test
        bind *:80
        bind *:443 ssl crt /etc/letsencrypt/live/testdomain.com/haproxy.pem alpn h2,http/1.1
        redirect scheme https if !{ ssl_fc }
        mode http
        acl domain_testdomain hdr_beg(host) -i testdomain.com
        use_backend web_servers if domain_testdomain

backend web_servers
        timeout connect 10s
        timeout server 100s
        balance roundrobin
        mode http
        server test_server 127.0.0.1:5000 check


So haproxy is running on the public interface as a service via systemd (not containerized) and containers are running on localhost.

This is going to become a production setup soon, so I want to deploy a single node docker swarm cluster, within that server only, as docker swarm implementation is more safe on a production environment.

My question is how can I deploy that on docker swarm.

  • Does it make sense to leave haproxy as a systemd service and somehow to make it forward requests to the docker swarm cluster?

  • Is it easier/better implementation, to also containerize the haproxy and put it inside the cluster as a docker-compose service?

If I follow the second approach, how can I make it run on a different interface than the application (haproxy --> public, flask & db --> localhost)

Again, I am talking about a single server here, so this is why I am trying to separate the network interfaces and only expose haproxy on 443 on the public interface.

Ideally I didn't want to change from haproxy to nginx reverse proxy, as I am familiar with it and how ssl termination exactly work there, but I am open to hear any other implementation that makes more sense.


Solution

  • You seem to be overthinking things, and in the process throwing away security features that docker offers.

    first off, docker gives you private networking out the box in both compose and swarm modes. an implicit network called <stack>_default is created and services are attached to it, and DNS resolution is setup in each container to resolve each service name.

    So, assuming your app and db don't explicitly declare any networks, then the following implicit declarations apply, and your app can connect to the db using the connection string mysql://db:3306 directly.

    The db container does not need to either publish, or try and protect, access to this port, only other containers attached to the [stack_]default network will have access.

    networks:
      default: # implicit
    
    services:
      app:
        networks:
          default: # implicit
        environment:
          MYSQL: mysql://db:3306 #
    
      db:
        networks:
          default: # implicit
    

    At this point, its your choice to run HAProxy as a service or not. Personally I would (do). It is handy in swarm to have a single service that handles :80 and :443 ingress, does offloading, and then uses docker networks to direct traffic to other services on whatever service:port's handle those connections.

    I use Traefik rather than HAProxy as it can use service labels to route traffic dynamically, but either way, having HAProxy as a service means, if you continue to use that, you can more easily deploy HAProxy config updates.