Wednesday, March 4, 2015

First Steps with Docker

Docker is platform-agnostic and relies on Linux kernel functionality which is present on most platforms, including s390x (IBM z Systems). Its commands are identical on every platform, so there are lots of resources on the Internet which work for z Systems z.
However, Docker only executes native z code and does not emulate anything: you need to work with containers which contain s390(x) binaries, not e.g. x86 binaries.

This post shows how containers are created, used, and modified. For all of these commands, an online help is available: docker --help shows an overview of all commands, and docker COMMAND --help shows more specifics on the specified command.

Installation

Get the binaries and deploy them as described on developerWorks. In a nutshell, you need to download the latest s390x Docker binaries from ftp://ftp.unicamp.br/pub/linuxpatch/s390x/suse/sles12/docker/ resp. ftp://ftp.unicamp.br/pub/linuxpatch/s390x/redhat/rhel7/docker/ .
Extract the archive (tar xzvf on the file) and copy the Docker binary somewhere into your search path; recommendation is /usr/local/bin, and make sure the file is executable (chmod 755 on the file). The developerWorks page will always have SHA1 checksums to verify your download (current checksums: 20de8b71dba231dafd07ef32c9b186a05e57b7de for docker-rhel7-20150302.tar.gz, as well as aa1859739025f0ba26fbaf78c2ebc159cdd4a026 for docker-sles12-20150304.tar.gz). On SLES 12, make sure apparmor is installed (zypper install patterns-sles-apparmor). developerWorks will also be updated when new files are available.

Starting the Daemon

A daemon on each host will run in the background, serving all Docker requests. We start this daemon:
[root@r1745042 ~]# docker -d
INFO[0000] +job serveapi(unix:///var/run/docker.sock)
INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
INFO[0003] +job init_networkdriver()
INFO[0003] -job init_networkdriver() = OK (0)
INFO[0004] Loading containers: start.

INFO[0004] Loading containers: done.
INFO[0004] docker daemon: 1.4.1 5bc2ff8-dirty; execdriver: native-0.2; graphdriver: devicemapper
INFO[0004] +job acceptconnections()
INFO[0004] -job acceptconnections() = OK (0)

This daemon will print out messages for each interaction.
Update 8/31: Note the output may look slightly different for newer versions of docker. Also, please note the docker daemon will not return to the shell unless you put it in the background explicitly (through an invokation like "docker -d &" or Ctrl-Z followed by "bg"). You can always continue to use docker from another shell.

Creating an Image

Since you cannot run existing x86 images, we need to create one ourselves as a first step. All we need is a tarball of a basic installation. Small images from a fresh install are an easy way to go, since they can be extended as needed, but still are larger than they need be. Should you have any tarballs from your deployment sources, that is convenient, too. In a future post, we'll discuss good ways to come to small and efficient base images. Anyway, we import this tarball as an image into Docker:
[root@r1745042 ~]# cat rootfs.tgz | docker import - rhel7
7228aca22f663d55fc05331a313831a5371f0165e5bd3f5d82b0c8f19336a30b

In this example, the tarball containing a RHEL root file system is imported and given the name "rhel7". Docker will return a long ID uniquely identifying the image, but you can also refer to it through the imagename (rhel7), or an abbreviation of the first unique characters of the long UUID string. We list all images:
[root@r1745042 ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
rhel7               latest              7228aca22f66        2 minutes ago       539.3 MB
sles12              latest              91e78dd3f4f5        7 minutes ago       1.537 GB

Running a Container

The following text goes with a RHEL-based image. SUSE-based images are as good (just note there are differences, e.g. you need to run zypper instead of yum).
As mentioned in the previous posting, usually just a single task or process is started. Let's start a bash:

[root@r1745042 ~]# docker run -t -i rhel7 bash 
[root@f678f0ae730f /]#

And look aorund:
[root@f678f0ae730f /]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  5.0  0.2   3540  1896 ?        Ss   22:06   0:00 bash
root        21  0.0  0.1   3292  1220 ?        R+   22:06   0:00 ps aux

Remember the scoping of processes ("PID namespace")? This makes bash have a PID of 1, and the container only be able to see two processes.

From another shell in the host, we can look at currently running containers (lines breaks may impact readability, sorry):
[root@r1745042 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
f678f0ae730f        rhel7:latest        "bash"              26 seconds ago      Up 26 seconds                           high_archimedes
This will list our container along with the ID. The ID per default also serves as host name, as the prompt indicated in our bash above. Since the ID is not very easy to type, Docker invents a readable name for every container: in our case "high_archimedes".

Working with Files in a Container


What happens if something changes in the container? Let's add a file:
[root@f678f0ae730f /]# touch newfile
Docker will never modify the original image we started off. Instead, only differences of running containers to its underlying images are stored. We can list the changes:
[root@r1745042 ~]# docker diff f678f0ae730f
C /tmp
A /newfile

So newfile has been added, and something in /tmp changed.
From the host, we can get to a container's files. Let's get our newfile using:
[root@r1745042 ~]# docker cp high_archimedes:newfile .

Stopping and Starting Containers

When we exit the container (e.g. typing exit), it is gone from the list of currently running containers:

[root@r1745042 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

However, Docker still knows about it, -a lists all containers including the stopped ones:
[root@r1745042 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                      PORTS               NAMES
f678f0ae730f        rhel7:latest        "bash"              About a minute ago   Exited (0) 17 seconds ago                       high_archimedes

The container is still there, but not running anymore. We can restart the container again:
[root@r1745042 ~]# docker restart high_archimedes
high_archimedes
Now docker ps shows high_archimedes again:
[root@r1745042 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
f678f0ae730f        rhel7:latest        "bash"              2 minutes ago       Up 2 seconds                            high_archimedes
Yet we cannot interact with the bash, so we need to re-attach to bash:
[root@r1745042 ~]# docker attach high_archimedes
<press enter so that bash prints a new prompt>
[root@f678f0ae730f /]#

Another way to see what is going on in a container is docker logs, which will print out stdout and stderr of the container. This is more suitable for management of a pile of containers.

Creating a New Image


So far, we have worked with a container, but we have not persisted any changes. Let's build a new image, in our case we'll add repository data for comfortable package updates and installations. On my system, the host carries this data already, all we need is copy this data into the container.

To do that, we start a new container, and use -v to mount our /etc directory into the container's file system:
[root@r1745042 ~]# docker run -t -i -v /etc:/mnt rhel7 bash
Copying yum repository files:
[root@3ffd90b20868 /]# cp /mnt/yum.repos.d/* /etc/yum.repos.d/
[root@3ffd90b20868 /]# exit
Now the container has everything it needs, we commit all the changes and create a new image:
[root@r1745042 ~]# docker commit 3ffd90b20868 rhel7-yum
63d2b40384d115a21b98af9b279cd3c46a974f60570c0a216bbb0c5d1bda0f20
[root@r1745042 ~]# docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
rhel7-yum           latest              63d2b40384d1        10 seconds ago      539.3 MB
rhel7               latest              7228aca22f66        8 minutes ago       539.3 MB
sles12              latest              91e78dd3f4f5        13 minutes ago      1.537 GB
Docker keeps track of every container's heritage:
[root@r1745042 ~]# docker history rhel7-yum
IMAGE               CREATED             CREATED BY          SIZE
63d2b40384d1        37 seconds ago      bash                3.372 kB
7228aca22f66        9 minutes ago                           539.3 MB
As seen in the docker diff example above, this method of modifying containers may do other changes to the image we may not be aware of. In the next posting, we will see a more controlled way to create and personalize images.

2 comments:

  1. After I start docker -d on Redhat ( 3.10.0-229.11.1.el7.s390x ) it stops at "Daemon has completed initialization" and it does not return control back to the shell. Do you have any documentation on how to setup on the System Z?

    ReplyDelete
    Replies
    1. This is perfectly fine. The daemon does not return, unless you start it in background with ampersand ("&"), or do a Ctrl-Z followed by bg to put it in background. I will update the description above to make this more clear, thanks for raising this!
      Or, you can start using docker from another shell.

      Delete