A Practical Introduction to Docker Containers

Background

Why

Docker has quite an amount of buzz around it today because it makes so many things easy that were difficult with virtual machines.

Docker containers makes it easy for Developers, Systems Administrators, Architects, Consultants and others to quickly test a piece of software in a container; much quicker than a virtual machine, and using less resources. The average command in Docker takes under a second to complete.
[root@keith]# time docker run fedora cat /etc/redhat-release
Fedora release 20 (Heisenbug)
real 0m0.715s
user 0m0.004s
sys 0m0.004s

What

Simple Use Cases

  • I need to see the man page from a specific version of RHEL, CentOS or Fedora
  • I need to quickly verify the command line options of a program
  • I need to test the functionality of a specific version of software
  • I need a scratch pad that is NOT my system
  • I need a single daemon running, and I don’t care what distribution of Linux it runs on (see registry below)

 

Complex Use Cases

Docker containers run a single process when started, but complex installations of software which require multiple daemons running simultaneously (RHEV-M, Satellite, etc) can be done. However, they require more engineering work using either Bash scripting or something like: Using Supervisor with Docker or SystemD.

Production vs. Development

At the time of this writing Docker has not yet reached version 1.0 and Red Hat Enterprise Linux 7 has not yet been released as generally available, so production workloads are not recommended. Also, take note that some of the examples in this tutorial make use of docker to test software from different versions of CentOS and Red Hat Enterprise Linux; while this will have limited support, it is quite useful for quick testing and reading man pages.

CentOS and Red Hat Enterprise Linux

This tutorial will focus on integration with Red Hat technologies including CentOS and Red Hat Enterprise Linux. As mentioned, at the time of this writing, Docker has not yet reached version 1.0 and Red Hat Enterprise Linux 7 has not yet been released as generally available, so the majority of the hands on portion of this tutorial will use CentOS. Many of these tutorials can also be completed using Fedora.

Architecture

One of the key advantages of using Docker is it’s centralized image management server, called a Registry Server. The Docker project maintains a public registry server which hosts images they maintain, as well as images created by the community. This registry service is free to use, as long as the images are public.

As one builds images, they are made up of layers. These layers are shared together in, what is called a repository. Users on the registry can share multiple repositories.

Docker has an official CentOS 6 repository which they support:

This tutorial will use several public CentOS repositories which I have created and shared on Docker’s public registry. I have created a separate repository for each major version of CentOS:

 

OS Virtualization vs. Application Virtualization

Why separate repositories for each major version of Red Hat Enterprise Linux or CentOS? This was a conscious decision because we are working with a full enterprise Linux distribution versus a single application. Historically, it has been best practice to install a fresh copy when upgrading major version of Red Hat Enterprise Linux, and CentOS. While it is possible to upgrade in place and share multiple versions in a single repository, this is not the preferred method with an enterprise operating system.

However, when virtualizing individual applications, it may be very appropriate to upgrade in place and share all versions within a single repository (See section: Set Up a Registry Server).

Known Caveats

After reading this tutorial, you may be excited to set up your own registry server. This is easy to do, but has some caveats, so think think through whether it is worth just using a hosted registry server.

  • As of version 0.6.9, the private registry server does not have any kind of authentication support without using an HTTP proxy such as Apache or NginX.
  • As of version 0.6.9, the private registry server has support for search, but the client can not search them by default, so a browser must be used to view the images.

 

Basic Operations

Now we will run through some basic operations to get you up and running

Install Docker

In CentOS 6, this requires the user space tools from the EPEL. Personally, I prefer to disable after installation so that later, full system updates don’t break the system.

rpm -ivh http://mirrors.syringanetworks.net/fedora-epel/6/i386/epel-release-6-8.noarch.rpm
yum install docker-io
yum-config-manager --disable epel
service docker start
chkconfig docker on

Test Docker

This will automatically pull the latest CentOS 4 image from the remote repository and cache it locally.
docker run -i -t -rm fatherlinux/centos4-base cat /etc/redhat-release

Pull an Image

This will pull the latest CentOS 5 image from the remote repository and cache it in the local index. If the image you are pulling is made up of layers, all of the layers will be pulled.
docker pull fatherlinux/centos5-base:latest

List Images

This will list all of the images in the local index and display how they are linked to each other. Every time a new container is spawned from an image, it will create another copy on write image to save it’s changes too. The tree structure will help make things clear.
docker images --tree

Tag an Image

It makes it easier to deal with images if they are tagged with simple names
docker tag fatherlinux/centos5-base centos5-base

Run a Container

Notice how easy it is to test a command in CentOS 4
docker run -i -t -rm fatherlinux/centos4-base man rsync

Log in to the Hosted Docker Registry

To create repositories on the public Docker Registry, it is necessary to sign up at:

https://www.docker.io/account/signup/
Once you have created an account, you will need to login from the command line

docker login index.docker.io
If login is successful.

Username: fatherlinux
Password:
Email: [email protected]
Login Succeeded

Dockerfile: Commit an Image

Once logged in to the public Docker Registry, new images can be built and committed with code using a Dockerfile. This allows an administrator to automatically rebuild a known good starting point quickly and easily. It is recommended to always start with an image defined by a Dockerfile.

There are a couple of important things to notice with this Dockerfile. First, the FROM directive specifies a username and repository: fatherlinux/centos6-base. This will pull the latest image from the centos6-base repostiory. In this example, I have provided the source repository for you. Second, the only change we have specified in the Dockerfile, is to update CentOS to latest available packages.

vi Dockerfile
# crunchtools image
#
# Version 1

# Pull from CentOS RPM Build Image
FROM fatherlinux/centos6-base

MAINTAINER Scott McCarty [email protected]

# Update the image
RUN yum update -y

# Output
#ENTRYPOINT tail /var/log/yum.log

Build and tag the image

docker build -t centos6-base-updated .

Inspect the new image

This will list all of the layers in an image
docker images --tree
├─f1e4a248eff0 Virtual Size: 798.4 MB Tags: 10.3.76.138:5000/centos6-base:latest, centos6-base:latest
│ └─2803e99f8692 Virtual Size: 798.4 MB
│ └─36fd8fbc3a8d Virtual Size: 800.8 MB
│ └─5d5af25558d1 Virtual Size: 1.191 GB
│ └─b35f28929f07 Virtual Size: 1.193 GB
│ └─69018b3c508f Virtual Size: 1.193 GB Tags: centos6-base-updated:latest

Notice that the new image is now available for deployment.

Test the new image

docker run -i -t -rm centos6-base-updated

Mar 04 14:17:14 Updated: upstart-0.6.5-13.el6_5.2.x86_64
Mar 04 14:17:14 Updated: psmisc-22.6-19.el6_5.x86_64
Mar 04 14:17:15 Updated: initscripts-9.03.40-2.el6_5.1.x86_64
Mar 04 14:17:16 Updated: dracut-004-336.el6_5.2.noarch
Mar 04 14:17:16 Updated: dracut-kernel-004-336.el6_5.2.noarch
Mar 04 14:17:20 Installed: kernel-2.6.32-431.5.1.el6.x86_64
Mar 04 14:17:22 Updated: 2:postfix-2.6.6-6.el6_5.x86_64
Mar 04 14:17:22 Updated: yum-rhn-plugin-0.9.1-49.el6.noarch
Mar 04 14:17:23 Updated: tzdata-2013i-2.el6.noarch
Mar 04 14:17:24 Updated: 1:dmidecode-2.11-2.el6_1.x86_64

Manually: Commit an Image

Once a container has changes made locally, they can be committed to the local index. This allows you to check point and continue. It also allows you to create new images based off of this modified container.

docker run -i -t fatherlinux/centos6-base bash

Modify the image

Make some changes inside the container. In this example, change the hostname and exit

vi /etc/sysconfig/network

NETWORKING=yes
HOSTNAME=test.crunchtools.com

exit

Commit the container

First, get a list of containers. Notice that every container has a CONTAINTER ID and a STATUS. A status of Up means the container is currently running, while a status of Exit indicates that the container has been stopped. Think of the CONTAINER ID as a branch from the base image that contains all of the changes that were made to the container while it was running. By default this data is saved even after the container is shut down.

docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6cd2c1235f52 centos6-base:latest bash 4 seconds ago Up 2 seconds cranky_babbage
53c30eb1551f centos6-base:latest bash 11 minutes ago Exit 0 condescending_archimedes
...

Now, commit the container back as a branch of it’s base image
docker commit 53c30eb1551f
Notice that the image is now available in the tree output. Also, notice that it is a branch of the root centos6-base:latest image, not the centos6-base-updated:latest image, which we previously built with a Dockerfile.

docker images --tree

├─e39724bc32b2 Virtual Size: 1.7 GB Tags: centos6-base:latest, fatherlinux/centos6-base:latest
│ ├─c9bfb69481a8 Virtual Size: 1.7 GB
│ └─dc8663ef4d49 Virtual Size: 1.7 GB
│ └─e650e0b3dfb5 Virtual Size: 2.486 GB
│ └─1db8597b3ed4 Virtual Size: 2.486 GB Tags: centos6-base-updated:latest

Tag the new image with something meaningful
docker tag c9bfb69481a8 centos6-base-hostname

Push a Container

Once a container is committed locally, it can be pushed back to the registry server to be shared. The changes will be pushed as a layered image. Notice how quickly it is able to push only the differences between your modified image and the base image. This is a big part of the value.

docker tag centos6-base-hostname fatherlinux/centos6-base-hostname
docker push fatherlinux/centos6-base-hostname

The push refers to a repository [fatherlinux/centos6-base-hostname] (len: 1)
Sending image list
Pushing repository fatherlinux/centos6-base-hostname (1 tags)
e39724bc32b2: Image already pushed, skipping
c9bfb69481a8: Image successfully pushed
Pushing tag for rev [c9bfb69481a8] on {https://registry-1.docker.io/v1/repositories/fatherlinux/centos6-base-hostname/tags/latest}

Advanced Operations

Pull All Standard Images

These CentOS images are in the public Docker repository.
docker pull fatherlinux/centos4-base
docker pull fatherlinux/centos5-base
docker pull fatherlinux/centos6-base

Create Base Image

This method was developed with guidance from this script. This example is based on CentOS 6.

Create a tar file of the system
tar --numeric-owner --exclude=/proc --exclude=/sys -cvf centos6-base.tar /
Copy the tar file to where the consuming system and Import the image
cat centos6-base.tar | docker import - centos6-base
Test
docker run -i -t centos6-base cat /etc/redhat-release

Set Up a Registry Server

Notice that the entire application is packaged up and ran from inside of a docker container. This has the interesting consequence that we are not even concerned with what operating system is hosting this registry application. Also, notice that port 5000 in the docker container is mapped to port 5000 on the hosting virtual machine, which makes the application running in the container transparently appear to be running on the virtual machine.

docker run -p 5000:5000 registry

Inspect the Registry Application

Notice the tree structure of the registry application. A branch has been saved for each minor release. Think about the consequenses of releasing software like this; when an application is packaged with docker, the end user can be given access to every version of the application simultaneously.

docker images --tree
└─511136ea3c5a Virtual Size: 0 B
├─8abc22fbb042 Virtual Size: 0 B
│ ├─0d20aec6529d Virtual Size: 387 MB Tags: fedora:rawhide
│ └─58394af37342 Virtual Size: 385.5 MB Tags: fedora:20, fedora:heisenbug, fedora:latest
├─46e4dee27895 Virtual Size: 0 B
│ └─0e5997dad26c Virtual Size: 137.3 MB
│ └─23cc61a3cbda Virtual Size: 170.2 MB
│ ├─1eb901173c39 Virtual Size: 401 MB
│ │ └─8183e1512350 Virtual Size: 401 MB
│ │ └─b09396059e11 Virtual Size: 409.9 MB
│ │ └─956865a3475d Virtual Size: 410.8 MB
│ │ └─59f37135f7e1 Virtual Size: 410.8 MB
│ │ └─b6a21b9fd5f5 Virtual Size: 432.8 MB
│ │ └─14b1999c6946 Virtual Size: 432.8 MB
│ │ └─d1f2b453a6c7 Virtual Size: 432.8 MB
│ │ └─376ca9433bfe Virtual Size: 432.8 MB Tags: registry:0.6.5, registry:latest
│ ├─c300214610ee Virtual Size: 402.6 MB
│ │ └─5a81acd5e981 Virtual Size: 403.5 MB
│ │ └─0c6219cab880 Virtual Size: 460.1 MB
│ │ └─2c550dc8534e Virtual Size: 460.1 MB
│ │ └─09da4bd2aa01 Virtual Size: 460.1 MB
│ │ └─9f98cb899f46 Virtual Size: 460.1 MB Tags: registry:0.6.1
│ └─528891a0cca3 Virtual Size: 403.1 MB
│ ├─40352477ac18 Virtual Size: 404 MB
│ │ └─fe89f1cc0f7f Virtual Size: 404 MB
│ │ └─54987f913190 Virtual Size: 490 MB
│ │ └─8151eebe76eb Virtual Size: 490 MB
│ │ └─15dddc3cda4b Virtual Size: 490 MB
│ │ └─1f7bbd131cd8 Virtual Size: 490 MB Tags: registry:0.6.3
│ └─b852c54de572 Virtual Size: 404 MB
│ └─fd1184b81ee4 Virtual Size: 404 MB
│ └─6b8a67c33f30 Virtual Size: 490 MB
│ └─27e39c0ca04b Virtual Size: 490 MB
│ └─68f965c066b1 Virtual Size: 490 MB
│ └─b04ace768d59 Virtual Size: 490 MB Tags: registry:0.6.4
└─b74728ce6435 Virtual Size: 0 B
└─72e10143e54a Virtual Size: 126.2 MB
└─70cfed2e6e39 Virtual Size: 204.7 MB
└─1dd74b50acd6 Virtual Size: 445.4 MB
├─ef94089b2d40 Virtual Size: 446.2 MB
│ └─9eb3e5a74dc8 Virtual Size: 466.6 MB
│ └─e4bf708b1c29 Virtual Size: 466.6 MB
│ └─e1c6f8c2815c Virtual Size: 466.6 MB
│ └─0b520d776e7d Virtual Size: 466.6 MB Tags: registry:0.6.2
├─7b102560c1a3 Virtual Size: 446.2 MB
│ └─f26c1e958d9d Virtual Size: 466.5 MB
│ └─0758cf860baf Virtual Size: 466.5 MB
│ └─1733ea6ad8fd Virtual Size: 466.5 MB
│ └─c565a3cc048c Virtual Size: 466.5 MB
│ └─873f518b98ef Virtual Size: 466.5 MB Tags: registry:0.6.0
└─6517c4695630 Virtual Size: 446.2 MB
└─d6c11fc1a075 Virtual Size: 466.3 MB
└─2388bca8b996 Virtual Size: 466.3 MB
└─3f8c215063ee Virtual Size: 466.3 MB
└─d490731c796e Virtual Size: 466.3 MB
└─e8e5377f8307 Virtual Size: 466.3 MB Tags: registry:0.5.9

Search Private Registry

List all images in the repository

http://registry.example.com:5000/v1/search?
Search for all repositories with the word “rhel” in their name

http://registry.example.com:5000/v1/search?q=rhel

Remove Old Docker Containers

By default, Docker keeps changes for every container which is instantiated. When testing, this can be undesirable. Be careful because this will remove all branches/data. Any containers which have not been committed will have all data deleted:
docker rm `docker ps --no-trunc -a -q`

Links

7 comments on “A Practical Introduction to Docker Containers

Leave a Reply

Your email address will not be published. Required fields are marked *