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