The course is part of these learning paths
Docker has made great strides in advancing development and operational agility, portability, and cost savings by leveraging containers. You can see a lot of benefits even when you use a single Docker host. But when container applications reach a certain level of complexity or scale, you need to make use of several machines. Container orchestration products and tools allow you to manage multiple container hosts in concert. Docker swarm mode is one such tool. In this course, we’ll explain the architecture of Docker swarm mode, and go through lots of demos to perfect your swarm mode skills.
Learning Objectives
After completing this course, you will be able to:
- Describe what Docker swarm mode can accomplish.
- Explain the architecture of a swarm mode cluster.
- Use the Docker CLI to manage nodes in a swarm mode cluster.
- Use the Docker CLI to manage services in a swarm mode cluster.
- Deploy multi-service applications to a swarm using stacks.
Intended Audience
This course is for anyone interested in orchestrating distributed systems at any scale. This includes:
- DevOps Engineers
- Site Reliability Engineers
- Cloud Engineers
- Software Engineers
Prerequisites
This is an intermediate-level course that assumes:
- You have experience working with Docker and Docker Compose
Services are what make up distributed applications running on a swarm. In this lesson, we'll get experience running and managing services in our swarm.
Agenda
To begin, I'll give a quick rundown of what services we'll be running, and then we'll get into the demo.
The Plan
I'll use two images for demonstrating how to work with services.
The first is a swarm visualizer provided by Docker. It allows you to visualize the state of nodes in a swarm and see where service tasks have been scheduled. It requires information that only manager nodes have access to. We'll constrain the placement of the service to make sure it gets what it needs.
The second is a web service that serves a simple web page that displays the name of the node running the task. This will give us a way to verify that requests are load balanced across multiple nodes when using the ingress network in swarm.
Demo
Ok, now let's get to the demo.
When working with services, all of the commands are conveniently located under the docker service management command
$ docker service --help
There are some familiar commands: inspect, logs, ls, ps, and rm. They do what you would expect given your knowledge of the Docker CLI. We'll use them as we work through this demo. We'll give the rest more attention, starting with create.
$ docker service create --help | more
This is the equivalent of docker run for swarm services. There are too many options to go through. Several match docker run options and several others are unique to services. We'll go through some of the unique ones in this lesson and save some for the next.
Let's start by creating the swarm visualizer. The visualizer must run on managers, so we can use a constraint on the node role to handle that. For demonstration purposes, I'll make the service global so that every manager will run one task for the service. The service could be load-balanced since the swarm state that the service visualizes is the same regardless of which manager you use. But I will publish the port using host mode so we can compare that to ingress mode. Ingress mode is the default, so the mode=host part of the string must be provided. The mount option is required so that the service containers can access the manager node's docker daemon socket. That is where it pulls the swarm state information from. Finally, we'll give the service the name viz and specify the latest version of the dockersamples/visualizer image.
$ docker service create \
--constraint=node.role==manager \
--mode global \
--publish mode=host,target=8080,published=8080 \
--mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
--name=viz \
dockersamples/visualizer
The commands can get pretty long and we'll see how to bettern manage them in the next lesson. I'll speed things up until it is finished. The service converged message lets us know that the actual state has converged to the desired state in the service spec. We can see the service spec using inspect
$ docker service inspect viz --pretty | more
This output shows some of the default values that were used, such as the update and rollback config. It also shows that the service mode is global and that the port has been published in host mode. We can use the ps command to confirm the actual state matches the desired state
$ docker service ps viz
Now let's switch over to a web browser to see the swarm visualizer. The manager is running on vm1 which has an IP address of 192.168.99.100. The visualizer displays a column for each node. The node's name, role, memory, operating system, and the hard to read text is the node labels we applied earlier. Tasks are shown with squares under each node's heading. Currently there is only the one visualizer task. If we didn't have any role constraint there would be one on every node, but because the service was constrained to managers, there is only one. Task borders are color-coded according to their service. Because the service published its port in host mode, I have to use the manager's IP. If I try vm2's IP, it won't be able to reach the service. So I'll go back to vm1's IP address.
I'll promote vm2 to be a manager and that will cause a viz replica to be started on vm2 because the viz service is global. I'll use the ls command to see the change. Notice the replicas has jumped up to 2. After awhile there will be 2 of 2 tasks running and I can try again to access the visualizer on vm2 in the browser. There it is. Both vm1 and vm2 are manager's and there are two replicas of the viz service shown. I'll go back to vm1's visualizer and demote vm2 back to a worker. Now we're back to just one viz replica.
Let's focus in on the 2nd service now. I'll switch over to VS Code and quickly go through the source. This file, index.php, is doing going to echo back the node name which it gets from an environment variable. That's all there is to it. Taking a look at the Dockerfile, the base image is an php image with the apache web server installed. The index.php source file is copied into the image and the web server serves it on port 80. There is also a healthcheck embedded in the image. You can of course override the image healthcheck or create a healthcheck if the image doesn't have one when you create the service. This is the same behavior as with docker run.
Back to the command line. We'll create the service with a constraint to not schedule any tasks in zone 1. The exclamation mark followed by equals means not equal to. We'll declare 2 replicas. This implies the service mode is replicated and not global. replicated is also the default. The manager will try to spread the tasks over available nodes by default, but we will specify a placement preference to take control of how it spreads. Next, I'll add an environment variable for the NODE_NAME and use a Go template to get the hostname of the node. Port 80 will be published in ingress mode by default making the service reachable from any node's IP address regardless of if a task is running on the node. I'll give the service the name nodenamer and specify version 1.0.0.
$ docker service create \
--constraint node.labels.zone!=1 \
--replicas 2 \
--placement-pref 'spread=node.labels.zone' \
-e NODE_NAME='{{.Node.Hostname}}' \
--publish 80:80 \
--name nodenamer \
lrakai/nodenamer:1.0.0
Now the two tasks move through the preparing, and starting states to reach the running state before the service converges to the desired state. We can check on the swarm state in the visualizer. There are two tasks for the nodenamer service and they have a dark grey border. They are deployed evenly across all the zones except zone 1 as we constrained the placement. We can also test the ingress routing capabilities. I'll send a request to port 80 on vm1 which isn't running a task for the service. But the page loads thanks to the ingress routing mesh. if I reload a few times, you can see the node name changing. This illustrates the virtual IP load balancing of the service. If I send my requests to vm2, the behavior is the same.
Back at the command-line I can also access the service at localhost on vm1 through the ingress network.
$ curl localhost
The output doesn't have new line so the command prompt gets tacked onto the end. Version 1.0.1 of nodenamer fixes this issue. We'll do a rolling update to version 1.0.1. Before we do, we can inspect the service
$ docker service inspect nodenamer
and observe the setting for updates. Parallelism is one by default so only one task will be upgraded at a time. We won't look at failed updates, so the only other relevant setting is update order which is only available in Docker 17.05 and above. It defaults to stopping existing tasks and then starting a new task. The other value is start first, which starts a new task first and then stop the old one when the new task is running. There's also update delay which sets the delay between rolling updates. It defaults to zero which is not a problem in this case, we'll still be able to see the update roll through because it takes some time for new tasks to get to running. Let's do the update to version 1.0.1
$ docker service update --image lrakai/nodenamer:1.0.1 nodenamer
And switch over the the visualizer to watch the update roll through. The old task on vm2 is taken down first, then a new task using the 1.0.1 image comes in. As soon as it reaches the running state, the task on vm3 is stopped and a new 1.0.1 image task starts.
Now if I curl localhost from vm1, the new line is added to the output.
We can scale up the service to, say 6 replicas using the scale command
$ docker service scale replicas=6
and we will see them come up equally spread across the eligible nodes. Even though there are more than one task on each node there are no port conflicts. This wouldn't be the case if host mode was used for publishing the service port.
Let's set the parallelism for rollbacks to 2 so we can rollback faster than we update.
$ docker service update --rollback-parallelism 2
Then we intentionally update the service back to version 1.0.0 so we can finish the lesson with a rollback to version 1.0.1.
$ docker service update --image lrakai/nodenamer:1.0.0 nodenamer
And we can see it roll through 1 task at a time.
Now we can rollback
$ docker service rollback nodenamer
and we should see two tasks at a time being rolled back. There we see it rolling back two at a time.
That's it for this lesson. You now know how to put the theory we studied earlier into practice by using the docker service management command for managing services.
Closing
In the next lesson, we'll cut down on the lengthy commands and improve the repeatability and maintainability of swarm service deployments by using stacks. Whenever you are ready, continue on to the next lesson.
Logan has been involved in software development and research since 2007 and has been in the cloud since 2012. He is an AWS Certified DevOps Engineer - Professional, AWS Certified Solutions Architect - Professional, Microsoft Certified Azure Solutions Architect Expert, MCSE: Cloud Platform and Infrastructure, Google Cloud Certified Associate Cloud Engineer, Certified Kubernetes Security Specialist (CKS), Certified Kubernetes Administrator (CKA), Certified Kubernetes Application Developer (CKAD), and Certified OpenStack Administrator (COA). He earned his Ph.D. studying design automation and enjoys all things tech.