Showing posts with label docker design. Show all posts
Showing posts with label docker design. Show all posts

Monday, March 30, 2015

Docker and Apache Tomcat

Introduction


I'm going to
  1. extend the official Dockerfile for Tomcat
  2. build a new image
  3. launch a container from the modified image
  4. deploy and test a RESTful Web Service onto this container



Apache Tomcat


A docker search shows me the most popular (and official) Docker Tomcat container:
$ sudo docker search tomcat
[sudo] password for craig: 
NAME                                  DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
tomcat                                Apache Tomcat is an open source implementa...   103       [OK]       
tutum/tomcat                          Tomcat image - listens in port 8080. For t...   38                   [OK]
consol/tomcat-7.0                     Tomcat 7.0.57, 8080, "admin/admin"              12                   [OK]
consol/tomcat-8.0                     Tomcat 8.0.15, 8080, "admin/admin"              9                    [OK]
consol/tomcat-6.0                     Tomcat 6.0.43, 8080, "admin/admin"              6                    [OK]
consol/tomcat-4.1                     Tomcat 4.1.40, 8080, "admin/admin"              4                    [OK]
consol/tomcat-5.0                     Tomcat 5.0.30,  8080, "admin/admin"             4                    [OK]
consol/tomcat-5.5                     Tomcat 5.5.36, 8080, "admin/admin"              4                    [OK]
consol/tomcat-3.3                     Tomcat 3.3.2, 8080, "admin/admin"               4                    [OK]
readytalk/tomcat-native               Debian backed Tomcat + Tomcat Native Library    3                    [OK]
malderhout/tomcat                     Tomcat7 with OpenJDK7 on CentOS7                3                    [OK]
dordoka/tomcat                        Ubuntu 14.04, Oracle JDK 8 and Tomcat 8 ba...   3                    [OK]
meirwa/spring-boot-tomcat-mysql-app   a sample spring-boot app using tomcat and ...   2                    [OK]
h2000/docker-tomcat-youtrack          Dockerfile for youtrack to run under tomcat.    1                    [OK]
nicescale/tomcat                      Tomcat service for NiceScale. http://nices...   1                    [OK]
dmean/liferay-tomcat                  Debian + Liferay CE Tomcat                      1                    [OK]
atomi/tomcat                                                                          0                    [OK]
mminke/apache-tomcat                  A Docker image which contains the Apache T...   0                    [OK]
ericogr/tomcat                        Tomcat 8.0.21, 8080, "docker/docker"            0                    [OK]
holmes/tomcat                                                                         0                    [OK]
paulkling/tomcat                                                                      0                    [OK]
dynamind/tomcat                                                                       0                    [OK]
fabric8/tomcat-8.0                    runs Apache Tomcat 8.0 with jolokia enable...   0                    [OK]
learninglayers/tomcat                                                                 0                    [OK]
dmglab/tomcat                         CentOS 7 based tomcat installation              0                    [OK]


The official site describes the supported tags:

I currently work with version 7, so I'll be using tomcat:7.

I'm new to Docker (at the time of this article) and hestitate to call out "best practices".  What I'm abou to describe seems like a good practice, and I'll happily listen to any contrary opinion. For each Docker container I plan to launch, I prefer to create my own Dockerfile and extend the image.  It's entirely possible that I won't ever extend the image, and will simply use it as-is.  But building my own image from the target image seems like a logical way to use and extend the work of others in a consistent fashion.

In this case, I'm going to start by creating a simple Dockerfile with a single line:
FROM tomcat:7-jre7
MAINTAINER "Craig Trim <craigtrim@gmail.com>"

I'm going to build this image using this command:
$ sudo docker build -t craig/tomcat .

One advantage to this immediate extension is that I simplify my environment.  Eventually, I'll be using containers for Eclipse, MySQL and other apps.  I can give each a simplified namespace and image name.  On a project, I might choose the project codename as the container namespace.  I've also simplified the tag name.  These are trivial changes, and it could be argued that the lack of precision obscures details that are important.  I would suggest that on a large team with multiple developers this approach offers some advantages.  A common namespace, with a simplified image name and tag, would make it eaiser to direct team members toward using official project images.

As an example, I offer this:
tomcat:7-jre7ns/tomcat
mysql:5.6.23ns/mysql
fgrehm/eclipse:v4.4.1ns/eclipse

ns corresponds to a namespace every team member would understand. Launching a container becomes a matter of remembering the project codename (namespace) and the app name. Nothing else.


Running Tomcat


This command will run Tomcat and expose the container's port 8080 on the host's port of 8080:
$ sudo docker run -p 8080:8080 craig/tomcat

If I wanted to launch additional containers from this same image, I could just change
$ sudo docker run -p 8081:8080 craig/tomcat

I can test the running instance on my host:



Extending the Dockerfile


I'm going to extend the Dockerfile to support a configuration that will permit automated deployments from Maven.

I need to add a settings.xml file and update the tomcat-users.xml file. A simplified form of each file is given

tomcat-users.xml:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
 <role rolename="manager-gui"/>
 <role rolename="manager-gui"/>
 <role rolename="manager-script"/>
 <user username="craig" password="password" roles="manager,manager-gui,manager-script" />
</tomcat-users>

settings.xml:
<?xml version="1.0" encoding="UTF-8"?>
<settings>
 <servers> 
  <server>
   <id>TomcatServer</id>
   <username>craig</username>
   <password>password</password>
  </server> 
 </servers>
</settings>

I place each of these files in the same directory as my Dockerfile.

The Dockerfile is updated to this:
FROM tomcat:7-jre7

MAINTAINER "Craig Trim <craigtrim@gmail.com>"

ADD settings.xml /usr/local/tomcat/conf/
ADD tomcat-users.xml /usr/local/tomcat/conf/

The configuration files are added to the correct directories when the image is built. Any container launched from this image will now contain these files.


Rebuilding the Image


The image is rebuilt in the same manner we initially built it:
$ sudo docker build -t craig/tomcat .
Sending build context to Docker daemon 5.632 kB
Sending build context to Docker daemon 
Step 0 : FROM tomcat:7-jre7
 ---> 77eb038c09d1
Step 1 : MAINTAINER "Craig Trim <craigtrim@gmail.com>"
 ---> Using cache
 ---> cadc51a3054c
Step 2 : ADD settings.xml /usr/local/tomcat/conf/
 ---> Using cache
 ---> 5009ba884f1f
Step 3 : ADD tomcat-users.xml /usr/local/tomcat/conf/
 ---> Using cache
 ---> 33917c541bb5
Successfully built 33917c541bb5

We can view the history of image:
$ sudo docker history craig/tomcat
IMAGE               CREATED             CREATED BY                                      SIZE
33917c541bb5        4 hours ago         /bin/sh -c #(nop) ADD file:c1d08c42d5808537b4   1.761 kB
5009ba884f1f        4 hours ago         /bin/sh -c #(nop) ADD file:5dd8f0f6d0cd64de3c   212 B
cadc51a3054c        4 hours ago         /bin/sh -c #(nop) MAINTAINER "Craig Trim <cra   0 B
77eb038c09d1        3 weeks ago         /bin/sh -c #(nop) CMD [catalina.sh run]         0 B
a96609fc8364        3 weeks ago         /bin/sh -c #(nop) EXPOSE map[8080/tcp:{}]       0 B
ca99125fbf51        3 weeks ago         /bin/sh -c curl -SL "$TOMCAT_TGZ_URL" -o tomc   13.63 MB
e7ca14a4280a        3 weeks ago         /bin/sh -c #(nop) ENV TOMCAT_TGZ_URL=https://   0 B
eac866e259d8        3 weeks ago         /bin/sh -c #(nop) ENV TOMCAT_VERSION=7.0.59     0 B
d391d657b53a        3 weeks ago         /bin/sh -c #(nop) ENV TOMCAT_MAJOR=7            0 B
7b323fd1e0d3        3 weeks ago         /bin/sh -c gpg --keyserver pool.sks-keyserver   113.9 kB
4412b8a11fb6        3 weeks ago         /bin/sh -c #(nop) WORKDIR /usr/local/tomcat     0 B
b4ec9d590927        3 weeks ago         /bin/sh -c mkdir -p "$CATALINA_HOME"            0 B
681b802059fe        3 weeks ago         /bin/sh -c #(nop) ENV PATH=/usr/local/tomcat/   0 B
11b245da4142        3 weeks ago         /bin/sh -c #(nop) ENV CATALINA_HOME=/usr/loca   0 B
44faa7b2809f        3 weeks ago         /bin/sh -c apt-get update && apt-get install    164.5 MB
42c3653e1b26        3 weeks ago         /bin/sh -c #(nop) ENV JAVA_DEBIAN_VERSION=7u7   0 B
45ff981e92b4        3 weeks ago         /bin/sh -c #(nop) ENV JAVA_VERSION=7u75         0 B
5e9b188bc82c        3 weeks ago         /bin/sh -c apt-get update && apt-get install    676 kB
1073b544a1cb        3 weeks ago         /bin/sh -c apt-get update && apt-get install    44.34 MB
50ec2d202fe8        3 weeks ago         /bin/sh -c #(nop) CMD [/bin/bash]               0 B
3b3a4796eef1        3 weeks ago         /bin/sh -c #(nop) ADD file:fb7c52fc8e65391715   122.8 MB
511136ea3c5a        21 months ago                                                       0 B

The changes I have made are shown as occuring 4 hours ago. I can now launch a container from this modified image and proceed to test an automated deployment.


Deploying to Tomcat


This is properly the subject of another tutorial, but the best way to test our Tomcat installation is to deploy a WAR file.

I've created a simple JEE project using Maven with this structure:
$ tree
.
+-- pom.xml
+-- src
¦   +-- main
¦   ¦   +-- java
¦   ¦       +-- com
¦   ¦           +-- trimc
¦   ¦               +-- blogger
¦   ¦                   +-- samplewebapp
¦   ¦                       +-- CorsFilter.java
¦   ¦                       +-- TestREST.java
¦   +-- test
¦       +-- java
¦           +-- com
¦               +-- trimc
¦                   +-- blogger
¦                       +-- samplewebapp
+-- target
¦   +-- classes
¦   ¦   +-- com
¦   ¦       +-- trimc
¦   ¦           +-- blogger
¦   ¦               +-- samplewebapp
¦   ¦                   +-- CorsFilter.class
¦   ¦                   +-- TestREST.class
¦   ¦                   +-- TestREST$Result.class
¦   +-- generated-sources
¦   ¦   +-- annotations
¦   +-- m2e-wtp
¦   ¦   +-- web-resources
¦   ¦       +-- META-INF
¦   ¦           +-- MANIFEST.MF
¦   ¦           +-- maven
¦   ¦               +-- com.trimc.blogger.samplewebapp
¦   ¦                   +-- trimc-samplewebapp
¦   ¦                       +-- pom.properties
¦   ¦                       +-- pom.xml
¦   +-- maven-archiver
¦   ¦   +-- pom.properties
¦   +-- maven-status
¦   ¦   +-- maven-compiler-plugin
¦   ¦       +-- compile
¦   ¦       ¦   +-- default-compile
¦   ¦       ¦       +-- createdFiles.lst
¦   ¦       ¦       +-- inputFiles.lst
¦   ¦       +-- testCompile
¦   ¦           +-- default-testCompile
¦   ¦               +-- inputFiles.lst
¦   +-- test-classes
¦   +-- trimc-samplewebapp-1.0.0
¦   ¦   +-- META-INF
¦   ¦   ¦   +-- MANIFEST.MF
¦   ¦   +-- WEB-INF
¦   ¦       +-- classes
¦   ¦       ¦   +-- com
¦   ¦       ¦       +-- trimc
¦   ¦       ¦           +-- blogger
¦   ¦       ¦               +-- samplewebapp
¦   ¦       ¦                   +-- App.class
¦   ¦       ¦                   +-- CorsFilter.class
¦   ¦       ¦                   +-- TestREST.class
¦   ¦       ¦                   +-- TestREST$Result.class
¦   ¦       +-- lib
¦   ¦       ¦   +-- asm-3.1.jar
¦   ¦       ¦   +-- genson-1.2.jar
¦   ¦       ¦   +-- jersey-core-1.9.jar
¦   ¦       ¦   +-- jersey-server-1.9.jar
¦   ¦       +-- web.xml
¦   +-- trimc-samplewebapp-1.0.0.war
+-- WebContent
    +-- META-INF
    ¦   +-- MANIFEST.MF
    +-- WEB-INF
        +-- lib
        +-- web.xml

48 directories, 26 files

This plugin (in pom.xml) specifies deployment information corresponding to both the exposed port and the username and password in the tomcat configuration files:
<plugin>
 <groupId>org.apache.tomcat.maven</groupId>
 <artifactId>tomcat7-maven-plugin</artifactId>
 <version>2.2</version>
 <configuration>
  <url>http://localhost:8080/manager/text</url>
  <server>TomcatServer</server>
  <path>/sample</path>
  <username>craig</username>
  <password>password</password>
 </configuration>
</plugin>

Using Maven to deploy to Tomcat:
$ mvn tomcat7:deploy
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Test Runtime 1.0.0
[INFO] ------------------------------------------------------------------------
[INFO] 
 
 *** SNIP ***
 
[INFO] --- tomcat7-maven-plugin:2.2:deploy (default-cli) @ sandbox-web2 ---
[INFO] Deploying war to http://localhost:8080/test  
Uploading: http://localhost:8080/manager/text/deploy?path=%2Ftest
Uploaded: http://localhost:8080/manager/text/deploy?path=%2Ftest (1352 KB at 18512.6 KB/sec)

[INFO] tomcatManager status code:200, ReasonPhrase:OK
[INFO] OK - Deployed application at context path /test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.495 s
[INFO] Finished at: 2015-03-31T19:08:12-07:00
[INFO] Final Memory: 15M/506M
[INFO] ------------------------------------------------------------------------

and the Tomcat log gives this:
Apr 01, 2015 2:08:12 AM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.9 09/02/2011 11:17 AM' 
Apr 01, 2015 2:08:12 AM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deployment of web application archive /usr/local/tomcat/webapps/test.war has finished in 826 ms

and the all important echo output:




Conclusion


So what's the big deal?

Sure, we didn't have to install Tomcat; we launched a container that was built from an image described in a Dockerfile. That might come across as seeming more difficult than simply downloading Tomcat, unzipping it and running the startup script.

There's a few advantages that come to mind:
  1. Not every application is as easy to install as Tomcat
  2. Nearly every application requires additional configuration once installed
Using Docker in this capacity is similar to Vagrant/Puppet/Chef/Ansible/etc.  We write a script that describes our environment and a build tool stands up the environment automatically.  Docker has some clear advantages here -- it's much more lightweight than using a full-fleged VM.  Using the entire VM stack for Tomcat may be overkill in many situations.  Considering many developers work on laptops, it may not be possible to run multiple VMs successfully.

This brings up a third advantage of Docker:  We can launch multiple containers from the same image.  This is nowhere nearly as resource intensive as launching multiple VMs, and the startup time with Docker is nearly instantaneous.


References

  1. Web Application Links
    1. [GitHub] Sample REST Webapp
      1. Download the sample webapp used in this blog post
      2. The WAR file is available here
    2. [GitHub] Apache Tomcat Dockerfile
    3. [Blogger] Maven: a build automation tool
    4. [Blogger] An Introduction to JAX-RS (Java API for RESTful Web Services)
  2. [Blog] Bootstrapping WebSphere Liberty in Docker with confd
    1. An alternative to Apache Tomcat
  3. [Blog] Getting Started with Docker

Thursday, March 26, 2015

Docker: Design and Architecture

Modifying an Image


Let's say I pull an ubuntu image, add docker and want to commit this change:
sudo docker commit -m "added git" -a "Craig Trim" e50212abcd root/ubuntu:14.04

I can then launch one or more containers from this modified image using:
sudo docker run -t -i root/ubuntu:14.04 /bin/bash

as I normally would.


Local Storage on the Docker Host


Under this directory
/var/lib/docker

We have these contents:
root@dockerhost:/var/lib/docker# ls -lah
total 132K
drwxr-xr-x   5 root root 4.0K Jan 26 21:10 aufs
drwx------  22 root root 4.0K Mar 26 10:53 containers
drwx------   3 root root 4.0K Jan 26 21:10 execdriver
drwx------ 718 root root  72K Mar 26 10:43 graph
drwx------   2 root root 4.0K Jan 26 21:10 init
-rw-r--r--   1 root root  11K Mar 26 10:53 linkgraph.db
-rw-------   1 root root 4.6K Mar 26 10:43 repositories-aufs
drwx------   2 root root 4.0K Jan 26 21:10 tmp
drwx------   3 root root 4.0K Feb 16 08:31 vfs
drwx------   7 root root 4.0K Feb 17 17:30 volumes

Look at the repositories file using this command:
 cat repositories-aufs | python -mjson.tool | grep root/ubuntu -A 2

and the output is
"root/ubuntu": {
    "14.04": "24ca647184c90565dbb5a9009bcdcc030e814177e5a1cc8a4f14ef83d476b625"
}

This matches the output from running the "docker images" command:
root@dockerhost:/var/lib/docker# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
root/ubuntu         14.04               24ca647184c9        22 minutes ago      1.289 GB

The first 12 bytes of the IMAGE ID match the start of the key/value pair in the JSON list of local repositories.

By using the entirety of the ID, I can navigate to
root@dockerhost:/var/lib/docker/graph/24ca647184c90565dbb5a9009bcdcc030e814177e5a1cc8a4f14ef83d476b625# ls -lah
total 88K
-rw-------   1 root root 1.4K Mar 26 10:43 json
-rw-------   1 root root    9 Mar 26 10:43 layersize

and view the contents of the directory.

The "json" file contains metadata about the image:
{
    "Size": 956843450,
    "architecture": "amd64",
    "author": "Craig Trim",
    "comment": "adding hadoop provisioning",
    "config": {
        "AttachStderr": false,
        "AttachStdin": false,
        "AttachStdout": false,
        "Cmd": [
            "/bin/bash"
        ],
        "CpuShares": 0,
        "Cpuset": "",
        "Domainname": "",
        "Entrypoint": null,
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "ExposedPorts": null,
        "Hostname": "",
        "Image": "",
        "Memory": 0,
        "MemorySwap": 0,
        "NetworkDisabled": false,
        "OnBuild": null,
        "OpenStdin": false,
        "PortSpecs": null,
        "StdinOnce": false,
        "Tty": false,
        "User": "",
        "Volumes": null,
        "WorkingDir": ""
    },
    "container": "11ed9058054f2a5d58c28403f50234ebaa444cc08b185210754be74f46c5595a",
    "container_config": {
        "AttachStderr": true,
        "AttachStdin": true,
        "AttachStdout": true,
        "Cmd": [
            "/bin/bash"
        ],
        "CpuShares": 0,
        "Cpuset": "",
        "Domainname": "",
        "Entrypoint": null,
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "ExposedPorts": null,
 "Hostname": "11ed9058054f",
 "Image": "root/ubuntu:14.04",
 "Memory": 0,
 "MemorySwap": 0,
 "NetworkDisabled": false,
 "OnBuild": null,
 "OpenStdin": true,
 "PortSpecs": null,
 "StdinOnce": true,
 "Tty": true,
 "User": "",
 "Volumes": null,
 "WorkingDir": ""
    },
 "created": "2015-03-26T17:43:52.04299283Z",
 "docker_version": "1.2.0",
 "id": "24ca647184c90565dbb5a9009bcdcc030e814177e5a1cc8a4f14ef83d476b625",
 "os": "linux",
 "parent": "c9bd11fbe917e2855ed9872f4d05d216cbb1c3d5172fd542f6a479692b023004"
}





References

  1. [Blog] Beginner's Tutorial
    1. The idea behind Docker is to create portable lightweight containers for software applications that can be run on any machine with Docker installed
    2. Docker solves many of the same problem that a VM solves, plus some other that VMs could solve if they didn’t were so resource intensive.
    3. Here are some of the things that Docker can deal with:
      1. Isolating an application dependencies
      2. Creating an application image and replicating it
      3. Creating ready to start applications that are easily distributable
      4. Allowing easy and fast scalation of instances
      5. Testing out applications and disposing them afterwards
  2. [Blog] Where are Docker images stored?
    1. Local Storage on the Docker Host