# 11. Docker Swarm to manage multiple containers
###### tags: `corsounipd2022`
> Based on:
> https://github.com/docker/labs/blob/master/swarm-mode/beginner-tutorial/README.md
> https://docs.docker.com/engine/swarm/
Check out the documentation on [Docker Swarm Mode](https://docs.docker.com/engine/swarm/) for more information.
:::warning
Create your own **Docker ID** at https://hub.docker.com/signup and…
Since this example would require various machine it can be done online using: https://labs.play-with-docker.com/
:::
## What is a Docker Swarm?
Docker swarm is a container orchestration tool, meaning that it allows the user to manage multiple containers deployed across multiple host machines.
A swarm consists of multiple Docker hosts which run in **swarm mode** and act as managers (to manage membership and delegation) and workers (which run [swarm services](https://docs.docker.com/engine/swarm/key-concepts/#services-and-tasks)). A given Docker host can be a manager, a worker, or perform both roles. When you create a service, you define its optimal state (number of replicas, network and storage resources available to it, ports the service exposes to the outside world, and more). Docker works to maintain that desired state. For instance, if a worker node becomes unavailable, Docker schedules that node’s tasks on other nodes.
A **task** is a running container which is part of a swarm service and managed by a swarm manager, as opposed to a standalone container.
---
One of the key advantages of swarm services over standalone containers is that you can modify a service’s configuration, including the networks and volumes it is connected to, without the need to manually restart the service. Docker will update the configuration, stop the service tasks with the out of date configuration, and create new ones matching the desired configuration.
---
When Docker is running in swarm mode, you can still run standalone containers on any of the Docker hosts participating in the swarm, as well as swarm services. A key difference between standalone containers and swarm services is that only swarm managers can manage a swarm, while standalone containers can be started on any daemon.
---
You can use the Docker CLI to create a swarm, deploy application services to a swarm, and manage swarm behavior.
In the same way that you can use [Docker Compose](https://docs.docker.com/compose/) to define and run containers, you can define and run [Swarm service](https://docs.docker.com/engine/swarm/services/) stacks.
![](https://i.imgur.com/JI1daE6.png)
One of the key benefits associated with the operation of a docker swarm is the high level of availability offered for applications.
### About data
Swarm Mode itself does not do anything different with volumes, it runs any volume mount command you provide on the node where the container is running. If your volume mount is local to that node, then your data will be saved locally on that node. There is no built in functionality to move data between nodes automatically.
We will not consider this detail here. If interested, a good starting point is this: https://docs.docker.com/engine/swarm/services/#give-a-service-access-to-volumes-or-bind-mounts
or this:
https://devopsian.net/posts/share-persistent-storage-volumes-in-swarm/
## Creating the nodes and Swarm
We start creating 5 nodes (the maximum allowed) to get something like this (_IP addresses will vary_):
![](https://i.imgur.com/GEvdQmH.png =200x)
We will now create a swarm by initializing it on the first node (`node1`). This is done using the command:
```shell=
[node1] $ docker swarm init \
--listen-addr <IP_node1> --advertise-addr <IP_node1>
```
we have to explicitly indicate the addresses since the nodes we are using have multiple addresses on different interfaces.
We'll get:
```
Swarm initialized: current node (mopd8x5wn62zef4euks9i30vq) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5thjr9bbckyfxfx2sqidv9gbh3thw5hm885hcijxpkgdppt886-7dtz99cahnr3hvsh6bt6fe0f2 192.168.0.13:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
```
So, to get the token for the managers we do:
```shell=
[node1] $ docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-2f4tkho405ez9hr9v3g42ym4quoodqsghefm2kvglzqohnmo7r-2knif0bnekclbqklf6wivaj9x 192.168.0.13:2377
```
**WARNING: the actual values for the token returned and shown from here on can obviously change!**
We will now create a swarm with 3 managers (red boxes) and 2 workers (grey circles) with the command obtained above:
```graphviz
digraph {
node [shape=box, color=red]; node1; node2; node3
node [shape=circle,fixedsize=true,style=filled,color=lightgrey; width=0.9]; node4; node5;
node1->node4;
node1->node5;
node2->node4;
node2->node5;
node3->node4;
node3->node5;
}
```
To show the members of swarm, from `node1`:
```shell=
[node1] $ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
vz9ii3frm4r7v2jx9brszwtip * node1 Ready Active Leader 20.10.0
9guxhwn7g47q87o6xsvirg5mn node2 Ready Active Reachable 20.10.0
96myll5dzbzfec7q08e8ucgcj node3 Ready Active Reachable 20.10.0
h0mcls38aesrustmlmx823ui5 node4 Ready Active 20.10.0
6k01qk0gclz9f6ychkbktjhv8 node5 Ready Active 20.10.0
```
From now on, to help understand what happen during this seminar, we will use a tool called [Docker Swarm Visualizer](https://github.com/dockersamples/docker-swarm-visualizer), and since we already have a swarm, we create a **service** with it with the command:
```shell=
[node1] $ docker service create \
--name=viz \
--publish=8080:8080/tcp \
--constraint=node.role==manager \
--mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
dockersamples/visualizer
```
after a few seconds, we'll have a web page available that looks like this:
![](https://i.imgur.com/d8SjJp9.png)
We now create a new single service called `web` that runs the latest nginx. We are doing this in the manager node `node1`:
```shell=
[node1] $ docker service create -p 80:80 --name web nginx:latest
zvchlmwdtfz2hlc4lyculozky
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
```
and:
```shell=
[node1] $ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
1i9vcslauzx4 viz replicated 1/1 dockersamples/visualizer:latest *:8080->8080/tcp
tew0o5yjcd1p web replicated 1/1 nginx:latest *:80->80/tcp
```
Now you can see that from any of the nodes we have port 80 open and if we access it, will get the nginx welcome page:
![](https://i.imgur.com/dS0ePZG.png)
![](https://i.imgur.com/vmeLwYt.png)
You can actually load any of the node ip addresses and get the same result because of [Swarm Mode's Routing Mesh](https://docs.docker.com/engine/swarm/ingress/).
#### Scaling services
Now, let's scale the service:
```shell=
[node1] $ docker service scale web=10
web scaled to 10
overall progress: 10 out of 10 tasks
1/10: running
2/10: running
3/10: running
4/10: running
5/10: running
6/10: running
7/10: running
8/10: running
9/10: running
10/10: running
verify: Service converged
```
and:
```shell=
[node1] $ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
1i9vcslauzx4 viz replicated 1/1 dockersamples/visualizer:latest *:8080->8080/tcp
tew0o5yjcd1p web replicated 10/10 nginx:latest *:80->80/tcp
```
Docker has spread the 10 services evenly over all of the nodes
```shell=
[node1] $ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kwgucargcd71 web.1 nginx:latest node4 Running Running 4 minutes ago
yla2e36xrv73 web.2 nginx:latest node5 Running Running about a minute ago
jzkvp5h1y2o0 web.3 nginx:latest node3 Running Running about a minute ago
aeopsb0gnip2 web.4 nginx:latest node2 Running Running about a minute ago
26uv5aujn6vf web.5 nginx:latest node5 Running Running about a minute ago
9l91jgq4z39e web.6 nginx:latest node4 Running Running about a minute ago
mk01nrm91stx web.7 nginx:latest node1 Running Running about a minute ago
ru8t79l4ltzo web.8 nginx:latest node1 Running Running about a minute ago
2xa6pqo4rjfi web.9 nginx:latest node3 Running Running about a minute ago
vlazyjipa91w web.10 nginx:latest node2 Running Running about a minute ago
```
![](https://i.imgur.com/0njYlyr.png)
We can also do a scale up or down by doing:
```shell=
[node1] $ docker service scale web=5
web scaled to 5
overall progress: 5 out of 5 tasks
1/5: running
2/5: running
3/5: running
4/5: running
5/5: running
verify: Service converged
```
![](https://i.imgur.com/SC0Dj0D.png)
In this moment the `viz` process is executing in `node1`. If we kill this service, the swarm will recreate it automatically in the same node or in another one of the swarm:
```shell=
[node1] $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
373f2e02b32c nginx:latest "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 80/tcp web.7.mk01nrm91stxpuo0qjru96mom
5f3ee10fa4f5 dockersamples/visualizer:latest "npm start" 10 minutes ago Up 10 minutes (healthy) 8080/tcp viz.1.1voq9cg02sjhi9kbeiyc6cq85
[node1] $ docker stop 5f3ee10fa4f5
5f3ee10fa4f5
[node1] $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
373f2e02b32c nginx:latest "/docker-entrypoint.…" 5 minutes ago Up 5 minutes 80/tcp web.7.mk01nrm91stxpuo0qjru96mom
[node1] $ docker service ps viz
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
3azlu5dipw1a viz.1 dockersamples/visualizer:latest node2 Running Starting 17 seconds ago
1voq9cg02sjh \_ viz.1 dockersamples/visualizer:latest node1 Shutdown Complete 36 seconds ago
```
![](https://i.imgur.com/gVTy0NE.png)
#### Adding a new service
We create now another service based on `redis` by doing:
```shell=
[node1] $ docker service create \
--replicas 3 \
--name redis \
--update-delay 10s \
redis:3.0.6
odbkv9uqhasiup238rbt8jcep
overall progress: 3 out of 3 tasks
1/3: running [==================================================>]
2/3: running [==================================================>]
3/3: running [==================================================>]
verify: Service converged
[node1] $ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
odbkv9uqhasi redis replicated 3/3 redis:3.0.6
1i9vcslauzx4 viz replicated 1/1 dockersamples/visualizer:latest *:8080->8080/tcp
tew0o5yjcd1p web replicated 5/5 nginx:latest *:80->80/tcp
[node1] $ docker service ps redis
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
d6achzobaxvy redis.1 redis:3.0.6 node1 Running Running about a minute ago
pz12mhdq08o2 redis.2 redis:3.0.6 node4 Running Running about a minute ago
209m1diu1iz2 redis.3 redis:3.0.6 node3 Running Running about a minute ago
```
![](https://i.imgur.com/BWS6Xac.png)
#### Updating a service
With swarm we can update automatically the version of the service that is currently executing, by doing:
```shell=
[node1] $ docker service update --image redis:3.0.7 redis
redis
overall progress: 3 out of 3 tasks
1/3: running [==================================================>]
2/3: running [==================================================>]
3/3: running [==================================================>]
verify: Service converged
[node1] $ docker service ps redis
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
f95vu5z6xpqj redis.1 redis:3.0.7 node1 Running Running about a minute ago
d6achzobaxvy \_ redis.1 redis:3.0.6 node1 Shutdown Shutdown about a minute ago
1f6youskoht0 redis.2 redis:3.0.7 node4 Running Running about a minute ago
pz12mhdq08o2 \_ redis.2 redis:3.0.6 node4 Shutdown Shutdown about a minute ago
97qc6hqts5xy redis.3 redis:3.0.7 node3 Running Running 2 minutes ago
209m1diu1iz2 \_ redis.3 redis:3.0.6 node3 Shutdown Shutdown 2 minutes ago
```
### Draining a node
You can also drain a particular node, that is remove all services from that node. The services will automatically be rescheduled on the other nodes.
For example, starting from:
![](https://i.imgur.com/h6fGRZZ.png)
if we drain all services from `node2` doing:
```shell=
[node2] $ docker node update --availability drain node2
```
we will get (after a few seconds):
![](https://i.imgur.com/i2iT9Dt.png)
To bring `node2` back online and show it's new availability, we have to do:
```shell=
[node1] $ docker node update --availability active node2
node2
[node1] $ docker node inspect node2 --pretty
ID: pectmlqbjc6oh0dsjrg5ec8bt
Hostname: node2
Joined at: 2021-04-15 14:52:15.365686958 +0000 utc
Status:
State: Ready
Availability: Active
Address: 192.168.0.17
Manager Status:
Address: 192.168.0.17:2377
Raft Status: Reachable
Leader: No
...
```
## There is still a lot more:
With Docker swarm you can also:
* Change node availability:
* drain a manager node so that only performs swarm management tasks and is unavailable for task assignment.
* drain a node so you can take it down for maintenance.
* pause a node so it can’t receive new tasks.
* restore unavailable or paused nodes available status.
* Promote or demote a node:
* You can promote a worker node to the manager role. This is useful when a manager node becomes unavailable or if you want to take a manager offline for maintenance. Similarly, you can demote a manager node to the worker role.
* a node can leave a swarm with: ```docker swarm leave```. After a node leaves the swarm, you can run the `docker node rm` command on a manager node to remove the node from the node list.
* Add manager nodes for fault tolerance
* Remove a manager...:
```shell=
[node1] $ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
vz9ii3frm4r7v2jx9brszwtip * node1 Ready Active Leader 20.10.0
9guxhwn7g47q87o6xsvirg5mn node2 Ready Active Reachable 20.10.0
96myll5dzbzfec7q08e8ucgcj node3 Ready Active Reachable 20.10.0
h0mcls38aesrustmlmx823ui5 node4 Ready Active 20.10.0
6k01qk0gclz9f6ychkbktjhv8 node5 Down Active 20.10.0
[node1] $ docker swarm leave --force
Node left the swarm.
```
now, if we go to another manager:
```shell=
[node2] $ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
vz9ii3frm4r7v2jx9brszwtip node1 Down Active Unreachable 20.10.0
9guxhwn7g47q87o6xsvirg5mn * node2 Ready Active Leader 20.10.0
96myll5dzbzfec7q08e8ucgcj node3 Ready Active Reachable 20.10.0
h0mcls38aesrustmlmx823ui5 node4 Ready Active 20.10.0
6k01qk0gclz9f6ychkbktjhv8 node5 Down Active 20.10.0
```
... much more!
:::info
You don't need to use the Docker CLI to perform these operations. You can use `docker stack deploy --compose-file STACKNAME.yml STACKNAME` instead. For an introduction to using a stack file in a compose file format to deploy an app, check out [Deploying an app to a Swarm](https://github.com/docker/labs/blob/master/beginner/chapters/votingapp.md).
:::