Docker Compose allows you to create multiple replicas of the same service, providing scalability to your applications. By having multiple replicas of the same service, you can distribute the workload, thus supporting a larger number of operations.
version: '3.1' services: myservice: deploy: replicas: 3
This is done through the services.<my-service>.deploy.replicas
field, which allows you to indicate how many replicas of the same service will be created.
Now, Docker-Compose is a basic orchestrator, so this requires some extra steps to make it possible. Unlike more complete orchestrators like Docker-Swarm or Kubernetes, it is necessary to create a service that acts as a “reverse proxy” and performs load balancing between different instances of the same service.
Setting up replicas in a service
Let’s start with a docker-compose.yaml
file that declares 2 services, the first for a MySQL database and the second for an application that consumes that database instance.
version: '3.1' services: book-service: image: book-service:latest restart: always environment: MYSQL_HOST: 'book_db' MYSQL_PORT: '3306' MYSQL_USER: 'user' MYSQL_PASSWORD: 'password' APP_PORT: '8080' ports: # <Port exposed> : <MySQL Port running inside container> - '8080:8080' expose: # Opens port 8080 on the container - '8080' depends_on: - book_db
You can then add the deploy.replicas
fields to the book-service
service indicating the number of instances desired. And also remove the lines corresponding to the ports indicated in this service, otherwise, if there are 2 or more instances/replicas of the same service there would be a conflict with the use of ports and the deployment would fail.
version: '3.1' services: book-service: image: book-service:latest restart: always environment: MYSQL_HOST: 'book_db' MYSQL_PORT: '3306' MYSQL_USER: 'user' MYSQL_PASSWORD: 'password' APP_PORT: '8080' STOCK_SERVICE_URL: 'http://stock-service:8080' depends_on: - book_db deploy: replicas: 3 endpoint_mode: dnsrr ## Round Robin Load Balancing
Si iniciamos los servicios docker-compose up -d
se puede observar como se han creado 3 instancias del servicio book-service
.
If we start the services docker-compose up -d
, then we can see how 3 instances of the book-service
have been created.
$ docker-compose ps NAME COMMAND SERVICE STATUS PORTS book-service-1 "java -jar book-serv…" book-service running 8080/tcp book-service-2 "java -jar book-serv…" book-service running 8080/tcp book-service-3 "java -jar book-serv…" book-service running 8080/tcp book_db-1 "docker-entrypoint.s…" book_db running 0.0.0.0:3306->3306/tcp
However, there are two issues that still need to be fixed before the scalability provided by these new instances can be used.
- There is no load balancing between instances.
- The ports are not exposing any endpoints to access your endpoints.
Using a Reverse Proxy
Nginx will be used as a reverse proxy to be the entry point for the replicated services. Nginx will receive the HTTP requests, and will redirect them to the docker-compose service, and this one to one of its multiple instances, solving the load balancing problem at the same time.
- A
reverse_proxy.conf
file is created and saved alongside thedocker-compose.yaml
file. This file is the NGINX configuration for a reverse proxy.
server { listen 80; server_name localhost; location / { proxy_pass http://book-service:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
These instructions say that when NGINX reads port 80, on localhost, it should redirect to the URL http://book-service:8080/. That is, it should redirect to the internal DNS of the book-service.
- In the file
docker-compose.yaml
a new service is added with namereverse-proxy
:
reverse-proxy: image: nginx:latest volumes: - ./reverse_proxy.conf:/etc/nginx/conf.d/default.conf ports: - "8080:80" depends_on: - book-service
This service is named reverse-proxy, its image is nginx:latest, it uses the configuration file created previously reverse_proxy.conf, and it maps nginx port 80 to 8080 which is exposed externally.
- Finally the file
docker-compose.yaml
should look at this:
version: '3.1' services: book-service: image: book-service:latest restart: always environment: MYSQL_HOST: 'book_db' MYSQL_PORT: '3306' MYSQL_USER: 'user' MYSQL_PASSWORD: 'password' APP_PORT: '8080' STOCK_SERVICE_URL: 'http://stock-service:8080' depends_on: - book_db deploy: replicas: 3 endpoint_mode: dnsrr ## Round Robin Load Balancing reverse-proxy: image: nginx:latest volumes: - ./reverse_proxy.conf:/etc/nginx/conf.d/default.conf ports: - "8080:80" depends_on: - book-service
- The orchestration is started with docker-compose with the instruction:
$ docker-compose up
Once the applications are started, you can see that there are 3 instances for the book-service, plus a reverse-proxy listening on port 8080.
$ docker-compose ps NAME COMMAND SERVICE STATUS PORTS book-service-1 "java -jar book-serv…" book-service running 8080/tcp book-service-2 "java -jar book-serv…" book-service running 8080/tcp book-service-3 "java -jar book-serv…" book-service running 8080/tcp book_db-1 "docker-entrypoint.s…" book_db running 0.0.0.0:3306->3306/tcp reverse-proxy-1 "/docker-entrypoint.…" reverse-proxy running 0.0.0.0:8080->80/tcp
Finally, accessing the URL of the service:
- Book Service : http://localhost:8080/ (through nginx redirecting to the services)
Conclusion
Docker-Compose allows you to scale your services to provide multiple instances, and through a simple configuration add a reverse-proxy that helps us provide load balancing and complement necessary functionalities.