Creating a Docker Swarm using Multipass and Ubuntu 20.04 on your laptop

; Date: May 21, 2020

Tags: Docker »»»» Docker Swarm »»»» Multipass

Docker is a cool system for deploying applications as reusable containers, and Docker Swarm is a Docker Orchestrator that let's us scale the number of containers across multiple machines. Multipass is a very light weight virtual machine manager application running on Windows, Linux and macOS, that let's us easily set up multiple Ubuntu instances on our laptop at low performance impact. Therefore Multipass can serve as a means to easily experiment with Docker Swarm on your laptop, learning how it works, setting up networks, etc.

In theory this is not hard, since launching Ubuntu instances on Multipass is easy as is installing Docker and Docker Swarm on Ubuntu. But there is enough moving parts that it's useful to review how to install Docker on Ubuntu 20.04 on Multipass, and then initialize a Docker Swarm. Further it's useful to set up scripts to easily initialize Docker Swarm instances, because of course Swarms are more useful when spread across multiple systems.

Docker Swarm is one of many Docker Orchestration platforms. It handles distributing Docker containers across a set of nodes, ensuring the reliability of the swarm by redeploying containers as needed and much more. Multipass gives us an easy way to experiment with Docker Swarm without leaving our laptop.

To install Multipass, head to (multipass.run) https://multipass.run and find the installer for your system. Alternately you'll find Multipass available in some package management systems.

The steps required to setup Docker Swarm on Ubuntu 20.04 on Multipass are:

  • Initialize an Ubuntu 20.04 instance on Multipass
  • Inside the instance execute the steps to initialize Docker on Ubuntu 20.04
  • Inside the first such instance run docker swarm init
  • Inside subsequent instances, run the command causing the instance to join the swarm

The official instructions for installing Docker on Ubuntu are at:

While on Windows we install Docker for Windows, and on macOS we install Docker for macOS, on Linux we simply install Docker. That's because Docker runs natively on Linux.

Our goal is to convert the official instructions into shell scripts. That's so we can easily create new instances any time we wish and quickly build up a Swarm cluster of any size.

Shell scripts for initializing Ubuntu/Docker/Docker Swarm instances

My experimentation with translating the official instructions into repeatable scripts led me to create two scripts.

First, create init-instance.sh containing:

NM=$1

multipass launch --name ${NM} focal
multipass transfer setup-instance.sh ${NM}:/home/ubuntu/setup-instance.sh
multipass exec ${NM} -- sh -x /home/ubuntu/setup-instance.sh

The name for the instance is to be passed on the command line. In the multipass launch we specify an image name of focal which refers to Ubuntu 20.04. As of this writing, in May 2020, the 20.04 image is the latest, but it is not supported in Multipass as the LTS release yet.

The rest of this script transfers a script, setup-instance.sh, to the newly created instance, and then executes that script. While most of the commands in setup-instance.sh could have been executed using multipass exec, some of the commands did not work well that way, and it was found necessary to instead transfer in a shell script.

Create setup-instance.sh containing:

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get -y install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo apt-key fingerprint 0EBFCD88

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo groupadd docker
sudo usermod -aG docker ubuntu
sudo systemctl enable docker

This is the commands listed in the official instructions, with a couple small transliterations. The first portion updates the installed packages, then adds to apt-get the ability to load packages from HTTPS servers. Then we set up the Ubuntu package repository for Docker, and then install Docker rom that repository. Finally we make sure the docker group exists, and add the ubuntu user ID to that group so that we can run docker commands, and finish it out by ensuring the docker daemon runs on system boot.

This does not do any Docker Swarm commands. But it does get Docker running, which you can verify as so:

$ multipass exec NAME -- docker run hello-world

This is the standard way to verify Docker is initialized, to run the hello-world container. If configured correctly, it will download the hello-world image, then instantiate a container, which will print out a useful message. Substitute for NAME the instance name you specified.

Setting up a Docker Swarm on Multipass Ubuntu instances

With those scripts, we can start up an Ubuntu/Docker instance, and initialize the Swarm.

$ sh -x init-instance.sh swarm1
+ NM=swarm1
+ multipass launch --name swarm1 focal
Launched: swarm1                                                                
+ multipass transfer setup-instance.sh swarm1:/home/ubuntu/setup-instance.sh    
+ multipass exec swarm1 -- sh -x /home/ubuntu/setup-instance.sh
+ sudo apt-get update
... much output
+ sudo systemctl enable docker
Synchronizing state of docker.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable docker
$ 

Within a minute or so this launches a Multipass instance and sets up Docker.

$ multipass list
Name                    State             IPv4             Image
swarm1                  Running           192.168.64.14    Ubuntu 20.04 LTS

$ multipass exec swarm1 -- docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete 
Digest: sha256:6a65f928fb91fcfbc963f7aa6d57c8eeb426ad9a20c7ee045538ef34847f44f1
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
...

The swarm1 instance is running, and we can execute Docker. Let's initialize Docker Swarm.

$ multipass exec swarm1 -- docker swarm init
Swarm initialized: current node (xki1yma9q91401zv0i0yksrht) is now a manager.

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

    docker swarm join --token SWMTKN-1-10r3749fp92qk7bpszvv2ejvdrbc4vkoaeks7kkpqdlytwbcq8-33a4kq3xpb315zrwhghs68atx 192.168.64.14:2377

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

$ multipass exec swarm1 -- docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
xki1yma9q91401zv0i0yksrht *   swarm1              Ready               Active              Leader              19.03.9

This Swarm has one node in it, and that one node is the Leader of the cluster. It's easy to be elected Leader when you're the only person voting, eh?

The output includes a command to use for any new Docker system you wish to add to the cluster. You don't need to record the command string, but instead you need to learn these two commands:

$ multipass exec swarm1 -- docker swarm join-token manager
$ multipass exec swarm1 -- docker swarm join-token worker

The docker swarm join-token {manager,worker} command gives you the command string for a node to join the cluster as either a Manager or Worker node.

There are two kinds of nodes in a Swarm cluster.

  • Manager nodes maintain the Swarm Cluster. They communicate with each other, and automatically maintain shared state across the nodes in the cluster. The inter-manager communication channel is called the control plane.
  • Worker nodes do not engage in the management of the cluster, and are simply executing tasks within the cluster. Manager nodes can also execute tasks as a worker, making Docker Swarm more egalitarian than most workplaces.

Adding a second manager to the Swarm

$ sh -x init-instance.sh swarm2
... much output
$ multipass exec swarm1 -- docker swarm join-token manager
To add a manager to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-10r3749fp92qk7bpszvv2ejvdrbc4vkoaeks7kkpqdlytwbcq8-4bfaew5hps8mntqubxt9kkc4n 192.168.64.14:2377
$ multipass exec swarm2 -- docker swarm join --token SWMTKN-1-10r3749fp92qk7bpszvv2ejvdrbc4vkoaeks7kkpqdlytwbcq8-4bfaew5hps8mntqubxt9kkc4n 192.168.64.14:2377
This node joined a swarm as a manager.

$ multipass exec swarm1 -- docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
xki1yma9q91401zv0i0yksrht *   swarm1              Ready               Active              Leader              19.03.9
wgcj54zs2u3k9x1th3s20s1lm     swarm2              Ready               Active              Reachable           19.03.9

There is now two nodes.

Adding a worker node to the Swarm

$ sh -x init-instance.sh swarm3
...
$ multipass exec swarm1 -- docker swarm join-token worker
To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-10r3749fp92qk7bpszvv2ejvdrbc4vkoaeks7kkpqdlytwbcq8-33a4kq3xpb315zrwhghs68atx 192.168.64.14:2377
$ multipass exec swarm3 -- docker swarm join --token SWMTKN-1-10r3749fp92qk7bpszvv2ejvdrbc4vkoaeks7kkpqdlytwbcq8-33a4kq3xpb315zrwhghs68atx 192.168.64.14:2377
This node joined a swarm as a worker.

$ multipass list
Name                    State             IPv4             Image
swarm1                  Running           192.168.64.14    Ubuntu 20.04 LTS
swarm2                  Running           192.168.64.15    Ubuntu 20.04 LTS
swarm3                  Running           192.168.64.16    Ubuntu 20.04 LTS

$ multipass exec swarm1 -- docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
xki1yma9q91401zv0i0yksrht *   swarm1              Ready               Active              Leader              19.03.9
wgcj54zs2u3k9x1th3s20s1lm     swarm2              Ready               Active              Reachable           19.03.9
vk3c3etx53zq4hhzk7q6kgo3y     swarm3              Ready               Active                                  19.03.9

And we now have three nodes, two managers and one worker.

Adding a workload to the swarm

In a Docker Swarm we create a service that in turn contains tasks each of which contain a container. The container is the same container we create using the docker run command, the rest of that is so the Swarm can manage a set of containers across a set of Swarm nodes.

$ multipass exec swarm2 -- docker service create --name nginx --replicas 3 -p 80:80 nginx
l0z5s0e1rpoy35gksnioikf5w
overall progress: 3 out of 3 tasks 
1/3: running   
2/3: running   
3/3: running   
verify: Service converged 

Here we request, on one of the manager nodes (swarm2), to create a Service named nginx. We're requesting three instances of the service, and it is to use the nginx image from Docker Hub. It will also expose port 80 from the container.

$ multipass exec swarm1 -- docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
l0z5s0e1rpoy        nginx               replicated          3/3                 nginx:latest        *:80->80/tcp

To prove that Swarm nodes talk to each other, let's list the services currently running on the cluster, but requesting that from the swarm1 node.

$ multipass exec swarm1 -- docker service ps nginx
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
wcoiz9p6cmck        nginx.1             nginx:latest        swarm1              Running             Running 5 minutes ago                       
yud37ipf4lwb        nginx.2             nginx:latest        swarm2              Running             Running 5 minutes ago                       
wrze6s2csz62        nginx.3             nginx:latest        swarm3              Running             Running 4 minutes ago     

And we can get the status of the nginx service, showing where the three tasks are running. Namely, the cluster distributed the tasks among the nodes, with one task per node.

The nginx image helpfully shows a default HTML page, so we can visit the three Multipass instances at http://192.168.64.14/, http://192.168.64.15/ and http://192.168.64.16/ in a browser to verify that it is working from each.

What won't be so visible is that the Swarm takes care of cross-connecting services so that each published service is visible on every node even if the service is not running on that node.

Exploring cross-connection of services across Swarm nodes

To explore how services are cross-connected across Swarm nodes, let's create a single service and see how it works.

$ multipass exec swarm1 -- docker service create --name httpd --replicas 1 -p 90:80 httpd
qnodrnbmpd5hy360fn99hgdg5
overall progress: 1 out of 1 tasks 
1/1: running   
verify: Service converged 

$ multipass exec swarm1 -- docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
qnodrnbmpd5h        httpd               replicated          1/1                 httpd:latest        *:90->80/tcp
l0z5s0e1rpoy        nginx               replicated          3/3                 nginx:latest        *:80->80/tcp

$ multipass exec swarm1 -- docker service ps httpd
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
yb6dv4ur8eq2        httpd.1             httpd:latest        swarm1              Running             Running 49 seconds ago      

To give equal time to Nginx's competition, we started one instance of the Apache HTTPD. The swarm assigned it to the swarm1 node. We set it to be visible on port 90.

And, visiting http://192.168.64.14:90/, http://192.168.64.15:90/ and http://192.168.64.16:90/ does show the Apache service is visible via each Node.

Modifying the state of a deployed service

We can modify the state of a service which has been deployed, and the Swarm will distribute that modification.

$ multipass exec swarm1 -- docker service update --replicas 2 httpd
httpd
overall progress: 2 out of 2 tasks 
1/2: running   
2/2: running   
verify: Service converged 

$ multipass exec swarm1 -- docker service ps httpd
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
yb6dv4ur8eq2        httpd.1             httpd:latest        swarm1              Running             Running 4 minutes ago                        
9uxc9p9wbvdu        httpd.2             httpd:latest        swarm2              Running             Running 10 seconds ago      

This increased the number of instances with there now being two httpd instances distributed to swarm1 and swarm2.

Exploring containers deployed to each node

Another command is docker container which has a number of commands for managing individual containers. With it we can explore the containers deployed on each node.

$ multipass exec swarm1 -- docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
b6fc2b1000e3        httpd:latest        "httpd-foreground"       7 minutes ago       Up 7 minutes        80/tcp              httpd.1.yb6dv4ur8eq2epuae97i34ij1
f950a9c2db96        nginx:latest        "nginx -g 'daemon of…"   19 minutes ago      Up 19 minutes       80/tcp              nginx.1.wcoiz9p6cmckvb8il9n7uwyvk

$ multipass exec swarm2 -- docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
649d821a4e78        httpd:latest        "httpd-foreground"       3 minutes ago       Up 3 minutes        80/tcp              httpd.2.9uxc9p9wbvdu8l9h1lnesqjhc
44f12c4b4c5f        nginx:latest        "nginx -g 'daemon of…"   20 minutes ago      Up 20 minutes       80/tcp              nginx.2.yud37ipf4lwb43idtgu2bfhs7

$ multipass exec swarm3 -- docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
be8bcd336ad1        nginx:latest        "nginx -g 'daemon of…"   20 minutes ago      Up 20 minutes       80/tcp              nginx.3.wrze6s2csz62cdoc2ozwb0g03

Notice that in the NAME column we see names like nginx.2.yud37ipf4lwb43idtgu2bfhs7. In order for the container name to be unique across the cluster, Docker Swarm is adding that nonce to the container name.

Another thing to notice is that, at the container, the HTTPD container is exposing port 80. It is at a higher level where the mapping from port 90 to port 80 occurs.

Deleting services

To clean up and remove a service:

$ multipass exec swarm2 -- docker service rm nginx
nginx

 multipass exec swarm2 -- docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
qnodrnbmpd5h        httpd               replicated          2/2                 httpd:latest        *:90->80/tcp

$ multipass exec swarm1 -- docker service rm httpd
httpd
$ multipass exec swarm2 -- docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS

The service rm command starts the procedure to shut down a named service. It does that by distributing commands out to the cluster to shut down containers.

Managing Multipass instances

You can stop individual instances as so:

$ multipass stop swarm3
$ multipass stop swarm2
$ multipass stop swarm1

Or delete them:

$ multipass delete swarm3
$ multipass delete swarm2
$ multipass delete swarm1
$ multipass purge

The difference is that a stopped instance can be restarted, but a deleted instance is in a purgatory state. The purge command removes data related to any deleted instances.

$ multipass start swarm3
$ multipass start swarm2
$ multipass start swarm1

If instead you wish to restart a node, simply use the start command.

Something to experiment with is for a swarm that has running instances, what happens when you run stop on one of the instances. The swarm will redeploy containers as needed, for instance. If you've stopped the Leader instance, a new Leader will be elected.

Performance

This experiment was performed on a 2012 MacBook Pro with 16 GB of main memory and an SSD boot drive. That is, it's an eight year old computer that's been expanded to it's limit. Currently running is Chrome (with multiple top level windows and a couple hundred open tabs), Firefox and Safari browsers, plus several Visual Studio Code windows, and several other applications. In other words this is not a lightly loaded computer.

But having three Multipass instances running simultaneously did not cause any appreciable slowdown to the system.

The Multipass team claims that it is an extremely light-weight (meaning low system impact) virtual machine manager that supports full fledged Ubuntu instances. This experiment indicates the "low system impact" part is correct. While the Nginx and HTTPD containers I launched did not present much impact on the system, I did have three virtual machines, with three Nginx instances and two HTTPD instances, running simultaneously. Try that with VirtualBox on an eight year old MacBook Pro that has that many other processes running simultaneously.

My memory of VirtualBox is that it quickly turns desktop computers into molasses.

Summary

Multipass looks to be a very useful tool for launching Ubuntu instances on a developers laptop. It is very capable, and you get a full-fledged Ubuntu instance in a minute or so. Supposedly it's possible for Multipass to support other operating systems, but it seems that Ubuntu is the only flavor available right now.

Docker Swarm is a very capable Docker orchestrator. It is worth learning about Swarm, if only so you have a better understanding of Docker orchestration and therefore a clearer understanding of other Docker orchestrators. Multipass looks to be an excellent platform on which to experiment with Docker Swarm without having to leave your laptop.

About the Author(s)

David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.