Manage your Docker container with Docker Swarm

Photo by Manuel Nägeli / Unsplash

Docker Swarm is a container orchestration tool that allows you to manage multiple Docker hosts and deploy containers across them. It is a native clustering tool that can turn a group of Docker engines into a single, virtual Docker engine.

Docker Swarm provides load balancing, service discovery, and scheduling features. With Docker Swarm, you can easily manage and deploy your applications across a cluster of Docker nodes.

Why do you need it?

With Docker and Docker-compose, you can deploy a complete web application requiring many containers on a single VM. As the load received by the application grows, you must increase the VM resources (CPU, RAM, Storage, etc..) to handle it, aka Vertical scaling.

The problem is you can't vertical scale a VM infinitely, so at some point, you will need to deploy the same application on many VMs and balance the load to all the application instances running in these VMs. How could you achieve this?

How Docker Swarm works?

Docker Swarm is built on top of the Docker engine and uses the same API, which means you can use Docker commands to interact with a Swarm cluster. Docker Swarm uses a manager-worker architecture.

The manager node is responsible for managing the cluster and scheduling tasks, while the worker nodes are responsible for running the containers. Docker Swarm uses a swarm mode to manage the cluster, which allows you to define services, networks, and volumes that can be used by the containers.

The manager-worker architecture used by Docker Swarm

Prerequisites

To follow this tutorial, you will use a Virtual Private Server running on Ubuntu 20.04 or higher with the minimum requirements of 2 CPUs, 4GB of RAM, and 20GB of storage.

You can buy a VPS on Hetzner, which provides affordable servers with good performance. The server running this blog and all my side projects are hosted here. Use my referral link to get €⁠20 after you sign up.

Once bought the server, you must set up some basic configuration on it. I wrote a whole blog post you can follow.

The minimal configuration of a VPS server to host a Web Application
You just bought a fresh VPS to host your Web application but don’t know our the configure it in order to be ready to make your app accessible through Internet? We will cover how to configure it in this tutorial.

Install the Docker Engine

We will first install the Docker engine responsible for building, running, and publishing containers. Run the following commands to install it on your server:


sudo apt update

sudo apt install ca-certificates curl gnupg lsb-release -y

sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  
 sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

Verify that the Docker Engine is installed by running the hello-word image:


sudo docker run hello-world

To run Docker without adding the keyword sudo, you must add the current user in the docker group using the following command:


sudo usermod -aG docker ${USER}

Logout the server and log in again, then run su - ${USER}and enter your password.

Initialize Docker swarm

Docker Swarm is not enabled by default; you can run the command docker system info to verify his status.

Verify the Docker Swarm status.

Run the command below to enable it:


docker swarm init

Swarm will initialize and define the current node (server) as the manager. The output will print the command to add a worker node to the Swarm, but we will not use it this post because we will deploy our application on the manager.

If you want to see how to work with the worker node, let me know 😉.

Write the stack definition of the application

Swarm uses a configuration file written YAML to manage your Docker container. The content of the configuration is similar to the Docker-compose syntax, with some additional properties that give Swarm another capability.

We will use the Docker image of the application we built in the tutorial below, where I show how to configure a load balancer with Nginx. The Docker image is stored on my personal Docker Hub.

Load balancing a Node.js application with Nginx
In this post, we will see how to configure a load balancer on a Node.js application using Nginx and see how it improves the performance.

We want to start two containers for our backend application; create a file named backend-stack.yaml and add the code below:


version: "3.9"

services:
  api:
    image: tericcabrel/nodelb-app:latest
    networks:
      - backend-app-net
    deploy:
      replicas: 2
      mode: replicated
      restart_policy:
        condition: on-failure

networks:
  backend-app-net:
    external: true

We create a service with the name api that will start a container from the Docker image  tericcabrel/nodelb-app:latest. The container will be linked to an external network named backend-app-net.

For this Docker container, we want to create two replicas, and for each replica will restart if the application running in the container crashes.

About the Swarm mode, there are possible values: global and replicated. The value global means Swarm will create precisely one container per physical node while replicated allow running many containers on the same physical node.

Since the network is marked as external, it means it should already exist when deploying the stack; let's create it using the command below:


docker network create traefik --scope=swarm --attachable

The option --scope allow the network to be used in Docker Swarm. The option --attachable will enable you to manually attach a container to the network.

💡
The network was provided because we want to manually attach containers to it. You can remove it from your configuration if it is not your case.

Run the stack definition

To run the stack, here is the template on the command:


docker stack deploy -c <stack_filename>.yaml <stack_name>

The command to run the stack will be:


docker stack deploy -c backend-stack.yaml my-node-backend

This command will start two Docker containers of the image; you can run docker ps to view them.

Run the stack with Docker Swarm.

Inspect the stack

When the stack is deployed, you might want to know the states for a specific container. This is possible by inspecting the stack of the application using the command:


docker stack ps <your-stack-name>

We gave the name node-backend-api to our stack.

View the state of every container in the stack deployed with Docker Swarm.

Other Swarm properties

You can define two properties in your stack configuration file to customize how you want to run your containers, such as limiting the resource to allocate and running a container only if some conditions are met.

The property "resources"

This property allows you to define the minimum physical resource capacity required to run the container and the ability to not exceed it. These are designated with two sub-properties reservations and limits.

On our stack, we can update it with the following code:


version: "3.9"

services:
  api:
    image: tericcabrel/nodelb-app:latest
    networks:
      - backend-app-net
    deploy:
      replicas: 2
      mode: replicated
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus: '1'
          memory: 500M
        reservations:
          cpus: '0.50'
          memory: 250M

networks:
  backend-app-net:
    external: true

So here, the container requires at least 250MB of RAM and 0.5 CPU to run and cannot exceed 500MB and 1 CPU.

If the container reaches the maximum capacity and needs more, the service provided to the user by this container will face degradation and possibly crash.

The property "placements"

This property specifies constraints for the platform to select a physical node to run service containers.

  • Do you have a service that can only be run on Linux?
  • A service must run only on the manager node?

You can use this property to define that. We use the sub-property constraints to describe what is required to run the service.

Our stack will look like this now:


version: "3.9"

services:
  api:
    image: tericcabrel/nodelb-app:latest
    networks:
      - backend-app-net
    deploy:
      replicas: 2
      mode: replicated
      restart_policy:
        condition: on-failure
      resources:
        limits:
          cpus: '1'
          memory: 500M
        reservations:
          cpus: '0.50'
          memory: 250M
      placement:
        constraints:
          - node.platform.os == linux
          - node.role == manager

networks:
  backend-app-net:
    external: true

There is another sub-property preferences that define what is nice to have for the service to work seamlessly.

Delete a stack

Deleting a stack stops all the running containers and deletes them. Use the command below our stack:


docker stack rm my-node-backend

Wrap up

With Docker Swarm, you manage the containers running on many nodes, which are physical servers located anywhere in the world. Among all the nodes, one acts as the manager and orchestrates the stack deployed on worker nodes.

Leveraging this technology is crucial when the number of containers required to handle your application load cannot be run on a single node. This will even allow you to predict your scaling needs and act accordingly.

You can find the code source on the GitHub repository.

Follow me on Twitter or subscribe to my newsletter to avoid missing the upcoming posts and the tips and tricks I occasionally share.