Home > Software engineering >  How can I load balance an internal service (with many instances) using docker-compose?
How can I load balance an internal service (with many instances) using docker-compose?

Time:10-24

I hope you can help me, thanks in advance!

I have a docker-compose file with three services traefik, app and service-x. app can have many instances and I implemented a load balancer / reverse proxy using traefik to it, and it is working fine. app calls internally to service-x and service-x can also have many instances, but when app calls http://service-x it always do it to the same instance (the first one), so I wonder if there is a way to implement a load balance mechanism, so when I call service-x it is distributed among all the instances?

this is the simplified docker-compose file:

services:
  traefik:
    image: "traefik"
    restart: always
    command:
      - "--api=true"
      ...
    ports:
      - "80:80"
      - "443:443"
  app:
    image: app:latest
    labels:
      - "traefik.enable=true"
      ...
    ports:
      - "3000"
    restart: on-failure
    links:
      - service-x
  service-x:
    image: service-x:latest
    ports:
      - "8001"

CodePudding user response:

I wonder if there is a way to implement a load balance mechanism, so when I call service-x it is distributed among all the instances?

That is the way docker-compose behaves by default. If you're not seeing that behavior, I suspect it's your use of the deprecated links parameter. You haven't need that for years now; when you attach containers to the same user-defined network (which docker-compose does by default), they can refer to each other by name using the DNS service maintained automatically by Docker.

Also -- and this isn't directly related to your question -- you don't need ports entries for the services that are only used internally.

For a practical example, take a look at this docker-compose.yaml:

version: "3"

services:
  proxy:
    image: traefik:latest
    command:
      - --api.insecure=true
      - --providers.docker
      - --accesslog=true
      - --accesslog.filepath=/dev/stderr
    ports:
      - "127.0.0.2:80:80"
      - "127.0.0.2:443:443"
      - "127.0.0.2:8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  app:
    image: docker.io/nginx:mainline
    volumes:
      - "./default.conf:/etc/nginx/conf.d/default.conf"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`example.com`)"

  service-x:
    image: docker.io/containous/whoami:latest

In the above, service-x is running this image, which runs a webserver that reports its hostname and some other metadata.

The role of your app service is played by nginx, using this configuration:

server {
    listen       80;
    server_name  localhost;

    location / {
        proxy_pass          "http://service-x";
        proxy_set_header    Host $http_host;
    }
}

With the above configuration, a request to http://127.0.02 with a Host header of example.com will go to the app service, which will then proxy the request to the service-x service.

If we bring up multiple instance of service-x, like this:

docker-compose up --scale service-x=3

So that we have:

$ docker ps -f label=com.docker.compose.project=proxy-example
CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS         PORTS                                                                    NAMES
e2cbcb3e4e36   containous/whoami:latest   "/whoami"                6 minutes ago    Up 5 minutes   80/tcp                                                                   proxy2_service-x_2
48655b9f0dd3   containous/whoami:latest   "/whoami"                6 minutes ago    Up 5 minutes   80/tcp                                                                   proxy2_service-x_3
f6cdd1cfe908   nginx:mainline             "/docker-entrypoint.…"   10 minutes ago   Up 5 minutes   80/tcp                                                                   proxy2_app_1
d65f996e7113   containous/whoami:latest   "/whoami"                22 minutes ago   Up 5 minutes   80/tcp                                                                   proxy2_service-x_1
af1310113b6a   traefik:latest             "/entrypoint.sh --ap…"   22 minutes ago   Up 5 minutes   127.0.0.2:80->80/tcp, 127.0.0.2:443->443/tcp, 127.0.0.2:8080->8080/tcp   proxy2_proxy_1

And then make multiple requests through Traefik:

for x in {1..10}; do
  curl -sf --resolve example.com:80:127.0.0.2 http://example.com/ | grep Hostname
done

We see that the requests are rotating among the service-x containers:

Hostname: e2cbcb3e4e36
Hostname: 48655b9f0dd3
Hostname: d65f996e7113
Hostname: e2cbcb3e4e36
Hostname: 48655b9f0dd3
Hostname: d65f996e7113
Hostname: e2cbcb3e4e36
Hostname: 48655b9f0dd3
Hostname: d65f996e7113
Hostname: e2cbcb3e4e36

Notes:

  • Why 127.0.0.2? Because I already have a service listening on 127.0.0.1 on port 80.
  • Related