If you're running a whole stack of containers, … every container needs to be run with the proper setup to ensure that the underlying application is configured correctly and will run as expected. Getting these settings correct each and every time can be challenging, especially when you are not the person who originally wrote the application. To help with this during development, people often resort to writing shell scripts that can build and run their containers in a consistent manner. although this works, it can become difficult to understand for a newcomer and hard to maintain as the project changes over time. It's also not necessarily repeatable between projects. To help address this problem, Docker, Inc. released a tool primarily aimed at developers called Docker Compose. … Docker compose is an incredibly useful tool that can streamline all sorts of development tasks that have traditionally been very cumbersome and error-prone.
—Sean P. Kane & Karl Matthias, Docker Up & Running, 2nd Edition
Configuration of network namespaces, including creating virtual interfaces and bridges, assigning these to the appropriate namespaces, defining IP addresses and subnets, and establishing route and firewall rules, is a complex and error-prone process. Luckily, Docker automates all of this, allowing you to create complex networked container environments. For applications consisting of multiple, interconnected containers, Docker Compose provides a way to specify the containers and their networks, further automating the process of running and networking several containers at once.
In this studio, you will:
Please complete the required exercises below. We encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is allowed.
As you work through these exercises, please record your answers, and when finished upload them along with the relevant source code to the appropriate spot on Canvas.
Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.
As the answer to the first exercise, please list the names of the people who worked together on this studio.
Docker provides several types of network drivers to its containers, which are realized under the hood by different combinations of network namespace settings, veth and bridge interfaces, routes, and iptables firewall rules.
In today's studio, we will focus on three of those drivers: bridge
, host
, and none
.
The none
driver, as the name suggests, does not give a container access to the network.
Docker realizes this by establishing the container in its own network namespace,
then providing it with no connections to the outside.
To observe this behavior, first launch an Alpine Linux container with default network settings:
docker run -it --rm alpine:latest
Verify that it has access to the network by pinging an IP address, e.g., Google's public DNS server:
ping 8.8.8.8
Exit the container, then launch another instance, this time with no networking:
docker run -it --rm --network none alpine:latest
Attempt to ping from this container; the command should fail.
To observe the network capabilities that have been provided to your container,
issue the ifconfig
command from within the container.
This lists the available network interfaces, as well as their IP addresses.
To see your container's network namespace, issue the command:
ls -l /proc/self/ns/net
Compare this to the same command issued from outside the container.
As the answer to this exercise, please
(1) show the output of the ping command from inside the networkless container;
(2) show the output of the ifconfig
command, and explain what this tells you about the network resources that have been provided to the container;
(3) show the outputs of the ls -l
commands run from both inside and outside the container, and explain what these values tell you.
Once you've completed this exercise, you can exit the container.
The Apache HTTP Server "is a Web server application notable for playing a key role in the initial growth of the World Wide Web. Originally based on the NCSA HTTPd server, development of Apache began in early 1995 after work on the NCSA code stalled. Apache quickly overtook NCSA HTTPd as the dominant HTTP server, and has remained the most popular HTTP server in use since April 1996." (From https://hub.docker.com/_/httpd)
Apache, while a fully-featured web server, is also very simple to get up and running, especially with the image provided through the Docker hub.
Pull the image onto your Raspberry Pi by issuing the following command:
docker pull httpd
For this exercise, you will create a simple web application running in Apache (really, just a single web page, unless you want to get fancy and build something more complex for this studio).
Create a new directory on your Raspberry Pi. Like in the previous studio,
this directory will contain a Dockerfile, as well as a subdirectory for your application.
Call the subdirectory public-html
; this will contain the pages for your website.
In the subdirectory, create a file called index.html
;
this is the webpage that will be served when somebody navigates to your website.
You can make its contents whatever you like, or use the following, which will have your website just print "Hello world!":
<html><body>Hello world!</body></html>
Your Dockerfile should define an image that inherits from the Apache httpd
image you pulled, then copies your HTML directory to Apache's default directory:
FROM httpd:latest
COPY ./public-html/ /usr/local/apache2/htdocs/
Now build the image:
docker build -t apache-hello .
Notice that we do not specify a version tag;
Docker uses the latest
tag by default for both building and running images.
To verify that your image successfully built, issue the following command, and as the answer to this exercise, show its output:
docker image list
Now you will run an instance of this image in a container that uses Docker's default network driver,
the bridge
driver:
docker run -dit --rm --name hello-bridge apache-hello
Notice that we are running the container in detached mode (-d
)
but still providing it with input/output streams and a pseudo-terminal (-it
).
Also, notice that we can provide a name for the container,
rather than having Docker generate one automatically.
Docker allows you to inspect its various network drivers to see information about containers using those drivers. Issue the command:
docker network inspect bridge
This prints a large amount of JSON-formatted information about the bridge network to the terminal.
Find the section labeled "Containers," then find the entry for the "hello-bridge" container you just launched.
You should see an IPv4 address listed.
Open a web browser on your Raspberry Pi,
and navigate to that IP address,
e.g. http://xxx.xx.x.x
.
As the answer to this exercise, please write the IP address, and provide a screenshot of the webpage.
If you are running your Raspberry Pi without a desktop environment, you can instead retrieve the contents of the webpage with the Client URL (cURL) utility:
curl http://xxx.xx.x.x
In this case, run the command, and provide a screenshot of the command and its output.
Issue the command ifconfig
.
You should see several interfaces listed.
Look at the interface called docker0
(if that doesn't exist, find another interface with a name that containers "docker").
Additionally issue the command:
route -n
Find the route that corresponds to the subnet of the docker0
interface.
As the answer to this exercise, please
(1) show the IPv4 address (probably labeled as inet) and the corresponding netmask
of the docker interface, as reported by ifconfig
,
(2) show the corresponding route for that subnet as reported by route -n
,
and (3) explain what this tells you about how Docker established the network connection for your container.
Once you're done, use docker stop
to stop the container.
Docker also allows you to establish port forwarding from the host to a container.
This enables other devices on the network to send requests to the host on port A,
which are then forwarded to the container on port B
(note that A and B might be, though are not required to be, the same).
You can specify this behavior with the -p
flag,
like in the following command:
docker run -dit --rm --name hello-portforward -p 8080:80 apache-hello
Run this command. It forwards requests to the host on port 8080 to the container on port 80 (which is the port that Apache listens on by default).
From another computer on the same network as your Raspberry Pi, open a web browser, and open your webpage. For example, if your Raspberry Pi has the IP address 192.168.0.42 on your network, navigate to the following site:
http://192.168.0.42:8080/
As the answer to this exercise, please (1) provide a screenshot of the webpage.
Also, (2) issue the docker ps
command, and show its output;
then (3) explain what this command tells you about the port forwarding behavior of running containers on your system.
Finally, (4) issue the command:
sudo netstat -anp | grep docker
and show its output, then (5) explain what this tells you about how the docker-proxy
process performs port forwarding.
When you are done, use docker stop
to stop the container.
The third and final networking driver that we will be exploring today is the host
driver.
Launch your web application using this driver, i.e. with the command:
docker run -dit --rm --name hello-host --network host apache-hello
The host
driver sets up the container in the same network namespace as the host.
This means that, from the perspective of network resources,
the container is just another process running on the host,
in this case listening on port 80.
From another computer on the same network as your Raspberry Pi, open a web browser, and open your webpage. For example, if your Raspberry Pi has the IP address 192.168.0.42 on your network, navigate to the following site:
http://192.168.0.42/
Notice that the port is not specified; HTTP uses port 80 by default.
Issue the command:
ls -l /proc/self/ns/net
Then, issue this command from inside your container:
docker exec -ti hello-host ls -l /proc/self/ns/net
As the answer to this exercise, please (1) provide a screenshot of the webpage,
and (2) show the output of the two ls
commands.
What do the two reported network namespace values tell you about the behavior of the host
driver?
Once you're done, use docker stop
to stop the container.
For the final exercises of this studio,
you will create a second container that monitors your web application.
The monitoring container will simply continuously ping the Apache web server,
ensuring that it is still alive.
Because Alpine Linux has the ping utility installed by default,
this container will be based on the default alpine:linux
image.
Normally, to set up both a web server and a monitoring application, you would have to perform some variation of the following steps:
This is somewhat cumbersome to do manually, and very cumbersome to automate, e.g. as a startup task on the host. For even more complicated applications, this can be very tricky to get right. Docker Compose attempts to address this problem by allowing you to provide a manifest that defines the containers in the application, and the connections between them; it then automates the process of launching those containers.
For this exercise, you will retrieve a Docker Compose image for your two-container application, then run it. Docker Compose files can be a bit complicated, and the syntax takes some time to learn, so we provide you with the file, and walk you through its commands. This file will leave out some commands that would be useful in a production environment; if you want to learn more about Docker Compose, the Docker Compose online documentation has a complete guide to its syntax and usage. Chapter 8 of the optional DKR textbook also has an extended example of its use.
First, install Docker Compose on your Raspberry Pi:
sudo apt install docker-compose
Then, create a new directory, and download the following file into that directory: https://classes.engineering.wustl.edu/cse522/studios/docker-compose.yaml
This contains the composition instructions for your application. It defines services, which are the containers it will run. The first container, "apache," is your Apache web server. It uses the "apache-hello" image you built in a previous exercise, and establishes port forwarding on port 80.
The second container, "monitor," is the monitoring service.
This is based on the Alpine Linux base image,
and is launched with the command ping apache
.
Docker places containers in their own UTS hostname by default;
Docker Compose provides them with hostnames matching the service name,
then establishes DNS resolution for those hostnames.
That allows the monitoring container to ping the web server by its hostname.
The "monitor" container also depends on the "apache" container,
meaning that the "apache" container is launched first.
The networks section defines a network called "webserver" using the Docker bridge driver. Docker Compose allows you to establish an application with multiple bridges, providing the ability to create highly complex networked applications. Here, though, we create a single bridge network, and assign it to both container services.
Verify that the file is well formed, and that Docker Compose is correctly installed, by running the following command from the same directory where you saved the manifest file:
docker-compose config
If that's successful, build it with:
docker-compose build
Then run it in the background with:
docker-compose up -d
Once you've done this, run docker ps
to verify that your containers are both running.
As the answer to this exercise, show the output of that command.
Also, verify from another computer on the network that you can navigate to your website.
Provide a screenshot of the web page.
The monitoring container will continue to ping the Apache web server indefinitely. Even though it is not connected to a terminal, its output will be written to its Docker log file. This provides a rudimentary way to monitor the network status of the web server. More advanced applications would typically use fully-featured network monitoring services; nonetheless, this illustrates the idea.
Execute the following command:
docker logs CONTAINERNAME -f
where CONTAINERNAME
is the name of the monitoring container
as listed by the output of docker ps
.
The -f
flag forces the log stream to remain open,
and it will continue to output new lines added to the log.
This allows you to see, in real-time, the output of the monitoring container.
Allow this to proceed for a few seconds, then interrupt it with CTRL+C. As the answer to this exercise, copy the last few lines of the log output.
Page updated Thursday, March 10, 2022, by Marion Sudvarg and Chris Gill.