This project is a minimalistic demonstration on how to run a swarm of microservices across multiple host machines using Docker Swarm.
Out of the box, Docker Swarm provides solutions for:
- Scaling services with automatic load balancing of traffic between containers
- Automatic distribution of containers across multiple nodes
- Automatic recovery after a container or a whole machine goes down
- Rolling upgrades with customizable strategies
- Virtual overlay network for inter-container communication
- ...and many other problems of running large distributed systems
Docker Swarm makes it easy to run hundreds or thousands of interconnected containers without really thinking about the physical machines that provide the computing resources.
The sample project in this repository is a very simplified approximation of a distributed system for managing customers, products and invoices. It's composed of the following services:
-
Customers, Products and Invoices
These are simple microservices with REST API (based on Node.js, Express and Tsoa), each with their own associated redis database. The Invoices service uses the Customers and Products API to validate new invoices.
- Customers manages the collection of customers (each has id, name and address)
- Products manages the collection of available products (each has id and name)
- Invoices manages invoices (each invoice has id, product, customer and quantity). The service talks to the Customers and Products microservices to validate each new invoice.
-
Webapp
A simple frontend (written in Vue.js) that allows creating and displaying customers and inovices.
-
API-gateway
Simple nginx-based service which routes each request to the appropriate microservice (based on URL).
-
Viz
An instance of Docker Swarm Vizualizer, accessible in browser on port 8080. Used for basic monitoring of the swarm status.
Requires: VirtualBox, Docker with Docker Machine
Our test cluster will have 3 nodes - one manager node (m1
) and two worker nodes (w1
, w2
).
Run the following commands
docker-machine create m1 --driver virtualbox
docker-machine create w1 --driver virtualbox
docker-machine create w2 --driver virtualbox
This will create and launch the 3 virtualbox VMs, m1
, w1
and w2
.
Verify with docker-machine ls
.
eval $(docker-machine env m1)
This will change your DOCKER_HOST
evn variable to the IP adress of m1
, so all docker
commands now run against this VM instead of your physical machine;
later, you can return to your physical machine with eval $(docker-machine env -u)
.
Verify with docker-machine active
.
docker swarm init --advertise-addr $(docker-machine ip m1)
Docker will print the command you should use for joining the other nodes to the cluster, copy it for use in the next step.
Verify with docker node ls
.
eval $(docker-machine env w1)
<join_command_from_step_2>
eval $(docker-machine env w2)
<join_command_from_step_2>
Verify with docker node ls
.
docker stack deploy demo --compose-file docker-compose.yml
Verify with docker service ps
.
If any service is not running, investigate with docker stack ps --no-trunc demo
.
To access the service in web browser, use the IP adress of any of the nodes.
For example docker-machine ip w1
Open http://<node_ip>
to interact with the web application
Open http://<node_ip>:8080
to see the cluster
Prerequisites:
- Your swarm is up and running
- You are connected to the manager node (
eval $(docker-machine env m1)
)
docker service scale demo_customers=3
Verify with docker service ps
Optional: To watch the swarm state in realtime, you can run watch -d -n 0.5 docker service ls
in a separate console window.
List the containers running on the manager node and kill one
docker ps
docker kill <container_id>
Watch how the swarm respawns the container automatically (with docker service ps
or in browser at http://<node_ip>:8080
)
Visit http://<node_id>:8080/
to see live visualisation of the swarm state.
Kill the w2
node and watch how the lost containers are respawned on the remaining nodes.
docker-machine kill w2
Restart the w2
node
docker-machine start w2
Note that by default, Docker Swarm will not automatically redistribute the existing containers to a newly added node. It will start using the new node only for new tasks (that is mostly when updating or scaling services). Let's force some new tasks to be created for a few service:
docker service update --force customers
docker service update --force invoices
Watch how the new customers
and invoices
containers are spun up on the w2
node again.
We will scale the customers
service to 10 instances and do a rolling upgraed to version 2
docker service update --update-delay 5s --image embair/swarm-demo:customers-v2
Watch how it gradually replaces the demo_customers
containers with new version one by one, waiting for 5s inbetween each upgrade.
Besides the --update-delay
, there are other arguments you can use to customize the upgrade process, for example:
--update-parallelism
to specify how many containers to upgrade simultaneously--
See the official docs for rull reference.
All of this can also by configured in the docker-compose.yml
file.
By default, you can access any published service on any node in the docker swarm. All requests are automatically load balanced between the nodes that have the corresponding Container using the automatically created Ingress network. For more information see official docs
You can verify this by accessing the webapp in browser using the IP adress of each node.
List the ip adresses with
docker-machine ls
Some topics fell outside the scope of this sample project, but are nevertheless critical for successfully running microservices in production environment. These include:
- Proper health-checking for each service
- Logging and monitoring
- Managing distributed transactions
To learn more about Microservice Architecture and Docker Swarm, I recommend these courses:
- Microservices Architecture by Rag Dihman
- Getting Started with Docker Swarm Mode by Wes Higbee