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.
Comments