Docker Swarm Mode is the built-in orchestration engine bundled with Docker 1.12. The Docker team has made a product that allows deploying a multi-host docker cluster a ridiculously simple and amazingly quick affair. I am not sure if it is still production grade compared to some of the older compatriots such as Kubernetes, but the experience of playing with it is surreal.
The objective of this article is to show you how to quickly deploy your complete application in Docker Swarm. I am building on the example that I showed in my previous article – Serverless Computing with Spring Cloud. I have identified Docker containers in the application below that we would deploy on a swarm cluster.
Quick Recap of the Application
- User posts a task execution request. The controller is a Spring Boot application.
- The request is to run either a red-task or a blue task. The request is wrapped with details such as URL and put on a RabbitMQ. The URL format in the original example was for a Maven repo. In this case, I have changed it to a file:// URL format.
- On the other end of RabbitMQ is a listener called Task Launcher. This launcher uses the URL in the request to create a task. Both Task Launcher and Task are Spring Boot Apps.
- Task, once launched, uses a MySQL DB for storing execution information.
Brief Review of Docker Swarm
Docker Swarm has two types of Nodes – (i) Manager (ii) Worker. You can have multiple manager and worker nodes. One manager node is qualified as the leader. Managers talk amongst themselves using the Raft Consensus algorithm. Workers gossip (protocol) among each other.
You launch services on the swarm. Each service has multiple tasks (read containers) associated with it. Service can be scaled up by launching multiple tasks across available nodes in the cluster
I am using a single node cluster for this example. Don’t be alarmed, since swarm mode allows us to do so. Also, no drastic steps are needed in this example to make it run on a multi-node cluster.
We want our user to be able to trigger a task request. That implies TaskTrigger service should be exposed to the outside world. The other services of Rabbit, MySQL, etc. need to be able to talk to each other but need not be exposed to the user. All these services will connect to each other on the network, named net1.
Let us get cracking on setting this up.
Step 1: Initialize Docker Swarm Mode
Create a cluster with the following command. And that's it, you are done. You can add more nodes using the docker join command, but on a single node, the following command suffices.
% node > docker swarm init –advertise-addr 192.168.99.100
There is a service by the name of portainer (portainer.io) that you can use to manage the Swarm through a user interface. Here is how you can get that going
% node > docker service create--name portainer --publish 9000:9000 --constraint 'node.role == manager' --mount ,src=//var/run/docker.sock,dst=/var/run/docker.sock portainer/portainer -H unix:///var/run/docker.sock
You now go to your browser and do whatever we do next using a UI instead of the CLI. Portainer is listening on port 9000.
Step 2: Create the Networks
We now create two networks, as per our topology.
% node > docker network –d overlay net1
The ingress network is automatically created by Docker Swarm.
Step 3: Create Services
Now we need to create our services. Let's create them one by one. Note we have specified the network while creating these services (highlighted in commands). Also, I have published the images of TaskTrigger and TaskLauncher on my DockerHub public repo, hence these commands should work seamlessly for you.
% node > docker service create --name rabbit --network net1 --env --env rabbitmq:3-management
% node >docker service create --name mysqldb1 --network net1 -p 3340:3306 --mount type=volume,source=mysqldata,target=/var/lib/mysql,readonly=false --env MYSQL_ROOT_PASSWORD=root --env MYSQL_USER=root mysql:latest
You may notice that we did not specify the ingress network as part of the command. This is because of the very fact that we are publishing the port to the outside world that connects it to ingress network.
% node > docker service create --name tasktrigger --network net1 --publish 8082:8082 x14gauravg/tasktrigger:1.0
Task Launcher Service
% node > docker service create --name tasklauncher --network net1 x14gauravg/tasklauncher:1.2
That is it, we are done. Our application is now running on a Docker cluster. We can scale up and down at will. We can add more nodes, and Docker orchestration would manage the containers automatically. You may want to use Portainer UI for such activities.
Service Discovery in Swarm
Before I conclude, I want to mention the seamless service discovery that happens in a swarm cluster. I can refer to the service name, for example, the Rabbit service that we created above. Docker would be able to identify the right container IP and route the traffic. Not only that, Docker is able to load balance across multiple container instances using just the service name.
Under the hood, Docker manages a virtual IP against the service. This virtual IP is used to route to multiple containers with different IPs using the Linux Kernel’s IPVS module. Docker automatically creates an Ingress Container connected to both the bridge network in node and the ingress network. A request on the Node IP is routed to the Docker bridge, which eventually routes it to the Ingress Container, and that is where further routing magic happens. If you are still curious, more is available in the Docker documentation.