Hands-on Lab: Build and Deploy Portable Applications Using Docker 1.12

Version 16

    Learn how to customize a Docker container image and use it to instantiate application instances across different Linux servers. This article describes how to create a Dockerfile, how to allocate runtime resources to containers, and how to establish a communication channel between two containers (for example, between web server and database containers).

     

    Introduction

     

    This hands-on lab is designed to help you get a taste for how you can use Docker to simplify application provisioning on Linux. It demonstrates how to create a Docker container on Oracle Linux, modify it, and use it to instantiate multiple application instances. It also describes how to allocate system CPU and memory resources to a Docker container, how to set up a database container, and how other containers can connect to that database container.

     

    Getting Started

     

    OPENWORLD ATTENDEES: IGNORE THIS BIT!

     

    You can download a pre-built VM template for Oracle VM VirtualBox for this lab: Oracle Linux VM Images for Hands-On Lab

     

    Once you have downloaded and imported the VM template into VirtualBox, log to the VM as holuser with the password oracle.

     

    To use Docker, the first step is to download and install the Docker Engine RPM packages. Oracle publishes Docker Engine RPMs for Oracle Linux 6 and Oracle Linux 7 on the Oracle Linux Yum Server and on the Unbreakable Linux Network (ULN).

     

    Up-to-date Instructions for installing the Docker Engine are available in the Oracle Linux 6 Docker User's Guide or the Oracle Linux 7 Docker User's Guide.

     

    OPENWORLD ATTENDEES START HERE!

     

    The initial steps are pretty simple: first enable the ol7_addons channel in /etc/yum.repos.d/public-yum-ol*.repo by using the yum-config-manager tool and then run yum install docker.

     

    In addition, the hands-on lab image has been pre-configured with a btrfs-based filesystem for /var/lib/docker. The instructions above detail how to create and format a btrfs filesystem. The following commands assume you are logged in as the holuser user and have opened a Terminal window:

     

    [holuser@docker ~]$ sudo yum-config-manager --enable ol7_addons
    [holuser@docker ~]$ sudo yum install docker-engine
    [holuser@docker ~]$ sudo usermod -a -G docker holuser
    

     

    You'll notice that the last command adds the holuser to the docker group. This allows the holuser to run the Docker tool and manipulate containers. In order for the group membership to take effect, please log out and then log back into the graphical user desktop.

     

    Once Docker has been installed, we need to enable and start it using the systemd tool:

     

    [holuser@docker ~]$ sudo systemctl status docker
    docker.service - Docker Application Container Engine
       Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
      Drop-In: /etc/systemd/system/docker.service.d
               └─docker-sysconfig.conf
       Active: inactive (dead)
         Docs: https://docs.docker.com
    

     

    Here we can see that the Docker Engine has not been started automatically after installation. First, we need to enable the Docker Engine so that it is started automatically whenever the system boots:

     

    [holuser@docker ~]$ sudo systemctl enable docker
    Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
    

     

    Next, we need to start the Docker Engine and verify that it has started correctly:

     

    [holuser@docker ~]$ sudo systemctl start docker
    [holuser@docker ~]$ sudo systemctl status docker
    docker.service - Docker Application Container Engine
       Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
      Drop-In: /etc/systemd/system/docker.service.d
               └─docker-sysconfig.conf
       Active: active (running) since Wed 2016-09-14 14:05:57 PDT; 1s ago
         Docs: https://docs.docker.com
     Main PID: 5882 (dockerd)
       Memory: 12.0M
       CGroup: /system.slice/docker.service
               ├─5882 dockerd --selinux-enabled
               └─5896 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --st...
    
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.282797074-07:00" level=info msg="Graph migration to content-addressability took 0.00 seconds"
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.283169910-07:00" level=warning msg="mountpoint for pids not found"
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.283362149-07:00" level=info msg="Loading containers: start."
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.294207528-07:00" level=info msg="Firewalld running: false"
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.361256467-07:00" level=info msg="Default bridge (docker0) is assigned with an IP add...P address"
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.421012757-07:00" level=info msg="Loading containers: done."
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.421121256-07:00" level=info msg="Daemon has completed initialization"
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.421121265-07:00" level=info msg="Docker daemon" commit=b9264d4 graphdriver=btrfs version=1.12.0
    Sep 14 14:05:57 docker.oracleworld.com systemd[1]: Started Docker Application Container Engine.
    Sep 14 14:05:57 docker.oracleworld.com docker[5882]: time="2016-09-14T14:05:57.445533944-07:00" level=info msg="API listen on /var/run/docker.sock"
    Hint: Some lines were ellipsized, use -l to show in full.
    

     

    Now that the Docker Engine has started, we can check the version of both the engine and the Docker client:

     

    [holuser@docker ~]$ docker version
    Client:
     Version:      1.12.0
     API version:  1.24
     Go version:   go1.6.3
     Git commit:   b9264d4
     Built:        
     OS/Arch:      linux/amd64
    
    Server:
     Version:      1.12.0
     API version:  1.24
     Go version:   go1.6.3
     Git commit:   b9264d4
     Built:        
     OS/Arch:      linux/amd64
    

     

    We can also view information about the currently running engine, including which storage and execution drivers are active:

     

    [holuser@docker ~]$ docker info
    Containers: 0
     Running: 0
     Paused: 0
     Stopped: 0
    Images: 0
    Server Version: 1.12.0
    Storage Driver: btrfs
     Build Version: Btrfs v3.19.1
     Library Version: 101
    Logging Driver: json-file
    Cgroup Driver: cgroupfs
    Plugins:
     Volume: local
     Network: null overlay host bridge
    Swarm: inactive
    Runtimes: runc
    Default Runtime: runc
    Security Options: seccomp
    Kernel Version: 4.1.12-37.5.1.el7uek.x86_64
    Operating System: Oracle Linux Server 7.2
    OSType: linux
    Architecture: x86_64
    CPUs: 4
    Total Memory: 7.545 GiB
    Name: docker.oracleworld.com
    ID: U5XC:JCMK:KTY3:5NJE:SNU7:GQSV:CX2W:KSCU:P57D:W3FC:TRLQ:BI66
    Docker Root Dir: /var/lib/docker
    Debug Mode (client): false
    Debug Mode (server): false
    Registry: https://index.docker.io/v1/
    Insecure Registries:
     127.0.0.0/8
    

     

    The Docker Hub Registry is a public cloud store of images hosted by Docker, Inc. that can be used to build running containers. Repositories provide a mechanism for Docker image distribution and sharing. Docker, Inc. also hosts private repositories for a monthly fee. Alternatively, you can create a private Docker Registry behind your own corporate firewall, implementing protection mechanisms, such as SSL encryption and HTTP authentication, to restrict and protect access.

     

    The following commands pull the Oracle Linux 6 and Oracle Linux 7 images from the public Docker Hub Registry, as well as the official MySQL image from Oracle, downloading them to the local machine:

     

    [holuser@docker ~]$ docker pull oraclelinux
    Using default tag: latest
    latest: Pulling from library/oraclelinux
    
    e48fc69ad7bb: Pull complete
    Digest: sha256:4dd37c90e3908fe2327754e45f7cd907691654d0d125ba1d842f2ae7d4de962d
    Status: Downloaded newer image for oraclelinux:latest
    

     

    [holuser@docker ~]$ docker pull oraclelinux:6
    6: Pulling from library/oraclelinux
    
    9df7b97f79e6: Pull complete
    Digest: sha256:5bbee1185549cf30a77f4e05907c386feb7518952df36aa592a24fef523e4930
    Status: Downloaded newer image for oraclelinux:6
    

     

    [holuser@docker ~]$ docker pull mysql/mysql-server
    Using default tag: latest
    latest: Pulling from mysql/mysql-server
    
    a3ed95caeb02: Pull complete
    89937cfc6593: Pull complete
    17cdbad66360: Pull complete
    a9c6b92f18d2: Pull complete
    35be0cb12451: Pull complete
    Digest: sha256:a994152904a25d6341e11b2c8f8d28ffe1e600e3898a7466c5186dde5d9fdb42
    Status: Downloaded newer image for mysql/mysql-server:latest
    

     

    Images can include a specific tag, which indicates a particular build of that image. In the commands above, you'll notice we pulled oraclelinux first, which always points to the latest version of Oracle Linux on the Docker Hub (currently 7.2). The second command specified the specific tag "6", which indicates that we want the latest Oracle Linux 6 build (which is Oracle Linux 6.8). To view all the available tags, visit the Oracle Linux official image page on the Docker Hub.

     

    During the download process, you'll notice that Docker tells us when a particular filesystem layer already exists. This is because Docker creates the final filesystem for each container by layering changes. This allows Docker to optimize both downloads and cloning operations to only operate on the layers that have changes.

     

    Next, we can view all the available images on our local machine:

     

    [holuser@docker ~]$ docker images
    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    oraclelinux          6                   3956a77fcc51        2 weeks ago         223.1 MB
    oraclelinux          latest              5f5dae795065        2 weeks ago         278.1 MB
    mysql/mysql-server   latest              18a962a188ee        5 weeks ago         366.9 MB
    

     

    Customizing a Container for Application Provisioning

     

    Suppose that I want to provision multiple, identical web servers across multiple Linux servers in my data center. Docker makes it easy to create a preconfigured, cookie-cutter environment in a container image. We can then use this prebuilt image and deploy it across one or many Linux hosts.

     

    To build a customized container image, we must first build a guest container, install the web server, and configure it to deliver web server content. We can use the docker run command to run an Oracle Linux 7 base container and execute a bash shell in the guest container:

     

    [holuser@docker ~]$ docker run -t -i --name guest oraclelinux /bin/bash
    [root@722c42b3c151 /]#
    

     

    The Docker Engine assigns an image ID to every running container instance. Because we used the arguments -i and -t, the bash shell runs interactively, and the prompt reflects the first 12 characters (f85d55a6893f) of my running container's image ID. The --name argument specifies a name for the running container instance. If you choose not to enter a name, the Docker Engine generates a random string that incorporates the name of a notable scientist, inventor, or developer, such as evil_jones; sad_ritchie; sleepy_curie and prickly_mestorf.

     

    Open another shell and use the docker ps command to show information about the running guest container, including the shortened form of the image ID, the base image used to create this container, and the container name:

     

    [holuser@docker ~]$ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
    722c42b3c151        oraclelinux         "/bin/bash"         About a minute ago   Up About a minute                       guest
    

     

    Back in the original shell and in the newly-created Docker guest, we install the httpd and perl RPM packages using yum, just as you would on any other physical or virtual server. We also use yum clean all to remove cache files that yum creates during package installation, because that will ultimately save some space in the exported container image:

     

    [root@722c42b3c151 /]# yum install -y httpd perl && yum clean all
    Loaded plugins: ulninfo
    ol7_UEKR3                                                                                                                                                | 1.2 kB  00:00:00
    ol7_latest                                                                                                                                               | 1.4 kB  00:00:00
    (1/5): ol7_UEKR3/x86_64/updateinfo                                                                                                                       |  64 kB  00:00:00
    (2/5): ol7_latest/x86_64/updateinfo                                                                                                                      | 848 kB  00:00:00
    (3/5): ol7_latest/x86_64/group                                                                                                                           | 681 kB  00:00:00
    (4/5): ol7_latest/x86_64/primary                                                                                                                         |  17 MB  00:00:03
    (5/5): ol7_UEKR3/x86_64/primary                                                                                                                          |  19 MB  00:00:05
    ol7_UEKR3                                                                                                                                                               444/444
    ol7_latest                                                                                                                                                          14758/14758
    Resolving Dependencies
    --> Running transaction check
    ---> Package httpd.x86_64 0:2.4.6-40.0.1.el7_2.1 will be installed
    --> Processing Dependency: httpd-tools = 2.4.6-40.0.1.el7_2.1 for package: httpd-2.4.6-40.0.1.el7_2.1.x86_64
    --> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-40.0.1.el7_2.1.x86_64
    --> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-40.0.1.el7_2.1.x86_64
    --> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-40.0.1.el7_2.1.x86_64
    ...
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ================================================================================================================================================================================
     Package                                          Arch                             Version                                           Repository                            Size
    ================================================================================================================================================================================
    Installing:
     httpd                                            x86_64                           2.4.6-40.0.1.el7_2.1                              ol7_latest                           1.2 M
     perl                                             x86_64                           4:5.16.3-286.el7                                  ol7_latest                           8.0 M
    ...
    Transaction Summary
    ================================================================================================================================================================================
    Install  2 Packages (+31 Dependent packages)
    
    Total download size: 14 M
    Installed size: 44 M
    Downloading packages:
    (1/33): apr-1.4.8-3.el7.x86_64.rpm                                                                                                                       |  99 kB  00:00:00
    (2/33): apr-util-1.5.2-6.0.1.el7.x86_64.rpm                                                                                                              |  91 kB  00:00:00
    (3/33): groff-base-1.22.2-8.el7.x86_64.rpm                                                                                                               | 933 kB  00:00:00
    ...
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    Total                                                                                                                                           7.0 MB/s |  14 MB  00:00:01
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : apr-1.4.8-3.el7.x86_64                                                                                                                                      1/33
      Installing : apr-util-1.5.2-6.0.1.el7.x86_64                                                                                                                             2/33
      Installing : httpd-tools-2.4.6-40.0.1.el7_2.1.x86_64                                                                                                                     3/33
    ...
    
    Installed:
      httpd.x86_64 0:2.4.6-40.0.1.el7_2.1                                                        perl.x86_64 4:5.16.3-286.el7
    
    Dependency Installed:
      apr.x86_64 0:1.4.8-3.el7              apr-util.x86_64 0:1.5.2-6.0.1.el7          groff-base.x86_64 0:1.22.2-8.el7               httpd-tools.x86_64 0:2.4.6-40.0.1.el7_2.1
      mailcap.noarch 0:2.1.41-2.el7         perl-Carp.noarch 0:1.26-244.el7            perl-Encode.x86_64 0:2.51-7.el7                perl-Exporter.noarch 0:5.68-3.el7
      perl-File-Path.noarch 0:2.09-2.el7    perl-File-Temp.noarch 0:0.23.01-3.el7      perl-Filter.x86_64 0:1.49-3.el7                perl-Getopt-Long.noarch 0:2.40-2.el7
      perl-HTTP-Tiny.noarch 0:0.033-3.el7   perl-PathTools.x86_64 0:3.40-5.el7         perl-Pod-Escapes.noarch 1:1.04-286.el7         perl-Pod-Perldoc.noarch 0:3.20-4.el7
      perl-Pod-Simple.noarch 1:3.28-4.el7   perl-Pod-Usage.noarch 0:1.63-3.el7         perl-Scalar-List-Utils.x86_64 0:1.27-248.el7   perl-Socket.x86_64 0:2.010-3.el7
      perl-Storable.x86_64 0:2.45-3.el7     perl-Text-ParseWords.noarch 0:3.29-4.el7   perl-Time-HiRes.x86_64 4:1.9725-3.el7          perl-Time-Local.noarch 0:1.2300-2.el7
      perl-constant.noarch 0:1.27-2.el7     perl-libs.x86_64 4:5.16.3-286.el7          perl-macros.x86_64 4:5.16.3-286.el7            perl-parent.noarch 1:0.225-244.el7
      perl-podlators.noarch 0:2.5.1-3.el7   perl-threads.x86_64 0:1.87-4.el7           perl-threads-shared.x86_64 0:1.43-6.el7
    
    Complete!
    Loaded plugins: ulninfo
    Cleaning repos: ol7_UEKR3 ol7_latest
    Cleaning up everything
    

     

    At this point, We need to configure some content for the web server to display. For simplicity, let's create a basic opening page in the /var/www/html directory on the guest:

     

    [root@722c42b3c151 /]# echo "Example Web Server Content" > /var/www/html/index.html
    

     

    The guest container is now configured with the software environment that we want. Typing exit stops the running guest, returning to the prompt for the Linux host:

     

    [root@722c42b3c151 /]# exit
    exit
    [holuser@docker ~]$
    

     

    We now need to save this modified guest as an image that can be used to create new containers. To get the ID of the modified container, we use the docker ps command with the --latest and --quiet parameters. This will output just the ID of the latest guest to be created or stopped:

     

    [holuser@docker ~]$ docker ps --latest --quiet
    722c42b3c151
    

     

    The docker ps command lists currently running containers by default. If we run it now, there are no results (because we have no running containers at the moment):

     

    [holuser@docker ~]$ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    

     

    We can add the --all parameter to list both running and stopped containers:

     

    [holuser@docker ~]$ docker ps --all
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                          PORTS               NAMES
    722c42b3c151        oraclelinux         "/bin/bash"         5 minutes ago       Exited (0) About a minute ago                       guest
    

     

    We can combine the output from the docker ps --latest --quiet command with the docker commit command to save the last-running guest as a new image:

     

    [holuser@docker ~]$ docker commit -m "OL7-httpd" `docker ps -l -q` doc-1002902/httpd:r1
    sha256:c6735c08eeab980aaa8206f8ebc60cb4838788290c1e7ab92567f477963ab57c
    

     

    Let's break down this command. To save an image, we use docker commit with a message (-m) parameter. We're then embedding the output of the docker ps -l -q command (using the short versions of the --latest and --quiet parameters) and finally providing a namespace, image name and tag for the image. We're saving our image into the hol10328 namespace with the image name of httpd and the tag, r1. If you want to save an image to the Docker Hub, you will need to use the namespace you created when you signed up.

     

    If we take a look at the images now, we'll see that our image is now available:

     

    [holuser@docker ~]$ docker images
    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    doc-1002902/httpd    r1                  c6735c08eeab        26 seconds ago      332.3 MB
    oraclelinux          6                   3956a77fcc51        2 weeks ago         223.1 MB
    oraclelinux          latest              5f5dae795065        2 weeks ago         278.1 MB
    mysql/mysql-server   latest              18a962a188ee        5 weeks ago         366.9 MB
    

    Deploying the Configured Docker Image

     

    We can deploy any number of web servers now using the new Docker image as a template. The following docker run commands run the container image hol10328/httpd, creating the containers guest1, guest2, and guest3 and executing httpd in each one:

     

    [holuser@docker ~]$ docker run -d --name guest1 -p 8081:80 doc-1002902/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    5339dd1872151b4e23e78e4576b3f92bb0a0c50d6e3efd30daa5abc74b1f9345
    [holuser@docker ~]$ docker run -d --name guest2 -p 8082:80 doc-1002902/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    d401a2b42f5eed77cc95d3231986b62360fbf01cb5899954502aaecfb646342d
    [holuser@docker ~]$ docker run -d --name guest3 -p 8083:80 doc-1002902/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    603792e96faed519757511b0c5b6a829d130eb9b1050ca67ffc586ab0a21591d
    

     

    The -p argument maps port 80 in each guest to ports 8080, 8081, or 8082 on the host. Because we didn't use the -i or -t parameters, all three containers have started in the background. We can use the docker ps command to show the running guests:

     

    [holuser@docker ~]$ docker ps
    CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                  NAMES
    603792e96fae        doc-1002902/httpd:r1   "/usr/sbin/httpd -D F"   18 seconds ago      Up 17 seconds       0.0.0.0:8083->80/tcp   guest3
    d401a2b42f5e        doc-1002902/httpd:r1   "/usr/sbin/httpd -D F"   23 seconds ago      Up 22 seconds       0.0.0.0:8082->80/tcp   guest2
    5339dd187215        doc-1002902/httpd:r1   "/usr/sbin/httpd -D F"   28 seconds ago      Up 27 seconds       0.0.0.0:8081->80/tcp   guest1
    

     

    The default IP address value of 0.0.0.0 means that the port mapping applies to all network interfaces on the host. Using a web browser or curl, we can test the web server running in each guest:

     

    [holuser@docker ~]$ curl http://docker.oracleworld.com:8081
    Example Web Server Content
    [holuser@docker ~]$ curl http://docker.oracleworld.com:8082
    Example Web Server Content
    [holuser@docker ~]$ curl http://docker.oracleworld.com:8083
    Example Web Server Content
    

    Using a Dockerfile

     

    Now that you've seen how to create and manipulate Docker containers using the command line, the preferred way to build and customize containers is actually using Dockerfiles. A Dockerfile is a small text file that contains the instructions required to construct a container. When a Dockerflle is built, each instruction adds a layer to the container in a step-by-step process. The build creates a container, runs the next instruction in that container, and then commits the container. Docker then runs the committed image as the basis for adding the next layer. The benefit of this layered approach is that Dockerfiles with the same initial instructions reuse layers.

     

    Dockerfiles also create an easily readable and modifiable record of the steps used to create a Docker image. Publishing a Dockerfile to the public Docker Hub or to an internal repository is generally the preferred method of creating and sharing Docker images.

     

    To create a Dockerfile, first create a directory for it:

     

    [holuser@docker ~]$ mkdir ~/dockerfile-httpd
    [holuser@docker ~]$ cd ~/dockerfile-httpd/
    

     

    In that directory, use a text editor to create a file called Dockerfile that contains the following contents:

     

    # Dockerfile for creating a Docker image for OL 7 and httpd and perl
    FROM oraclelinux
    MAINTAINER E. Jones <ejones@email-address.com>
    RUN yum install -y httpd perl && yum clean all
    RUN echo "Example Web Server Content" > /var/www/html/index.html
    EXPOSE 80
    CMD /usr/sbin/httpd -D FOREGROUND
    

     

    This Dockerfile reflects the same steps as the previous exercise in which we manually built the Docker image for my web server: it configures a new container from a base Oracle Linux 7 image, installs the httpd and perl RPMs, and adds placeholder content for the opening web page.

     

    The docker build command constructs a new Docker image from this Dockerfile, creating and removing temporary containers as needed during its step-by-step build process:

     

    [holuser@docker ~]$ docker build -t doc-1002902/httpd:r2 ~/dockerfile-httpd/.
    Sending build context to Docker daemon 2.048 kB
    Step 1 : FROM oraclelinux
     ---> 5f5dae795065
    Step 2 : MAINTAINER E. Jones  ---> Running in 80809c13f726
     ---> 40fc9a14038e
    Removing intermediate container 80809c13f726
    Step 3 : RUN yum install -y httpd perl && yum clean all
     ---> Running in 99f88a5ce9ff
    Loaded plugins: ulninfo
    Resolving Dependencies
    --> Running transaction check
    ---> Package httpd.x86_64 0:2.4.6-40.0.1.el7_2.1 will be installed
    ...
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ================================================================================
     Package                  Arch     Version                   Repository    Size
    ================================================================================
    Installing:
     httpd                    x86_64   2.4.6-40.0.1.el7_2.1      ol7_latest   1.2 M
     perl                     x86_64   4:5.16.3-286.el7          ol7_latest   8.0 M
    ...
    
    Transaction Summary
    ================================================================================
    Install  2 Packages (+31 Dependent packages)
    
    Total download size: 14 M
    Installed size: 44 M
    Downloading packages:
    --------------------------------------------------------------------------------
    Total                                              4.0 MB/s |  14 MB  00:03
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : apr-1.4.8-3.el7.x86_64                                      1/33
      Installing : apr-util-1.5.2-6.0.1.el7.x86_64                             2/33
      ...
    
    Installed:
      httpd.x86_64 0:2.4.6-40.0.1.el7_2.1        perl.x86_64 4:5.16.3-286.el7
    
    Dependency Installed:
      apr.x86_64 0:1.4.8-3.el7
      apr-util.x86_64 0:1.5.2-6.0.1.el7
    ...
    
    Complete!
    Loaded plugins: ulninfo
    Cleaning repos: ol7_UEKR3 ol7_latest
    Cleaning up everything
     ---> 4d8683e7f9b9
    Removing intermediate container 99f88a5ce9ff
    Step 4 : RUN echo "Example Web Server Content" > /var/www/html/index.html
     ---> Running in 74b86752bbcf
     ---> 09928d191d8e
    Removing intermediate container 74b86752bbcf
    Step 5 : EXPOSE 80
     ---> Running in 6b6d859399e0
     ---> 16006844f470
    Removing intermediate container 6b6d859399e0
    Step 6 : CMD /usr/sbin/httpd -D FOREGROUND
     ---> Running in 1eb525d7b1e6
     ---> b28b7dca2dd1
    Removing intermediate container 1eb525d7b1e6
    Successfully built b28b7dca2dd1
    

     

    The docker images command lists the new doc-1002902/httpd:r2 image created from the Dockerfile:

     

    [holuser@docker ~]$ docker images
    REPOSITORY           TAG                 IMAGE ID            CREATED              SIZE
    doc-1002902/httpd    r2                  b28b7dca2dd1        About a minute ago   332.3 MB
    doc-1002902/httpd    r1                  c6735c08eeab        8 minutes ago        332.3 MB
    oraclelinux          6                   3956a77fcc51        2 weeks ago          223.1 MB
    oraclelinux          latest              5f5dae795065        2 weeks ago          278.1 MB
    mysql/mysql-server   latest              18a962a188ee        5 weeks ago          366.9 MB
    

    Limiting Runtime Resources

     

    A Docker container uses underlying control group (cgroup) technologies, creating a cgroup for each Docker container. Cgroups provide useful capabilities, such as collecting metrics for CPU, memory, and block I/O usage and providing resource management capabilities. For each Docker container, you can find metrics in the host's corresponding cgroup hierarchy; for example, on an Oracle Linux 7 host running the Unbreakable Enterprise Kernel Release 3 (UEKR3), a memory.stat file in the directory /sys/fs/cgroup/memory/system.slice/docker-<container ID> lists memory-related metrics for that container. (See the Docker blog article "Gathering LXC and Docker containers metrics.")

     

    The docker run command provides options that enable runtime limits for memory and relative CPU allocations. These options provide a degree of resource control when executing container images. The -m or --memory option limits the amount of physical and swap memory available to processes within a container. For example, the following command places a limit of 256 MB for physical memory (and up to 512 MB for memory plus swap):

     

    [holuser@docker ~]$ docker run -d --memory=256m --name guest10 -p 8090:80 doc-1002902/httpd:r2
    e8132ca96e46dd3c03113c5facc6331bd5dd7fee6836331ba2578348d6a5605f
    

     

    The -c or --cpushares option provides a method of assigning a relative number of CPU shares:

     

    [holuser@docker ~]$ docker run -d --cpu-shares=512 --name guest11 -p 8091:80 doc-1002902/httpd:r2
    a21d4110898c2811c36b62006e17a57e6ddcc14b986d8a03cb650826f65ac273
    

     

    By default, a container gets 1024 CPU shares, so in this example, the guest2 container gets a relative 50 percent share of the available computing resources. Note that the host kernel applies limits only when there is resource contention. For example, if guest2 is assigned 512 shares (50 percent, as shown above) and guest1 is assigned 1024 shares (100 percent), processes in guest2 will still get 100 percent of the available CPU resources if all guest1 processes are idle.

     

    For more information about options to the docker run command, enter docker run --help or see the Docker run reference guide.

     

    Connecting a Web Server Container to a MySQL Container

     

    Suppose we want to connect my web server container to a container running an instance of MySQL. Docker makes it possible to link containers together, creating a secure channel for one container to access certain information from another.

     

    Previously we pulled the Oracle MySQL image from the Docker Hub using the command docker pull mysql/mysql-server. To run the MySQL image as a container named db, we need to create a location to store the MySQL data and start a new container:

     

    [holuser@docker ~]$ cd ~
    [holuser@docker ~]$ mkdir mysql-datadir
    [holuser@docker ~]$ docker run --name db -d -e MYSQL_ROOT_PASSWORD=Oracle123 -e MYSQL_DATABASE=webdb -e MYSQL_USER=webuser -e MYSQL_PASSWORD=secret -v /home/holuser/mysql-datadir/:/var/lib/mysql mysql/mysql-server
    dc275a55ff987471402b8fd3e4e9147e9e785d7d19482150f59ef4ccf0d63458
    

     

    Let's break down the run command: we start a container named db in the background using the -d (detached) parameter. We then set a series of environment variables using multiple -e parameters. The MySQL image allows you to customize the database that is created on startup by using environment variables to configure the container. In this example, we are setting the MySQL root password to Oracle123 and creating a new database called webdb. We then create a new MySQL user called webuser with the password of secret. The container automatically assigns the correct permission to the user for the database it creates.

     

    Using the -v or --volume flag is how you can associate database storage (or, in general, any files or folders) that a Docker container needs to change while it's running. Remember that Docker containers are "ephemeral"; that is, they are transient in nature and data does not persist beyond the lifespan of a container's execution. Associating a data volume with a container using the -v flag allows the container to access, modify, and persist data on the host.

     

    Reminiscent of an NFS mount, the -v flag causes the directory /home/holuser/mysql-datadir on the host to be mounted as /var/lib/mysql (the default database location for MySQL) on the container. From an operational standpoint, the ability to mount a database directory as a container volume in this way also provides an easy way to enable database backups.

    The Docker Hub mysql/mysql-server image includes an entrypoint script (entrypoint.sh) that sets up the database server automatically, initializing mysqld. We can view log messages generated from a container using the docker logs command:

     

    [holuser@docker ~]$ docker logs db
    Initializing database
    Database initialized
    MySQL init process in progress...
    Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
    Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
    mysql: [Warning] Using a password on the command line interface can be insecure.
    mysql: [Warning] Using a password on the command line interface can be insecure.
    mysql: [Warning] Using a password on the command line interface can be insecure.
    mysql: [Warning] Using a password on the command line interface can be insecure.
    
    /entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
    
    
    MySQL init process done. Ready for start up.
    

     

    We now have a MySQL container running. Next, we need to create a web server container than contains code to connect to the database and retrieve information.

     

    Let's create a new Dockerfile that configures a web server that includes some Perl scripts:

     

    [holuser@docker ~]$ cd ~
    [holuser@docker ~]$ mkdir dockerfile-linked-httpd
    [holuser@docker ~]$ cd dockerfile-linked-httpd/
    [holuser@docker dockerfile-linked-httpd]$
    

     

    Use your favourite text editor to create a Dockerfile with the following content:

     

    FROM oraclelinux:7
    RUN yum install -y httpd perl perl-DBI.x86_64 libdbi-dbd-mysql.x86_64 perl-DBD-MySQL.x86_64 && yum clean all
    ADD version.pl /var/www/cgi-bin/version.pl
    RUN chmod 755 /var/www/cgi-bin/version.pl
    ADD initdb.pl /var/www/cgi-bin/initdb.pl
    RUN chmod 755 /var/www/cgi-bin/initdb.pl
    ADD doquery.pl /var/www/cgi-bin/doquery.pl
    RUN chmod 755 /var/www/cgi-bin/doquery.pl
    EXPOSE 80
    ENTRYPOINT /usr/sbin/httpd -D FOREGROUND
    

     

    This Dockerfile introduces the new command ADD which allows us to add a file from the host into the container during build. In order to successfully build this container, we need to create the three Perl scripts on the host, in the same directory as the Dockerfile itself.

     

    version.pl:

    #!/usr/bin/perl
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb:host=db",
            "webuser",
            "secret",
            { RaiseError => 1 },
    ) or die $DBI::errstr;
    my $sth = $dbh->prepare("SELECT VERSION()");
    $sth->execute();
    my $ver = $sth->fetch();
    print "Version = ", @$ver, "\n";
    $sth->finish();
    $dbh->disconnect()
    

     

    initdb.pl

     

    #!/usr/bin/perl
    use strict;
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb:host=db",
        "webuser",
        "secret",
        { RaiseError => 1}
    ) or die $DBI::errstr;
    $dbh->do("DROP TABLE IF EXISTS People");
    $dbh->do("CREATE TABLE People(Id INT PRIMARY KEY, Name TEXT, Age INT) ENGINE=InnoDB");
    $dbh->do("INSERT INTO People VALUES(1,'Alice',42)");
    $dbh->do("INSERT INTO People VALUES(2,'Bobby',27)");
    $dbh->do("INSERT INTO People VALUES(3,'Carol',29)");
    $dbh->do("INSERT INTO People VALUES(4,'Daisy',20)");
    $dbh->do("INSERT INTO People VALUES(5,'Eddie',35)");
    $dbh->do("INSERT INTO People VALUES(6,'Frank',21)");
    my @noerr = ('Rows inserted in People table');
    print @noerr;
    print "\n";
    my $sth = $dbh->prepare( "SELECT * FROM People" );
    $sth->execute();
    
    for ( 1 .. $sth->rows() ) {
        my ($id, $name, $age) = $sth->fetchrow();
        print "$id $name $age\n";
    }
    $sth->finish();
    $dbh->disconnect();
    

     

    doquery.pl

     

    #!/usr/bin/perl
    use strict;
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb;host=db",
            "webuser",
            "secret",
            { RaiseError => 1 },
    ) or die $DBI::errstr;
    my $sth = $dbh->prepare( "SELECT * FROM People WHERE Age > $ARGV[0]" );
    $sth->execute();
    my $fields = $sth->{NUM_OF_FIELDS};
    my $rows = $sth->rows();
    print "Selected $rows row(s) with $fields field(s)\n";
    for ( 1 .. $rows ) {
        my ($id, $name, $age) = $sth->fetchrow();
        print "$id $name $age\n";
    }
    $sth->finish();
    $dbh->disconnect();
    

     

    These three scripts will allow us to create a basic MySQL database using initdb.pl, then query the MySQL server using version.pl and finally query the MySQL database we created using doquery.pl.

     

    Before we can run the web server, we need to build a new image using the docker build command. Using what you learned in the previous exercise, build a new Docker image for hol10328/httpd with the tag R3.

     

    Once your new Docker image is created, you can run it:

     

    [holuser@docker dockerfile-linked-httpd]$ docker run -d --name webdb -p 8080:80 --link db:db doc-1002902/httpd:r3
    06100187714ffe4de81079c8065e57383fa97104578c1b32ac2fd2a65378647f
    

     

    By linking containers using the --link flag, other application containers can access the MySQL database running in the db container. This simplifies the separation of database, application, and web services, making it easier to isolate services in different containers. Docker automatically configures networking and the /etc/hosts file in each linked container.

     

    Finally, we can run the Perl scripts in the web server container to create and query the database running in the database container

     

    [holuser@docker dockerfile-linked-httpd]$ curl http://docker.oracleworld.com:8080/cgi-bin/version.pl
    Version = 5.7.13
    

     

    [holuser@docker dockerfile-linked-httpd]$ curl http://docker.oracleworld.com:8080/cgi-bin/initdb.pl
    Rows inserted in People table
    1 Alice 42
    2 Bobby 27
    3 Carol 29
    4 Daisy 20
    5 Eddie 35
    6 Frank 21
    

     

    [holuser@docker dockerfile-linked-httpd]$ curl http://docker.oracleworld.com:8080/cgi-bin/doquery.pl?30
    Selected 2 row(s) with 3 field(s)
    1 Alice 42
    5 Eddie 35
    

     

    [holuser@docker dockerfile-linked-httpd]$ curl http://docker.oracleworld.com:8080/cgi-bin/doquery.pl?21
    Selected 4 row(s) with 3 field(s)
    1 Alice 42
    2 Bobby 27
    3 Carol 29
    5 Eddie 35
    

     

    The Oracle Linux documentation includes a detailed example of how to link a web container with a MySQL database, and it provides sample scripts that configure httpd.conf, create a database, and perform queries. Additional background on linking containers and using data volumes is available there and also in the Docker user guide.