---
# System prepended metadata

title: 11. Docker Swarm to manage multiple containers
tags: [corsounipd2022]

---

# 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).
:::



