Deploying a Java Application with Docker and Tutum

Introduction

This post will demonstrate how to deploy Java applications on a Cloud infrastructure using Tutum, Docker and Maven.

Prerequisites

In order to perform this tutorial you will need to have:

  • a Java development environment installed including:
  • A Java 8 JDK
  • Maven 3
  • Docker which is required to build the images even if we don’t need to run them in the development environment.
  • And of course a Tutum account.

Before starting the tutorial you might want to go through the getting started folder of the documentation. Make sure to familiarize yourself with the steps to link a cloud provider (for instance Digital Ocean or AWS) to your Tutum account.

Java Applications

In order to keep this tutorial as focused as possible, the code excerpts included will be stripped down of any boilerplate parts, such as package declarations, imports, etc. The full source code is available on bitbucket.

We will deploy two flavors of a similar application: from an executable jar and from a war file to be deployed on a Tomcat server. The application will basically be a REST Hello World returning a JSON document similar to the one below:

{
 message: "Hello World",
 date: "2014-12-14T19:09:17.675+0000",
 clientIP: "/127.0.0.1:51486",
 serverHostname: "myserver.mydomain",
 hitCount: 3
}

In both applications this message will be implemented by the simple HelloWorld class (the getter and a constructor of this attribute are omitted):

public class HelloWorld {
 private final String message;

 private final Date date;

 private final String clientIP;

 private final String serverHostname;

 private final long hitCount;
}

First Application

This application will be a simple Java application (i.e. starting with a main method). To implement this we will use the Fluent HTTP library. We can start with the simple pom.xml file below:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>net.ggtools.tutum.hello-world</groupId>
  <artifactId>fluent-http</artifactId>
  <packaging>jar</packaging>
  <version>0.1-SNAPSHOT</version>
  <name>Hello World Rest with Fluent Http</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>net.code-story</groupId>
      <artifactId>http</artifactId>
      <version>2.34</version>
    </dependency>
  </dependencies>
<project>

The application class will also be simple:

public class Application {

  public static void main(String[] args) throws UnknownHostException {
    String serverHostname = InetAddress.getLocalHost().getHostName();
    AtomicLong hitCount = new AtomicLong();
    new WebServer().configure(routes -> routes.get("/", (context) -> {
      return new HelloWorld("Hello World",
      new Date(),
      context.clientAddress(),
      serverHostname,
      hitCount.incrementAndGet());
    })).start();
  }
}

You can compile and run it and check that everything is working by opening http://localhost:8080/ with a browser or curl:

curl http://localhost:8080/

Notice that I didn’t add anything to configure the port on which the server will be listening, so the default 8080 port will be used and won’t be an issue.

Packaging

Next step will be to choose a packaging method to be able to deploy the code and the required dependencies. The easiest method is to put everything in a single über jar that can be launched by java -jar hello-world.jar. We will use the maven assembly plug-in with the following configuration:

<plugins>
  <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
      <archive>
        <manifest>
          <mainClass>net.ggtools.tutum.java.hello.Application</mainClass>
        </manifest>
      </archive>
      <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
    </configuration>
    <executions>
      <execution>
        <id>make-assembly</id>
        <phase>package</phase>
        <goals>
          <goal>single</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

At this point, we are able to build a self contained jar and run it with a simple command:

  1. mvn package will create fluent-http-0.1-SNAPSHOT-jar-with-dependencies.jar file in the target directory
  2. java -jar target/fluent-http-0.1-SNAPSHOT-jar-with-dependencies.jar will launch it

You can test it by connecting to http://localhost:8080/ using curl http://localhost:8080/ or your browser.

Docker image

The next part will be to create a docker image to be deployed with Tutum. In a nutshell, I will start from a base image with a Java 8 JRE, add the über jar, expose port 8080 and instruct Docker to run the java -jar command above.

A straightforward approach would be to write a Dockerfile similar to the one below and run docker build with the required parameters.

FROM java:8-jre
EXPOSE 8080
ADD hello-world-0.1-SNAPSHOT-jar-with-dependencies.jar /hello-world-0.1-SNAPSHOT-jar-with-dependencies.jar
ENTRYPOINT ["java", "-jar", "/hello-world-0.1-SNAPSHOT-jar-with-dependencies.jar"]

In addition to manually running docker build you would have to make sure the versions in the Dockerfile and the pom.xml are consistent, and copy the Dockerfile and the über jar to a working directory.

Luckily there is a nice docker plugin for maven making it possible to automate the image creation. In order to do this we need to add the following plugin configuration to the pom.xml:

<plugin>
  <groupId>com.spotify</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.1.1</version>
  <configuration>
    <imageName>tutum.co/yourtutumid/hello-world</imageName>
    <imageTags>
      <imageTag>${project.version}</imageTag>
    </imageTags>
    <baseImage>java:8-jre</baseImage>
    <entryPoint>["java", "-jar", "/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
    <exposes>
      <expose>8080</expose>
    </exposes>
    <resources>
      <resource>
        <targetPath>/</targetPath>
        <directory>${project.build.directory}</directory>
        <include>${project.build.finalName}-jar-with-dependencies.jar</include>
      </resource>
    </resources>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>build</goal>
      </goals>
    </execution>
  </executions>
</plugin>

This configuration includes both the information to create dynamically a Dockerfile and to build the image. As we are going to deploy the image on our private registry the image name starts with tutum.co/. We also added an instruction to tag the image with the maven artifact’s version. With this added configuration, running mvn package will create the über jar and create a docker image.

Second Application

This application will do exactly the same thing as the first one but instead of being based on Fluent Http and being packaged as an executable jar it will be based on Spring and packaged as a war file to be deployed on a Tomcat server. I’ll also use a subset of Springboot features in order to have a smaller code base, especially for the configuration part.

The pom.xml is a standard Springboot file stripped down since I only need to create a plain war file.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>net.ggtools.tutum.hello-world</groupId>
  <artifactId>spring-war</artifactId>
  <version>0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>Hello World as a war file</name>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.0.RELEASE</version>
    <relativePath/>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <start-class>Application</start-class>
    <java.version>1.8</java.version>
  </properties>
</project>

I will reuse the HelloWorld class from the first project so the only non-boilerplate portion of this application will be the controller:

@RestController
public class HelloWorldController {
  private final AtomicLong hitCount = new AtomicLong();
  private String hostName;

  @PostConstruct
  public void init() throws UnknownHostException {
    hostName = InetAddress.getLocalHost().getHostName();
  }

  @RequestMapping("/")
  public HelloWorld sayHello(HttpServletRequest request) throws UnknownHostException {
    return new HelloWorld("Hello World",
    new Date(),
    request.getRemoteAddr(),
    hostName,
    hitCount.incrementAndGet());
  }
}

Docker image

We will start from the official Tomcat image and add the application war file. The only issue here is that Tomcat uses the war file name as the context path. So, with the default configuration the application will be accessible through http://server/spring-war-0.1-SNAPSHOT/. We’ll solve this by adding a property to force the name of the war file to HelloWorld.war:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <start-class>Application</start-class>
  <java.version>1.8</java.version>
  <war.warName>HelloWorld</war.warName>
</properties>

We will use the same maven plugin to generate the image during the package phase of maven build:

<plugin>
  <groupId>com.spotify</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.1.1</version>
  <configuration>
    <imageName>tutum.co/clabouisse/hello-world-war</imageName>
    <imageTags>
      <imageTag>${project.version}</imageTag>
    </imageTags>
    <baseImage>tomcat:8-jre8</baseImage>
    <env>
      <JAVA_OPTS>-Djava.security.egd=file:/dev/urandom</JAVA_OPTS>
    </env>
    <!-- copy the service's jar file from target into the root directory of the image -->
    <resources>
      <resource>
        <targetPath>/usr/local/tomcat/webapps</targetPath>
        <directory>${project.build.directory}</directory>
        <include>${war.warName}.war</include>
      </resource>
    </resources>
    <cmd>catalina.sh run</cmd>
  </configuration>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>build</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The only difference with the first application will be that the application will be accessible on /HelloWorld rather than directly on the root context.

Into the Cloud

At this point, we now have a local Docker image for both applications. We’re just a few steps away from having our application deployed in the cloud.

I will walk through the steps using the first application, and you will use the same steps with the second application.

Pushing the image

The next step will be to push the image in order to be able to deploy it. We could use Docker Hub to store our image but we will use the Tutum private registry instead.

If you never pushed an image on your Tutum private repository you’ll need to enter your credentials using:

docker login tutum.co

After doing this operation you can push your image with:

docker push tutum.co/yourtutumid/hello-world

If you need more information about this step please refer to Tutum documentation.

Creating a Node

The first step will be the creation of a node cluster. As our application is quite simple, a cluster with a single node will be enough. For deployment purposes we will add a Hello-World deploy tag.

Node Creation

For more information about creating node clusters please refer to Tutum’s documentation.

Creating a Service

Last step will be the creation of a service. You can find more information about creating services by referring to Tutum’s documentation.

On the first screen we will select our newly pushed image from the private images tab:

Service Creation: image selection

We have two things to do on the Service configuration screen: add our Hello-World tag in the Deploy Tags box (the same as our node we just created) and publish port 8080. In order to access the service directly, we will remap port 8080 to the standard http port 80. There is nothing to fill on the Environment variables screen so you can click on Create and deploy now.

Screen Shot 2014-12-15 at 00.03.03

Testing the Service

Once the service has been deployed, it’s ready to be tested. From the Service dashboard you should now have a hello-world service running. Click on the triangle on the left side of the name to display the individual container:

Service Creation: image selection

Click on the container name and you’ll be redirected to the container screen on the Endpoints tab.

Service Creation: image selection

The last step is now to click on the icon on the right of the http-alt endpoint and voilà:

Screen Shot 2014-12-15 at 11.30.49

If you deployed the second application you will see the tomcat server welcome page so you have to add HelloWorld to the end of the URL to access your application.

Conclusion

We now have a live application deployed on a cloud infrastructure with no platform specific library and a very simple configuration. This is even simpler than with a traditional deployment as we weren’t even required to configure the port to listen on, as this is taken care of by Tutum during the deployment.

TODO

Beside deploying a more complex application some things can be done on the build side:

  • automate the docker push operation and make it a part of the maven deploy goal
  • as is, the build can only work on a machine configured for docker client operations. It would be smart to create a tutum profile and ensure that docker related operations would only occur when the profile is activated.
Posted in Tutorial
10 comments on “Deploying a Java Application with Docker and Tutum
  1. You need to HTML encode your XML code blocks.

  2. […] Following Deploying a Java Application with Docker and Tutum this article will demonstrate how to implement and deploy a more enterprise-focused application using Tutum. In a nutshell, we will start from the previously developed hello world application and add some important features in a real life application: […]

  3. […] Following Deploying a Java Application with Docker and Tutum this article will demonstrate how to implement and deploy a more enterprise-focused application using Tutum. In a nutshell, we will start from the previously developed hello world application and add some important features in a real life application: […]

  4. Kirk says:

    It would be helpful to see how to make it so the war file deployed into Tomcat is available at the root context instead of at /HelloWorld.

  5. Nitin Sharma says:

    Great article ! But I’m stuck at making docker images. I’m getting the following error :

    Failed to execute goal com.spotify:docker-maven-plugin:0.1.1:build (default) on project fluent-http: Exception caught: java.util.concurrent.ExecutionException: com.spotify.docker.client.shaded.javax.ws.rs.ProcessingException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:2375 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused -> [Help 1]
    org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal com.spotify:docker-maven-plugin:0.1.1:build (default) on project fluent-http: Exception caught

    Please help.

    I am trying to create image on macbook pro with Boot2Docker installed.

  6. www says:

    A custom web design service can also give additional solutions
    this sort of as social network advertising and research engine optimization. Prior to the experience, costs,
    and the various elements, what each potential customer would judge you by is your own site.
    These design elements look at the components making up the composition and offer a designer the groundwork from where he can begin working.

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories
%d bloggers like this: