Docker Swarm With Virtual Machines Using Multipass
Tags: tutorial, docker, virtualization
Published: 2023-12-31 01:37:23

According to the Docker documentation, [Swarm](https://docs.docker.com/engine/swarm/key-concepts/) mode is an advanced feature for managing a cluster of Docker daemons. Each instance of Docker Engine participating in the swarm is a node, and act as managers, to manage membership and delegation, and workers, which run swarm services. A given Docker host can be a manager, a worker, or perform both roles. You can run one or more nodes on a single physical computer or cloud server, but production swarm deployments typically include Docker nodes distributed across multiple physical and cloud machines.

In a development environment, where you probably don't have access to multiple physical devices to configure a swarm, a solution would be to create virtual machines (VMs), each of them running an instance of Docker Engine.

Last month, I wrote a tutorial about [Multipass](https://dev.to/mattdark/multipass-ubuntu-virtual-machines-made-easy-3am9), a tool from Canonical that can be used to create Ubuntu virtual machines, and through this article, you will learn how to use it to configure the swarm. I've followed the instructions from [this article](https://techsparx.com/software-development/docker/swarm/multipass.html), but I'll explain the general process and how you can deploy services in a swarm.

### Virtual Machines
To create a virtual machine with Multipass, you would run the following command:

```
$ multipass launch --name node1 lunar
```

The above command would create a virtual machine, named `node1`, with the following characteristics:

* 1 CPU
* 5GB of disk
* 1GB of RAM

Depending on the requirements of your development environment, the default configuration can be limited, and you may want to assign more resources. I suggest creating a VM with the following features if more resources are needed:

* 2 CPUs
* 8GB of disk
* 2GB of RAM

I'm running tests on a computer with 4 CPUs and 12GB of RAM, and assigning the above resources to each node won't affect the normal operation of the host.

Let's create a three-node cluster.

First, create a BASH script (`init-instance.sh`):

```
NM=$1

multipass launch --name ${NM} lunar --memory 2G --disk 8G --cpus 2

multipass transfer install-docker.sh ${NM}:/home/ubuntu/install-docker.sh
multipass exec ${NM} -- sh -x /home/ubuntu/install-docker.sh
```

1. `NM` is the variable that will get the name of the VM from the command line
2. Will launch a VM with custom configuration
3. The `install-docker.sh` file will be transfered from the host to the VM
4. The script that will install Docker Engine on each node

Now create the script for installing Docker Engine on each node of the swarm.

```
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/u>  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Install latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Manage Docker as a non-root user
## Create the docker group
sudo groupadd docker

## Add your user to the docker group
sudo usermod -aG docker $USER

## Enable the docker daemon to run on system boot
sudo systemctl enable docker
```

The script will:

* Add Docker's official GPG key
* Add the repository to Apt resources
* Install latest version of Docker Engine
* Manage Docker as a non-root user
    * Create the `docker` group
    * Add your user to the `docker` group

Docker Swarm uses the `overlay` driver by default, and it's not supported by the rootless mode. That's why Docker must be configured following the [post-installation](https://docs.docker.com/engine/install/linux-postinstall/) steps.

Now run the following command to create the manager node:

```
$ sh -x init-instance.sh manager
```

Then, create two worker nodes:

```
$ sh -x init-instance.sh worker1
```

```
$ sh -x init-instance.sh worker2
```

The Docker Engine instances are now running.

## Initialize the Swarm
The swarm must be initialized in the manager node, and the `worker1` and `worker2` VMs must be added to the swarm as worker nodes.

Initialized the swarm:

```
$ multipass exec manager -- docker swarm init
```

You'll get the following output:

```
Swarm initialized: current node (fgjgd7qr4yg0dnlk8nurrqk9q) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-5d9bj8wua4wy812hzl7vqc2r54yroy227i2oqkl27k3vzxdk7e-7na4j9souwf0s2yf6tmw5daiq 10.81.157.203:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```

It will give you the token and command needed to add the worker nodes to the swarm. `10.81.157.203` is the IP address assigned by Multipass to the `manager` VM.

Add the `worker1` VM to the swarm as worker:

```
$ multipass exec worker1 -- docker swarm join --token SWMTKN-1-5d9bj8wua4wy812hzl7vqc2r54yroy227i2oqkl27k3vzxdk7e-7na4j9souwf0s2yf6tmw5daiq 10.81.157.203:2377
```

Add the `worker2` VM to the swarm as worker:

```
$ multipass exec worker2 -- docker swarm join --token SWMTKN-1-5d9bj8wua4wy812hzl7vqc2r54yroy227i2oqkl27k3vzxdk7e-7na4j9souwf0s2yf6tmw5daiq 10.81.157.203:2377
```

The swarm is now configured. You can list the nodes in the swarm by running:

```
$ multipass exec manager -- docker node ls
```

## Deploy a Service to the Swarm
Now that the swarm is configured, you can run standalone containers on each node but they won't be managed by the manager node, only services deployed to the swarm.

Let's deploy the first service, an Nginx server with three replicas.

```
$ multipass exec manager -- docker service create --name nginx --replicas 3 -p 80:80 nginx
```

* The `docker service create` command creates the service
* The `--name` flag is used to assing a name to the service, `nginx`
* The `--replicas` flag specifies the number of replicas
* The `-p` flag is used to open the port `80` in the host to receive and redirect requests to the port `80` in the container

You can check the running services with the following command:

```
$ multipass exec manager -- docker service ls
```

You'll get the following output:

```
ID             NAME      MODE         REPLICAS   IMAGE          PORTS
5rflc7yq4pyg   nginx     replicated   3/3        nginx:latest   *:80->80/tcp
```

To see which nodes are running the service, run the following command:

```
$ multipass exec manager -- docker service ps nginx
```

You'll get the following output:

```
ID             NAME      IMAGE          NODE      DESIRED STATE   CURRENT STATE           ERROR     PORTS
ysp4p3ut581a   nginx.1   nginx:latest   worker1   Running         Running 6 minutes ago
7zozyfe4s8a5   nginx.2   nginx:latest   worker2   Running         Running 6 minutes ago
njdou1bmh4tm   nginx.3   nginx:latest   manager   Running         Running 6 minutes ago
```

To display details about a service in an easily readable format, run:

```
$ multipass exec manager -- docker service inspect --pretty nginx
```

It will display:

```
ID:             5rflc7yq4pygo7kxu8pq7ev7l
Name:           nginx
Service Mode:   Replicated
 Replicas:      3
Placement:
UpdateConfig:
 Parallelism:   1
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:   1
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:         nginx:latest@sha256:2bdc49f2f8ae8d8dc50ed00f2ee56d00385c6f8bc8a8b320d0a294d9e3b49026
 Init:          false
Resources:
Endpoint Mode:  vip
Ports:
 PublishedPort = 80
  Protocol = tcp
  TargetPort = 80
  PublishMode = ingress
```

If you want to delete the service, just type:

```
$ multipass exec manager -- docker service rm nginx
```

## Conclusion
Through this article, you learned how to configure a swarm using virtual machines and you deploy your first service.

---

[![Support me on Buy Me A Coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzx9uenqvwqv1vp8ub5d.png)](https://buymeacoffee.com/letstalkoss)