Introduction to Docker containers

>> Today is Wednesday and David didn’t publish the OpenShift tutorial’s 4th part, but now he want to talk about Docker. Is it the Armageddon?

>> I have no response for last question but I can explain why there is no tutorial this week. Due a particular labor issue I’ve decided to pause the OpenShift v3 Mega Tutorial.

>> When it will be back?

>>I neither have the response but maybe two or three weeks from now

>>And what I will do meanwhile?

>> Well  during this pause I’m going to talk about Docker and Ansible, because there were many requests and also will help us to go deeper in OpenShift v3

 

Start?

Docker

Docker containers wrap a piece of software in a complete filesystem that contains everything needed to run: code, runtime, system tools, system libraries – anything that can be installed on a server. This guarantees that the software will always run the same, regardless of its environment.

Extracted from Docker

 LXC

LXC (LinuX Containers) is a operative-system-level virtualization method that allows to run multiple isolated Linux systems. Is not a virtual machine but it has all that a contained system requires such CPU, network, blocks I/O, through two kernel components:

  • Namespaces: isolates process resources: process ID (PID), hostnames, users ID (UID), network access, interprocess comunication and filesystems.
  • CGroups: this kernel feature limits, policy and account resources for a set of processes.

Definitely, jails a set of resources, like a Sandbox where kids can play “safe”.

 

Docker + LXC

Configure LXC could be hard, thus Docker offers a simple mechanism to manage “containers”. Docker will be on charge of create necessary namespaces and cgroups to run our code. But the most impressive of Docker is their modular architecture based on two main components:

  • Image: Is a set of read-only binaries and libraries as a base to run our code.
  • Container: As our code requires process and file deployment in a read-write manner, the container creates a layer above an image where the code can run.

So like Lego pieces (I wase fan of TENTE, such a memories,sniff, sniff), we can connect one image over another until we have the necessary to deploy our container.

Let’s think we want to run PHP over Laravel framework. First we need a base system, suppose CentOS, after we need an Apache Web server, and also we need Laravel framework. At the end we have three images, one above other, with a whole system with the binaries and libraries required to connect our container with executable processes where our PHP code will run.

It seems easy but is it really easy? Really, it is easy. We only need to connect the images between them with the commands Docker has until we have the expected result.

But still is more easy, as Docker is an open system we can find lots of base images for our code created and shared by lots of users. Thus we have an image named docker.io/eboraas/laravel ready to run our PHP code.

 

Hands on work

At the moment of this post the latest Docker version is 1.12.1.

I will use  1.10.3, as it is available on CentOS 7.2.

 

First I’m going to install Docler via yum, but you can install it in a different distro following instruction in Docker web. It is also available in cloud services as AWS, as well as is available in MAC and Windows.

sudo yum install docker

 

I don’t like to work as root, so I’m going to grant privileges to user david to manage containers. So I create a group named docker. This group has some administrative permissions granted by docker service, so we need to be careful who belongs to this group because will be able to see and manage docker images and containers 😉

sudo groupadd docker
sudo usermod -aG docker david
<logout and login again>
sudo systemctl start docker
sudo systemctl enable docker

 

Now we can run our first container

>> But, don’t you need first to download an image?

We could do it, but one of the advantages of docker is that is capable to run automatically all the steps needed to deploy a container. As this example:

docker run hello-world
Unable to find image 'hello-world:latest' locally
Trying to pull repository docker.io/library/hello-world ... 
latest: Pulling from docker.io/library/hello-world
c04b14da8d14: Pull complete 
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for docker.io/hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
 executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
 to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
 https://hub.docker.com

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

As it is visible docker client has contacted with the service to ask if has an imaged named  hello-world. The service doesn’t have it locally, so it contacts with a registry (images store), docker hub by default, to download the image. After that it creates a container running a command defined on the image blueprint.

 

Commands

We’ve already started using docker, but is necessary stop to take a look some basic commands:

  • docker run: creates a container executing the command defined on the image’s blueprint. It is possible to create a container running a different command, but we’ll see this later.
  • docker images: shows the main images in our local storage. Images can be distinguished in main and  intermediate, the ones intended to create a base image, something we’ll see when we create a blueprint. In order to see all images we have the “-a” option
  • docker ps: shows a list of running containers. A container can have three states: running, on pause or finished. The “-a” shows all.

Now we can gather some information about what happened before:

docker images
REPOSITORY            TAG    IMAGE ID     CREATED      SIZE
docker.io/hello-world latest c54a2cc56cbb 12 weeks ago 1.848 kB


docker images -a
REPOSITORY            TAG    IMAGE ID     CREATED      SIZE
docker.io/hello-world latest c54a2cc56cbb 12 weeks ago 1.848 kB


docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES


docker ps -a
CONTAINER ID IMAGE        COMMAND CREATED        STATUS                     PORTS   NAMES
185a3df70ef2 hello-world "/hello" 16 minutes ago Exited (0) 16 minutes ago          mad_swirles

 

When run  docker images we can see next information:

  • IMAGE ID: An unique indetifier for the image. It can be used as a checksum to compare with the registry
  • REPOSITORY: Which repository belongs the image. docker.io is docker public registry
  • TAG: The image can have versioning control, as code reopsitories. If empty, it will use always an image tagged latest
  • CREATED: When the image has been created. It is not the date when the image has been downloaded
  • SIZE: The image size. It is not the real image as is showed adding the size of intermediate images

When run docker ps this is the information:

  • CONTAINER ID: Am unique identifier for each container. It is possible to create different containers from the same base image.
  • IMAGE: The base image where the container is running.
  • COMMAND: The command the container runs.
  • CREATED: The time since the container has been created.
  • STATUS: The container status.
  • PORTS: The ports exposed by the container. Later we’ll see how to bind them.
  • NAMES: A name to identify the container instead their ID.

 

Working with images

The quest

We want to start a project and we want to do it on docker. First I know I’m going to write Java code for a simple web application. NOTE: I won’t deploy code, this topic is only for image management.

I need a Java Web Server to run my code and the one I choose is Tomcat. This will be my key word. Now I’m going to run docker search, a tool to search my keyword in the configured registries. Docker searches the keyword in the image name and description.

docker search tomcat
INDEX     NAME                           DESCRIPTION                                     STARS OFFICIAL AUTOMATED
docker.io docker.io/tomcat               Apache Tomcat is an open source implementa...   943   [OK] 
docker.io docker.io/dordoka/tomcat       Ubuntu 14.04, Oracle JDK 8 and Tomcat 8 ba...   25             [OK]
docker.io docker.io/consol/tomcat-7.0    Tomcat 7.0.57, 8080, "admin/admin"              16             [OK]
docker.io docker.io/consol/tomcat-8.0    Tomcat 8.0.15, 8080, "admin/admin"              15             [OK]
....
OMMITED
....

 

Searches are limited to 25 results maximum:

  • INDEX: The registry where the image is stored. –no-index=false hide it.
  • NAME: The image name.
  • DESCRIPTION: A truncated image description. –no-trunc=true to show full description.
  • STARS: The number of stars awarded by users.  -s x | –stars=x  to limit results with at least X stars
  • OFFICIAL: Whether the image is official.
  • AUTOMATED: The image is created and update based on an official image. –automated=true to show only automated builds.

The fetching

Once selected which image we are going to use, is time to download it. docker pull allows us to fetch the image from a registry. The command requires <image name> or <registry>/<image_name>

docker pull docker.io/tomcat
Using default tag: latest
Trying to pull repository docker.io/library/tomcat ... 
latest: Pulling from docker.io/library/tomcat

6a5a5368e0c2: Pull complete 
7b9457ec39de: Pull complete 
d5cc639e6fca: Pull complete 
dae3b0638638: Pull complete 
ab678d1c6f00: Pull complete 
d5bf826c3153: Pull complete 
0081bad1df81: Pull complete 
8fafa3f26de4: Pull complete 
ae984359ed7e: Pull complete 
9175a2e1674f: Pull complete 
2e8f15e74426: Pull complete 
Digest: sha256:87025ccebdd5534826b4d315ceda1a97ba75e35431075eeaacaf616998a46ed0
Status: Downloaded newer image for docker.io/tomcat:latest

Docker has downloaded the tomcat from docker.io registry, as we didn’t set anything, the tag latest, corresponding to Tomcat 8. If we wanted to use Tomcat 6 we should have run docker pull docker.io/tomcat:6

 

The inspection

Well, we have the image. What is next?. We should inspect the image, and thus we have docker inspect. With docker images let’s find image id to inspect

docker inspect 30d95ba23356
[
 {
   "Id": "sha256:30d95ba23356c135ca73b2e6a83fb7c5f8a35a7a6b5a2d57ced9adedc47badb4",
   "RepoTags": [
     "docker.io/tomcat:latest"
   ],
   "RepoDigests": [],
   "Parent": "",
   "Comment": "",
   "Created": "2016-09-23T23:53:21.418015533Z",
   "Container": "1b1d84493d7bd13a2afbba20f6b0421f003ad06a52cead3562d1966ee7f07a45",
   "ContainerConfig": {
     "Hostname": "383850eeb47b",
     "Domainname": "",
     "User": "",
     "AttachStdin": false,
     "AttachStdout": false,
     "AttachStderr": false,
     "ExposedPorts": {
        "8080/tcp": {}
     },
     "Tty": false,
     "OpenStdin": false,
     "StdinOnce": false,
     "Env": [
        "PATH=/usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "LANG=C.UTF-8",
        "JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64/jre",
        "JAVA_VERSION=7u111",
        "JAVA_DEBIAN_VERSION=7u111-2.6.7-1~deb8u1",
        "CATALINA_HOME=/usr/local/tomcat",
        "TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
        "LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
        "OPENSSL_VERSION=1.0.2i-1",
        "TOMCAT_MAJOR=8",
        "TOMCAT_VERSION=8.0.37",
        "TOMCAT_TGZ_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=tomcat/tomcat-8/v8.0.37/bin/apache-tomcat-8.0.37.tar.gz",
        "TOMCAT_ASC_URL=https://www.apache.org/dist/tomcat/tomcat-8/v8.0.37/bin/apache-tomcat-8.0.37.tar.gz.asc"
    ],
    "Cmd": [
       "/bin/sh",
       "-c",
       "#(nop) ",
       "CMD [\"catalina.sh\" \"run\"]"
    ],
    "ArgsEscaped": true,
    "Image": "sha256:bb923de3fcd8edef19b14b1c4a9415d3ab97be519a1b727a631ac0ba85bc2bd5",
    "Volumes": null,
    "WorkingDir": "/usr/local/tomcat",
    "Entrypoint": null,
    "OnBuild": [],
    "Labels": {}
   },
   "DockerVersion": "1.12.1",
   "Author": "",
   "Config": {
      "Hostname": "383850eeb47b",
      "Domainname": "",
      "User": "",
      "AttachStdin": false,
      "AttachStdout": false,
      "AttachStderr": false,
      "ExposedPorts": {
      "8080/tcp": {}
   },
   "Tty": false,
   "OpenStdin": false,
   "StdinOnce": false,
   "Env": [
      "PATH=/usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "LANG=C.UTF-8",
      "JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64/jre",
      "JAVA_VERSION=7u111",
      "JAVA_DEBIAN_VERSION=7u111-2.6.7-1~deb8u1",
      "CATALINA_HOME=/usr/local/tomcat",
      "TOMCAT_NATIVE_LIBDIR=/usr/local/tomcat/native-jni-lib",
      "LD_LIBRARY_PATH=/usr/local/tomcat/native-jni-lib",
      "OPENSSL_VERSION=1.0.2i-1",
      "TOMCAT_MAJOR=8",
      "TOMCAT_VERSION=8.0.37",
      "TOMCAT_TGZ_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=tomcat/tomcat-8/v8.0.37/bin/apache-tomcat-8.0.37.tar.gz",
      "TOMCAT_ASC_URL=https://www.apache.org/dist/tomcat/tomcat-8/v8.0.37/bin/apache-tomcat-8.0.37.tar.gz.asc"
   ],
   "Cmd": [
      "catalina.sh",
      "run"
   ],
   "ArgsEscaped": true,
   "Image": "sha256:bb923de3fcd8edef19b14b1c4a9415d3ab97be519a1b727a631ac0ba85bc2bd5",
   "Volumes": null,
   "WorkingDir": "/usr/local/tomcat",
   "Entrypoint": null,
   "OnBuild": [],
   "Labels": {}
 },
 "Architecture": "amd64",
 "Os": "linux",
 "Size": 355294925,
 "VirtualSize": 355294925,
 "GraphDriver": {
    "Name": "devicemapper",
    "Data": {
       "DeviceId": "30",
       "DeviceName": "docker-253:0-16777380-6346c1bd8ddda0fa9be8fc867860e1e011ad47514d821598cf2ee6d7a9598666",
       "DeviceSize": "10737418240"
    }
  }
 }
]

We have a json object with lots of information, but the most relevant is:

  • “Config”: {… “ExposedPorts”: { “8080/tcp”: {} } it says that port 8080 will be exposed. It will be exposed in docker network only, which is configured by docker service and it is accessible only on the host. Later we’ll see how to expose ports to external networks.
  • “Config”: { “Env”: [ … ] there is a lot of environment variables used by the container.
  • “Cmd”: [ “catalina.sh”, “run” ] The key. The command that will be run by the container. The first element is the command, and next are the arguments token by the command

 

The running

The command docker run <image> creates a container based on the image and it will run the command defined by CMD blueprint argument.

docker run tomcat
.....
<After a while>
.....
28-Sep-2016 14:17:20.012 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-apr-8080"]
28-Sep-2016 14:17:20.019 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-apr-8009"]
28-Sep-2016 14:17:20.020 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 139292 ms

Docker captured the terminal. If we stop the process with CTRL+C or close the terminal, the container finalizes. The way to detach the container is:

docker run -d tomcat
6a0a3e5410192519bccf8a4b2ec59b3f480f5e9dc1be393d36aab43ccbf16c3a

The given id is the container id, in order to find it

docker ps
CONTAINER ID IMAGE  COMMAND           CREATED       STATUS       PORTS    NAMES
6a0a3e541019 tomcat "catalina.sh run" 2 minutes ago Up 2 minutes 8080/tcp backstabbing_meninsky

If it is running we see the creation time and the status, and it was created 2 minutes ago and is up for 2 munites. It also exposes the port 8080, but we don’t know what is the container IP, so time to inspect the container.

I’m going to use a tool that is like sed but for JSON, jq. Once installed I’ll extract the container IP from the inspection.

mkdir ~/bin
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 -O ~/bin/jq &amp;amp;&amp;amp; chmod +x ~/bin/jq
docker inspect backstabbing_meninsky|jq '.[0].NetworkSettings.IPAddress'
"172.17.0.2"

As we have the IP, let’s take a look to Tomcat:

curl -s http://172.17.0.2:8080/|head -n 20</pre>
<pre><!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="UTF-8" />
 <title>Apache Tomcat/8.0.37</title>
 <link href="favicon.ico" rel="icon" type="image/x-icon" />
 <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
 <link href="tomcat.css" rel="stylesheet" type="text/css" />
 </head>

 <body>
 <div id="wrapper">
 <div id="navigation" class="curved container">
 <span id="nav-home"><a href="http://tomcat.apache.org/">Home</a></span>
 <span id="nav-hosts"><a href="/docs/">Documentation</a></span>
 <span id="nav-config"><a href="/docs/config/">Configuration</a></span>
 <span id="nav-examples"><a href="/examples/">Examples</a></span></pre>
<pre>

Now I’m going to stop the container and after I’m going to do an advanced deployment:

  1. With a specific name
  2. Forwarding ports to our host. The -p option requires one of the exposed ports to forward it to a random host port, or we can define the binding as host_ip:host_port:container_port
  3. Of course, it will run in background
docker stop backstabbing_meninsky 
docker run -d --name=myTomcat_8 -p 8080:8080 tomcat
659838deee0f03dd991a2bae7d127698a678cfe8cb5541b33470d705d9d5223
docker ps
8659838deee0 tomcat "catalina.sh run" 2 minutes ago Up 2 minutes 0.0.0.0:8080->8080/tcp myTomcat_8

curl -s http://localhost:8080 |head -n 20
<OMMITED>

And this is the end… ‘Cos it was a small introduction. And yes, I know is pending how to deploy our code in a container, but it will be in other post, and for God’s sake, it will be very interesting. See you.

Leave a Reply

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