Troubleshooting Containers
Introduction
I find my self rather often than not in a situation where I have to trouble shoot a containerized application or a coworker needs some help doing so. In this post I want to show some practices how you can resolve your problems quickly.
On a side note: I have been nominated to be a Docker Captain. This is a big honor to me and I’m feeling really excited. For me containers in general and Docker containers in specific are some of the most disruptive and innovative features in IT.
Prerequisites
If you have not done so yet then please install Docker for Mac or Docker for Windows.
Why is this not working?
Let’s assume we are containerizing a RESTful API written in Python using Flask. The language and framework used here are not really relevant, the application could as well be a Node JS, a Java or a .NET application. What matters here is the techniques shown in an around containers.
Create a new project folder. Add a file app.py
to this folder. The content of this file defines our simple API and looks like this
If we have Python and Flask installed on our developer machine we can run this application as follows
python app.py
Then we open a Browser and navigate to http://localhost:5000/api/tasks
and should see this
So far so good. But the goal is to run this application in a container. Let’s add a Dockerfile
to our project folder
Now we want to build a Docker image using the above Dockerfile
. Open a terminal and navigate to the project folder. Execute the following command to build the image
docker build -t sample-api .
you should see the following output in you terminal
Well, that was easy. Now let’s try to run a container using the image we just built.
docker run --name my-api -d -p 5000:5000 sample-api
OK, in the above command we told Docker to run a container with the name my-api
as a service (or daemon), mapping the container port 5000 to the host port 5000 using the image sample-api
.
We can now double check whether we were successful by using this command
docker ps -a
We should see this
Wait a second… Let’s look at the status of the container. It says Exited
. What the heck…? Apparently there is a problem. Help…!?! What can we do?
Analyzing the Log
Although the container doesn’t run and is in the Exited
status it’s log is still available to us. We can access the log of any container by using the command
docker logs [Container ID]
or
docker logs [Container Name]
Let’s use the container name, my-api
in our case
docker logs my-api
OK, the log informs us that Python encountered an error. The module flask
could not be found. Oh yeah, my bad, I forgot to install this module. The Docker base image python:2.7
from which we inherit our image does not have any 3rd party modules installed. Let’s add a file requirements.txt
to our project and add flask as a dependency. The content of the file looks like this
now we can modify the Dockerfile
as follows
Note how I added two lines after the WORKDIR
command. One to copy the requirements.txt
file into the image and the other to run pip install
. We can now rebuild the image
docker build -t sample-api .
Once the image is built it is now including Flask
. We can try to run a container with the new image. But before we do that we need to remove the Exited
container which is still around using
docker rm my-api
now we’re good to go
docker run --name my-api -d -p 5000:5000 sample-api
executing docker logs my-api
will show the expected output
The Flask (web-) server is listening at port 5000. If we open a browser and navigate to http://localhost:5000/api/tasks
we see the same result as when we ran the Python application directly on our host.
Using an alternative Entrypoint
Let’s for a moment assume that we still have problems with our container and it doesn’t work as expected. What other means do we have to analyze the situation? The problem is that every time we run a container it exits with an error. In this situation it is helpful to have the opportunity to create a container from the very same image but this time we override the predefined entry point. The ENTRYPOINT
command in a Dockerfile
defines what action is executed upon start of the container. In our case the action is
python app.py
which means that we run the application whose starting point is defined in app.py
. Now let’s execute this command instead
docker run --rm -it --entrypoint /bin/bash sample-api
If we do this then a container based on the sample-api
image is started in interactive mode with an attached terminal (-it) and the command that is executed upon start of the container is /bin/bash
, that is, instead of starting our Python application we start a bash shell.
Doing that we find ourselves inside the container and have all possibilities to analyze the situation and do all kinds of experiments. We can e.g. investigate whether all expected assets are available and in the right place, or we can use pip
to install (missing) modules and even execute the default start command python app.py
.
If we do the latter then the difference here is that if an error happens we find ourselves still inside the container and can do further investigations and/or experiments.
Once we’re done we can exit the container by just typing exit
in the shell. The container will stop and will automatically be removed from the Docker workspace due to the command line argument --rm
we used when running the container.
Exec into a running Container
A third possibility we have is to intrude into a running container using the docker exec
command. This is a very useful method if the container is running but it’s behavior is somewhat unexpected. Let’s start a a container from our image sample-api
as we did before
docker run --name my-api -d -p 5000:5000 sample-api
We can verify using docker logs my-api
that the Flask web server is running and listening at port 5000. Now we can use the following command to break into the running container
docker exec -it my-api /bin/bash
with the above command we start a new process running bash inside the container my-api
. We also run this process interactively with terminal (-it).
We can now use the ps aux
command to show all running processes in the container
And indeed we see our Python application running as well as the bash shell that we just started.
Summary
In this post I have shown a few techniques that we can use to trouble shoot misbehaving containers. I have shown how we can retrieve the log generated by the application running inside the container using docker logs
. I have also shown how we can override the entrypoint of a container upon start. Last I have discussed how one can hijack a running container using the docker exec
command. These are only the most straight forward techniques. I’m sure that I will elaborate on more advanced techniques in a future post. Please stay tuned.