Lab 3 : Docker Images

POD parameters : OpenStack Group-1 user0 aio110 10.1.64.110 compute120 10.1.64.120 [email protected]
User aioX computeY Network & Allocation Pool
user0
vnc  : lab.onecloudinc.com:5900
aio110
eth0            : 10.1.64.110
eth1            : 10.1.65.110
eth2            : ext-net
Netmask  : 255.255.255.0
Gateway  : 10.1.64.1
compute120
eth0            : 10.1.64.120
eth1            : 10.1.65.120
eth2            : ext-net
Netmask  : 255.255.255.0
Float Range  : 10.1.65.0010.1.65.00
Network         : 10.1.65.0/24
Gateway         : 10.1.65.1
DNS                   : 10.1.1.92

Introduction

Docker images are the basis of containers. In lab -1 we used Docker images that already exist, for example the centos image and also discovered that Docker stores downloaded images on the Docker host. If an image isn’t already present on the host, then it’ll be downloaded from a registry: by default, the Docker Hub Registry.

1. Basic Commands

The following table summarizes the instructions; many of these options map directly to option in the “docker run” command:

Command Description
ADD Copies a file from the host system onto the container
CMD The command that runs when the container starts
ENTRYPOINT Allows to configure a container that will run as an executable
ENV Sets an environment variable in the new container
EXPOSE Opens a port for linked containers
FROM The base image to use in the build. This is mandatory and must be the first command in the file.
MAINTAINER An optional value for the maintainer of the script
ONBUILD A command that is triggered when the image in the Docker file is used as a base for another image
RUN Executes a command and save the result as a new layer
USER Sets the default user within the container
VOLUME Creates a shared volume that can be shared among containers or by the host machine
WORKDIR Set the default working directory for the container

Once we have created a Docker file and added all instructions, we can use it to build an image using the docker build command. The format for this command is:

 $ docker build [OPTIONS] PATH | URL | -

The build command results in a new image that user can start using docker run, just like any other image. Each line in the Docker file will correspond to a layer in the images’ commit history.

2. Building a Docker Image

The general Docker workflow is:

  • Start a container based on an image in a known state
  • Add things to the filesystem, such as packages, codebases, libraries, files, or anything else
  • Commit the changes as layers to make a new image

2.1 Committing Docker Image

It is useful to commit a container’s file changes or settings into a new image. This allows user to debug a container by running an interactive shell, or to export a working dataset to another server.

As with the previous labs, you will need to SSH the aio node.
If you have logged out, SSH into your AIO node:

ssh centos@aio110

If asked, the user password (as with the sudo password) would be centos, then become root via the sudo password:

sudo su –
  1. Search for a suitable image by using the docker search command to find all the images that contain the term centos.
  2. docker search fedora
  3. Identified a suitable image, centos and now download it using the docker pull command.
  4. docker pull fedora
  5. List the images locally on host.
  6. docker images
  7. Run a container, refer to a tagged image.
  8. docker run --name fedora_custom -it fedora /bin/bash
  9. Installing Maria dB-server packages inside the image.
  10. dnf install mariadb-server -y
    dnf install git -y
    Note: Fedora uses dnf as a Package Manager instead of yum.
  11. Exit from the interactive mode.
  12. exit
  13. To check all the containers, use this command.
  14. docker ps -a
  15. Now have a container with the change want to make. Then commit a copy of this container to an image using the docker commit command.
  16. docker commit -m "added mariadb-server and git" -a "OneCloud" fedora_custom labs/fedora_custom

    Here we used the docker commit command. We specified two flags: -m and -a. The -m flag allows us to specify a commit message, much like we would with a commit on a version control system. The -a flag allows us to specify an author for our update.

  17. Check images list.
  18. docker images

    Output:

    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    labs/fedora_custom   latest              bf294a57bdc0        8 minutes ago       579.7 MB

2.2 Building Image from a Dockerfile

Dockerfile is used for automation of work by specifying all step that we want on docker image. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using Docker build users can create an automated build that executes several command-line instructions in succession.

  1. First, create a directory and a Dockerfile.
  2. mkdir example
    cd example
  3. Each instruction creates a new layer of the image.
  4. cat > Dockerfile <<EOF
    # This is a comment  
    FROM ubuntu
    RUN apt-get update && apt-get install -y ruby ruby-dev 
    EOF
    Note:The first instruction FROM tells the source of our image and RUN instruction executes a command inside the image.
  5. Now let’s take Dockerfile and use the docker build command to build an image.
  6.  docker build -t example .

    Output:

    Sending build context to Docker daemon 2.048 kB
    Step 1 : FROM ubuntu
     ---> bd3d4369aebc
    Step 2 : RUN apt-get update && apt-get install -y ruby ruby-dev
     ---> Using cache
     ---> 9f65bddd0425
    Successfully built 9f65bddd0425
    Note: Location of Dockerfile using the to indicate a Dockerfile in the current directory.
  7. Add a tag to an existing image after commit or build it. We can do this using the docker tag command. Now, add a new tag to example image.
  8. Syntax

    docker tag docker-image-id repo-name:tag
    docker tag example example:ver1
  9. Check the image we have built on host.
  10. docker images

    Output:

    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    example              latest              9f65bddd0425        3 minutes ago       206.8 MB
    example              ver1                9f65bddd0425        3 minutes ago       206.8 MB

Some more activity on Dockerfile

  1. Create a dockerfile.
  2. cat > test <<EOF
    ############################################################
    # Dockerfile to build MongoDB container images
    # Based on Ubuntu
    ############################################################
    # Set the base image to Ubuntu
    FROM ubuntu
    # File Author / Maintainer
    MAINTAINER Example Test
    # Update the repository sources list
    RUN apt-get update
    ################## BEGIN INSTALLATION ######################
    # Install MongoDB Following the Instructions at MongoDB Docs
    # Ref: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/
    # Add the package verification key
    RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
    # Add MongoDB to the repository sources list
    RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/mongodb.list
    # Update the repository sources list once more
    RUN apt-get update
    # Install MongoDB package (.deb)
    RUN apt-get install -y mongodb-10gen
    # Create the default data directory
    RUN mkdir -p /data/db
    ##################### INSTALLATION END #####################
    # Expose the default port
    EXPOSE 27017
     # Default port to execute the entrypoint (MongoDB)
    CMD ["--port 27017"]
    # Set default container command
    ENTRYPOINT usr/bin/mongod
    EOF
  3. Take Dockerfile and use the docker build command to build an image.
  4. docker build -f /root/example/test .
  5. Check the image built on host.
  6. docker images
  7. Add a tag to an existing image after commit or build it. We can do this using the docker tag command. Now, add a new tag to example image.
  8. docker tag `docker images | head -2 | tail -1 | cut  -d " " -f 30` monogodb:ubuntu
  9. Check the image that has built.
  10. docker images
  11. Come out of the directory “example”.
  12. cd

2.3 Launching a newly built Container

In this step, verify the new images and then run new image. Open a command line terminal type docker images. This command lists the images we have locally.

docker images

Specify tag_name it will automatically run image with ‘latest’ tag. Instead of image_name we can also specify Image Id (no tag_name).

Run newly built image,
Syntax

docker run image-name:tag 
docker run --name custom_container -dit monogodb:ubuntu
Note: You may also notice that Docker didn’t have to download anything. That is because the image was built locally and is already available.

2.4 Configuring automatic restart

To ensure containers are always running with Docker’s Restart Policy, let’s understand a bit more about how Docker behaves when an application crashes. To facilitate this, we’ll create a Docker container that executes a simple bash script named crash.sh.

mkdir scripts
cd scripts
cat > crash.sh <<EOF
/bin/bash
sleep 30
exit 1
EOF

The above script is simple; when started, it will sleep for 30 seconds, and then it will exit with an exit code of 1 indicating an error.

Building and running a custom container

In order to run this script within a container, we’ll need to build a custom Docker container which includes the crash.sh script. In order to build a custom container, we first need to create a simple Dockerfile.

cat > Dockerfile <<EOF
FROM ubuntu:14.04
ADD crash.sh /
CMD /bin/bash /crash.sh
EOF

The above Dockerfile will build a container based on the latest ubuntu:14.04 image. It will also add the crash.sh script into the / directory of the container. The final line tells Docker to execute the crash.sh script when the container is started.

With the Dockerfile defined, we can now build our custom container using the docker build command.

docker build -f /root/scripts/Dockerfile .

Sending build context to Docker daemon 3.072 kB

Step 1 : FROM ubuntu:14.04
 ---> e36c55082fa6
Step 2 : ADD crash.sh /
 ---> eb6057d904ef
Removing intermediate container 5199db00ba76
Step 3 : CMD /bin/bash /crash.sh
 ---> Running in 01e6f5e12c3f
 ---> 0e2f4ac52f19
Removing intermediate container 01e6f5e12c3f
Successfully built 0e2f4ac52f19

This build command created a Docker image with a tagged name of testing_restarts. We can now start a container using the testing_restarts image by executing docker run.

docker images
docker tag `docker images | head -2 | tail -1 | cut  -d " " -f 30` testing_restarts:ver1
docker images

Output:

REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
testing_restarts     ver1                532b81fe3ed4        46 seconds ago      187.9 MB
docker run -d --name testing_restart testing_restarts:ver1

Output:

a35bb16634a029039c8b34dddba41854e9a5b95222d32e3ca5624787c4c8914a

From the above, it appears that Docker was able to start a container named testing_restarts. Let’s check the status of that container by running docker ps.

docker ps

Output:

CONTAINER ID       IMAGE               COMMAND             CREATED             STATUS             PORTS

The docker ps command doesn’t show any running containers. The reason for this is because docker ps by default only shows running containers. Let’s take a look at running and non-running containers by using the -a flag.

docker ps -a

Output:

CONTAINER ID    IMAGE               COMMAND                  CREATED         STATUS     PORTS         NAMES
a35bb16634a0    testing_restarts    "/bin/sh -c '/bin/bas"   9 minutes ago   Exited (1) 8 minutes ago

With the docker ps results, we can see that when an application within a Docker container exits, that container is also stopped. This means that, by default, if an application that is running within a container crashes, the container stops and that container will remain stopped until someone or something restarts it.

Changing Docker’s Default Behavior

It’s possible to automatically restart crashed containers by specifying a restart policy when initiating the container. To understand restart policies better, let’s see what happens when we use the always restart policy with this same container.

docker run -d --name testing_restart1 --restart always testing_restarts:ver1

Output:

8320e96172e4403cf6527df538fb7054accf3a55513deb12bb6a5535177c1f19

In the above command, we specified that Docker should apply the always restart policy to this container via the –restart flag. Let’s see what effect this has on our container by executing a docker ps again.

docker ps

Output:

CONTAINER ID   IMAGE             COMMAND                 CREATED              STATUS     PORTS    NAME  
8320e96172e4   testing_restarts  "/bin/sh -c '/bin/bas"  About a minute ago   Up 21 seconds

This time we can see that the container is up and running but only for 21 seconds. If we run docker ps again, we will see something interesting.

docker ps

Output:

CONTAINER ID    IMAGE             COMMAND                 CREATED              STATUS      PORTS  NAMES
8320e96172e4    testing_restarts  "/bin/sh -c '/bin/bas"  About a minute ago   Up 19 seconds 

The second run shows the container has only been up for 19 seconds. This means that even though our application (crash.sh) continues to exit with an error, Docker is continuously restarting the container every time it exits.

Now that we understand how restart policies can be used to change Docker’s default behavior, let’s take a look at what restart policies Docker has available.

Docker’s Restart Policy

Docker currently has four restart policies:

  1. No
  2. on-failure
  3. unless-stopped
  4. always

The no policy is the default restart policy and simply does not restart a container under any circumstance.

Restarting on failure but stopping on success

The on-failure policy is a bit interesting as it allows to tell Docker to restart a container if the exit code indicates error but not if the exit code indicates success. Also specify a maximum number of times Docker will automatically restart the container.

Let’s try this restart policy out with our testing_restarts container and set a limit of 5 restarts.

docker run -d --name testing_restart2 --restart on-failure:5 testing_restarts:ver1

Output:

85ff2f096bac9965a9b8cffbb73c1642bf7b64a2173bbd145961231861b95819

If we run docker pswithin a minute of launching the container, we will see that the container is running and has been recently started.

docker ps

Ouput:

CONTAINER ID    IMAGE                   COMMAND                 CREATED             STATUS      PORTS  NAMES
85ff2f096bac    testing_restarts:ver1  "/bin/sh -c '/bin/bas"  About a minute ago  Up 8 seconds

The same will not be true, however, if we run the docker ps command 3 minutes after launching the container.

docker ps -a

Output:

CONTAINER ID     IMAGE                    COMMAND                  CREATED             STATUS   PORTS   NAMES
85ff2f096bac     testing_restarts:ver1    "/bin/sh -c '/bin/bas"   3 minutes ago Exited (1) 20 seconds ago

We can see from the above that after 3 minutes the container is stopped. This is due to the fact that the container has been restarted more than our max-retries setting.

With success

The benefit of on-failures is that when an application exits with a successful exit code, the container will not be restarted. Let’s see this in action by making a quick minor change to the crash.sh script.

The change will be to set the exit code to 0.

cat > crash.sh <<EOF
/bin/bash
sleep 30
exit 0
EOF

By setting the script to exit with a 0 exit code, we will be removing the error indicator from the script. Meaning as far as Docker can tell, this script will execute successfully every time. With the script changed, we will need to rebuild the container before we can run it again.

docker build -f /root/scripts/Dockerfile .

Output:

Sending build context to Docker daemon 3.072 kB

Step 1 : FROM ubuntu:14.04
 ---> e36c55082fa6
Step 2 : ADD crash.sh /
 ---> a4e7e4ad968f
Removing intermediate container 88115fe05456
Step 3 : CMD /bin/bash /crash.sh
 ---> Running in fc8bbaffd9b9
 ---> 8aaa3d99f432
Removing intermediate container fc8bbaffd9b9
Successfully built 8aaa3d99f432

With the container image rebuilt, let’s launch this container again with the same on-failures and max-retries settings.

docker images
docker tag `docker images | head -2 | tail -1 | cut  -d " " -f 30` testing_restarts:ver2
docker run -d --name testing_restarts3 --restart on-failure:5 testing_restarts:ver2

Ouput:

f0052e0c509dfc1c1b112c3b3717c23bc66db980f222144ca1c9a6b51cabdc19

This time, when we perform a docker ps -a execution, we should see some different results.

docker ps -a

Output:

CONTAINER ID    IMAGE                     COMMAND                  CREATED          STATUS    PORTS NAMES
f0052e0c509d    testing_restarts:ver2    "/bin/sh -c '/bin/bas"   41 seconds ago    Exited (0) 11seconds ago

Since the crash.sh script exited with a successful exit code (0), Docker understood this as a success and did not restart the container.

Always restart the container

If we wanted the container to be restarted regardless of the exit code, we have a couple of restart policies we could use:

  • always
  • unless-stopped

The always restart policy tells Docker to restart the container under every circumstance. We experimented with the always restart policy earlier, but let’s see what happens when we restart the current container with the always restart policy.

docker run -d --name testing_restarts4 --restart always testing_restarts:ver2

Output:

676f12c9cd4cac7d3dd84d8b70734119ef956b3e5100b2449197c2352f3c4a55

If we wait for a few minutes and run docker ps -a again, we should see that the container has been restarted even with the exit code showing success.

docker ps -a

Output:

CONTAINER ID   IMAGE                   COMMAND                  CREATED             STATUS     PORTS   NAMES
9afad0ccd068   testing_restarts:ver2   "/bin/sh -c '/bin/bas"   4 minutes ago       Up 22 seconds

What’s special about the always restart policy is that even if our Docker host was to crash on boot, the Docker service will restart our container. Let’s see this in action to fully appreciate why this is useful.

reboot

Run the below commands after reboot.

ssh centos@aio110
sudo su -

By default, or even with on-failures, our container would not be running on reboot. Which, depending on what task the container performs, may be problematic.

docker ps -a

Output:

CONTAINER ID     IMAGE                    COMMAND                  CREATED         STATUS     PORTS   NAMES
676f12c9cd4c     testing_restarts:ver2    "/bin/sh -c '/bin/bas"   9 minutes ago   Up 2 seconds

With the always restart policy, that is not the case. The always restart policy will always restart the container. This is true even if the container has been stopped before the reboot.

Let’s look at that scenario in action.

docker stop testing_restarts4

Output:

testing_restarts4
reboot

Run the below commands after reboot.

ssh centos@aio110
sudo su -

Before rebooting our system, we simply stopped the container. This means the container is still there, just not running. Once the system is back up after our reboot however, the container will be running.

docker ps

Output:

CONTAINER ID        IMAGE       COMMAND      CREATED    STATUS      PORTS             NAMES
676f12c9cd4c      testing_restarts:ver2    "/bin/sh -c '/bin/bas"   11 minutes ago   Up 24 second

The reason our container is running after a reboot is because of the always policy. Whenever the Docker service is restarted, containers using the always policy will be restarted regardless of whether they were running or not. The problem is that restarting a container that has been previously stopped after a reboot can be a bit problematic. What if our container was stopped for a valid reason, or worse, what if the container is out of date?

The solution for this is the unless-stopped restart policy.

Only stop when Docker is stopped

The unless-stopped restart policy behaves the same as always with one exception. When a container is stopped and the server is rebooted or the Docker service is restarted, the container will not be restarted. Let’s see this in action by starting the container with the unless-stopped policy and repeating our last example.

docker run -d --name testing_restarts5 --restart unless-stopped testing_restarts:ver2

Output:

fec5be52b9559b4f6421b10fe41c9c1dc3a16ff838c25d74238c5892f2b0b36

With the container running, let’s stop it and reboot the system again.

docker stop testing_restarts5

Output:

testing_restarts5
reboot

Run the below commands after reboot.

ssh centos@aio110
sudo su -

This time when the system restarts, we should see the container is in a stopped state.

docker ps -a

Output:

CONTAINER ID    IMAGE                    COMMAND                 CREATED         STATUS     PORTS   NAMES
fec5be52b955    testing_restarts:ver2    "/bin/sh -c '/bin/bas"  2 minutes ago Exited (137) About a minute ago

One important item with unless-stopped is that if the container was running before the reboot, the container would be restarted once the system restarted. We can see this in action by restarting our container and rebooting the system again.

docker start testing_restarts5

Output:

testing_restarts5
reboot

Run the below commands after reboot.

ssh centos@aio110
sudo su -

After this reboot, the container should be running.

docker ps -a

Output:

CONTAINER ID    IMAGE                    COMMAND                  CREATED        STATUS    PORTS    NAMES
fec5be52b955    testing_restarts:ver2    "/bin/sh -c '/bin/bas"   5 minutes ago  Up 13 seconds     testing_restarts

The difference between always and unless-stopped may be small, but in some environments this small difference may be a critical decision.

To remove all the containers run the below commands.

docker rm `docker ps -a -q` -f

To remove all the images run the below commands.

docker rmi `docker images -q` -f