Dockerize Java Web App: Maven + Tomcat + Docker

Asked

Viewed 5,515 times

15

I have often seen several tutorials from Docker stop up "ready" services, but I see few related instructions on how to use it within the development flow. Mainly on compiled languages as an example the Java which requires an additional compilation step that unlike dynamic languages do not require this step.

How could I organize mine development workflow using Docker throughout life cycle? Both in the use of a container to compile my code, running the "mvn clean install", how much deploy on the application server (here in case Tomcat). Should I assemble 2 containers one for each step? What the dependencies between these two containers would look like since the second person in charge of the deploy will only be able to take action after the build container has done its job and generated the artifact (.War)?

A next step (and no less important) would be to create an integration of the execution of these steps with a IDE (Netbeans or Eclipse for example) so that this development flow with Docker would be transparent during the process.

In short:

  • What the flow would look like?
  • How would the Dockerfiles look?
  • How to organize the execution order? (Using shellscript, maybe or would fit in a Docker-Compose?)

4 answers

3


The idea is that I can use this solution in any project I use "Maven + Tomcat" putting the following files in the project root by following the steps below.

In the example I used the following project available on github: https://github.com/rogeriofonseca/docker-java-tomcat-maven

1 - File structure for Docker configuration

docker-compose.yml
dockerfiles/
├── Dockerfile-apache
├── Dockerfile-maven
├── Dockerfile-mysql
└── tomcat-users.xml
scripts/
└── entrypointscript.sh

2 - Docker-Compose.yml

version: '2'
services:
  db:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=projeto_jpa
    ports:
      - "3306:3306"
  maven:
      build:
        context: dockerfiles
        dockerfile: Dockerfile-maven
      volumes:
        - ~/.m2:/root/.m2
        - $PWD:/usr/src/mymaven
      volumes_from:
        - tomcat
  tomcat:
    build:
      context: dockerfiles
      dockerfile: Dockerfile-apache
    ports:
      - "8888:8080"

2.1 Dockerfile-apache

FROM tomcat:8.0

ADD . /code
WORKDIR /code
COPY tomcat-users.xml  $CATALINA_HOME/conf/
VOLUME $CATALINA_HOME/webapps

2.2 Dockerfile-Maven

FROM openjdk:7-jdk

ARG MAVEN_VERSION=3.3.9
ARG USER_HOME_DIR="/root"

RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
  && curl -fsSL http://apache.osuosl.org/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz \
    | tar -xzC /usr/share/maven --strip-components=1 \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"

VOLUME "$USER_HOME_DIR/.m2"
WORKDIR /usr/src/mymaven
ENTRYPOINT ["/usr/src/mymaven/scripts/entrypointscript.sh"]

2.3 Dockerfile-mysql

FROM mysql:5.7

ENV MYSQL_ROOT_PASSWORD=root
ENV MYSQL_DATABASE=projeto_jpa

expose 3306:3306

2.4 A point of attention for this one. He is responsible for compiling and copying all artifacts for Tomcat.
In my case I have a project that has several modules, so the end result will have more than one .War, for this reason the "find" searching all directories and subdirectories.

Filing cabinet: scripts/entrypoint.sh

#!/bin/bash
mvn clean install -e -f  /usr/src/mymaven  &&
find /usr/src/mymaven/target -name "*.war" -exec cp '{}' /usr/local/tomcat/webapps \;

2.5 Tomcat-users.xml

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

# Useful commands:

1 - To move the containers up just run the command at the root of the project:

$ docker-compose build --no-cache && docker-compose up -d

2 - To recompile after any change in the code Java just run only the specific container of Maven. He will play the role of recompiling the code and copying the artifacts to the volume shared between the two containers.

$ docker-compose run maven

Ps.: The next step will be to tailor this flow into my development environment by integrating the flow with my IDE. But this is agenda for another topic.

2

In the current project of which I participate I am using the following structure:

raiz-projeto/
  frontend/
    ...
    package.json 
  backend/
    ...
    pom.xml
  infra/
    dockerfiles/
      db.Dockerfile
      app-server.Dockerfile
      web-server.Dockerfile
      ...
    helpers.sh
  docker-compose.yml
  dev.docker-compose.yml
  test.docker-compose.yaml
  .alias

As the names suggest:

1 o código da api rest reside em 'backend';

2. o código da spa reside em 'frontend';

3. boa parte do código adicional relacionado a configuração do ambiente onde a solução roda reside em 'infra';

4. em 'docker-compose.yml', encontra-se a configuração dos containeres mais próxima possível de como estará na produção, em se fazendo necessário poderá haver um arquivo de extensão Dockerfile para montagem da imagem daquele container/serviço específico.

5. 'dev.docker-compose.yml' contem a configuração necessária para rodar um ambiente mínimo para desenvolvimento nas tecnologias envolvidas, por exemplo: 
  5a. ambiente nodejs para desenvolvimento de frontend;
  5b. ambiente maven para desenvolvimento do backend;

6. 'test.docker-compose.yml' contem a configuração necesária para execução de testes automatizado, seja mocando serviços inteiros para prover dependências aos módulos sob teste (um backend para um frontend, um banco de testes para um backend, um ldap para um backend, etc)

7. por fim, nao menos importante, '.alias' é um compêndio de linux aliases a serem usado dentro daquele projeto e facilmente configurável através de 'source ./.alias'. Dentre outras coisas pode conter um apelido para a linha de comando quase sempre necessário quando se trabalha com docker, exemplo:
  7a. alias draf='docker rm `docker ps -aq` -f'
  7b. alias dcr='docker-compose run'
  7c. alias dev-backend='dcr -f dev.docker-compose.yml backend /bin/bash'

It is a structure in process of optimization, so it is always undergoing adaptations.

2

I did something similar to demonstrate, modestly, technical compositions [Github Pssilva: 2017] (work in progress). But I adapted to contribute to the community and thus try to answer the questions above.

What the flow would look like?
Workflow is usually a subjective thing, so I will describe a generic flow that I used in my environment:
1. Docker environment in Virtualbox:

Assuming you already have a VM with Ubuntu 16.04.2 LTS (Xenial Xerus)16.04.2 LTS (Xenial Xeru) with GIT, and the SSH server installed and properly configured. Then, we can install Docker.

$ssh [SEU_USER]@[IP_HOST] -p 22
$sudo su
#echo "deb [arch=amd64] https://apt.dockerproject.org/repo ubuntu-$(lsb_release -cs) main" > /etc/apt/sources.list.d/docker.list
#apt-cache policy docker-engine
#apt-get update
#apt-get install linux-image-generic-lts-$(lsb_release -cs)
#reboot
$ssh [SEU_USER]@[IP_HOST] -p 22
$sudo apt-get update
$sudo apt-get install docker-engine
$sudo service docker start
$sudo service docker status
$sudo gpasswd -a $(whoami) docker
$exit
$ssh [SEU_USER]@[IP_HOST] -p 22


2. Testing the Docker: Helo Word.

$ssh [SEU_USER]@[IP_HOST] -p 22
$docker run oskarhane/hello echo "Hello, let me out of here"
$docker ps -a

Ready, after testing we can clean the containers and images.

$docker stop $(docker ps -a -q) && docker rm $(docker ps -a -q)
$docker rmi $(docker images -a -q)


3. Create the Spring Boot project

3.1 Installing plugins in the IDE

In my case I use Eclipse IDE Luna Service Release 2 (4.4.2), Eclipse Java EE IDE for Web Developers. And the main plugins I use are:
- The Spring Tool (STS)
- m2e - Maven Integration for Eclipse
- Gradle IDE & Enide Gradle for Eclipse in one Operation.

3.2 Creating the project:

For this test here, using the STS plugin, create a project like this one from the image:

Spring Boot

Important, see Location: /Users/[SEU_USER]/Projects/microservicesSpringProject
That’s where we clone the repository.

Spring Boot Dependencias

3.3 Edit the file: /microservicesSpringProject/src/main/java/br/com/microservicesSpringProject/Microservicesspringprojectapplication.java

Leaving as low as:

package br.com.microservicesSpringProject;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class MicroservicesSpringProjectApplication {

    @RequestMapping("/")
    public String home() {
        return "My Microserver: Stackoverflow";
    }
    public static void main(String[] args) {
        SpringApplication.run(MicroservicesSpringProjectApplication.class, args);
    }
}

3.4 Build Application:

$cd /Users/[SEU_USER]/projects/microservicesSpringProject
$mvn package && java -jar target/microservicesSpringProject-0.0.1-SNAPSHOT.jar

Just to test the application, access the link: localhost:8080.
IMPORTANT: the file: /Users/[SEU_USER]/projects/microservicesSpringProject/target/microservicesSpringProject-0.0.1-SNAPSHOT.jar


4. Creating the project repository

4.1 In Virtual Machine, assuming you have already configured your git server [Git: 2017 - b]

$ssh [SEU_USER]@[IP_HOST] -p 22
$mkdir -p /opt/microservicesSpringProject.git
$cd /opt/microservicesSpringProject.git
$git --bare init  --shared=[DEV_GROUP_PERMISSION]
$chmod 0777 -Rf /opt/microservicesSpringProject.git
$chown [SEU_USER] -Rf /opt/microservicesSpringProject.git

4.2 On Host machine, physical machine.

$cd /Users/[SEU_USER]/projects/microservicesSpringProject
$chmod 0777 /Users/[SEU_USER]/projects/microservicesSpringProject
$git init
$echo "#[NOME_PROJETO]" > README.md
$git add .
$git commit -m 'initial commit'
$git remote add origin ssh://[SEU_USER]@[IP_SERVER]:22/opt/microservicesSpringProject.git
$git push origin master


5. Create your own Docker image: Create the Dockerfile file

Here we can answer: How would the Dockerfiles look?.
The same Dockerfile file can be reused to generate multiple images.
The dockerfile file [Docker: 2017 - the] can be done in several ways, the following format met me [Docker: 2017 - b]:

# escape=\ (backslash)
# My Microservices
#
# VERSION               0.0.1
# AUTHOR                Paulo Sergio da Silva
# EMAIL                 [email protected]

FROM    ubuntu
VOLUME /tmp

LABEL Description="This image is used to build a application whith Architecture Microservices." Vendor="PSSILVA Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools apache2 openssh-server git
RUN apt-get clean all
RUN apt-get install -y default-jre
RUN apt-get install -y default-jdk
RUN apt-get install -y openjdk-8-jre
RUN apt-get install -y openjdk-8-jdk
RUN apt-get install -y software-properties-common && add-apt-repository ppa:maxmind/ppa && apt-get update && apt-get -y install libmaxminddb0 libmaxminddb-dev mmdb-bin
RUN apt-get clean all
RUN apt-get update

CMD ["mkdir","-p","/opt/spring-1.5.1.RELEASE"]
#COPY spring-1.5.1.RELEASE/* /opt/spring-1.5.1.RELEASE/

#CMD ["ln","-s","./opt/spring-1.5.1.RELEASE/bash/spring","/etc/bash_completion.d/spring"]
#CMD ["ln","-s","./opt/spring-1.5.1.RELEASE/zsh/_spring","/usr/local/share/zsh/site-functions/_spring"]

ADD microservicesSpringProject-0.0.1-SNAPSHOT.jar /root/microservicesSpringProject-0.0.1-SNAPSHOT.jar
RUN sh -c 'touch /root/microservicesSpringProject-0.0.1-SNAPSHOT.jar'

ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /root/microservicesSpringProject-0.0.1-SNAPSHOT.jar" ]


#CMD ["java","-jar","/root/microservicesSpringProject-0.0.1-SNAPSHOT.jar"]

For versioning only, create the Dockerfile file in the /Users/[SEU_USER]/Projects/Microservices

$mkdir -p /Users/[SEU_USER]/projects/microservicesSpringProject/docker-image/spring-1.5.1.RELEASE
$cd /Users/[SEU_USER]/projects/microservicesSpringProject/docker-image
$vim Dockerfile

In Virtual Machine, create the file and build (build) the Docker image.

$ssh [SEU_USER]@[IP_HOST] -p 22
$mkdir -p ~/projects/microservicesSpringProject/docker-image/spring-1.5.1.RELEASE
$cd ~/projects/microservicesSpringProject/docker-image

//O comando abaixo envia a aplicação Spring Boot (.jar) da Máquina Física - MF para a Máquina Virtual.
$scp  -P22 -v -r -C  /Users/[SEU_USER]/projects/microservicesSpringProject/target/microservicesSpringProject-0.0.1-SNAPSHOT.jar [SEU_USER]@[IP_HOST]:/home/[SEU_USER]/projects/microservicesSpringProject/docker-image

$vim Dockerfile
$docker build -t ubuntu:MicroservicesSpringBoot .

After processing above, we can check the installed Docker images:

$docker images

Now, still in Virtual Machine, we can run the Spring Boot application:

$docker run -e "JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t -i ubuntu:MicroservicesSpringBoot java -jar /root/microservicesSpringProject-0.0.1-SNAPSHOT.jar

After the above command, we can access the Spring Boot application running in the Docker container from within the VM by accessing the link: [IP_HOST]:8080.

We can enter the container and work on the server as follows:

 $docker run -t -i ubuntu:MicroservicesSpringBoot /bin/bash


6. Send image Docker: hub.docker.com

Whereas you already have an account on hub.docker.com [hub.Docker: 2017]. And bearing in mind that reuse increases productivity, it is strongly indicated as good practice we have to send the Docker container to my repository.

$docker login
$docker tag [IMAGE_ID] [LOGIN_HUB_DOCKER]/[NOME_REPO]:latest
$docker push LOGIN_HUB_DOCKER]/[NOME_REPO]


7. Automation of the Process.

Here, I try to answer the following questions: How to organize the execution order? (Using shellscript, maybe or would fit in a Docker-Compose?)
But unfortunately I will deal with a more conceptual than empirical approach to the subject Continuous Integration - IC [Stackoverflow - CI: 2017]. For not having much confidence in what little I know, I am seeking to implement the guidelines and good practices [Alan Mark Berg: 2015] used in the Jenkins environment.


I believe that is it. Thank you for the question and for that, for the opportunity to learn and contribute.


Reference:

[Sébastien Goasguen: 2016], Docker Cookbook: SOLUTIONS AND EXAMPLES FOR BUILDING DISTRIBUTED APPLICATIONS.
[Alan Mark Berg: 2015], Jenkins Continuous Integration Cookbook: Second Edition
[Docker: 2017 - a], Available at: Dockerfile Best Practices. Accessed: 01 Apr 2017
[Docker: 2017 - b], Available at: Dockerfile Examples. Accessed: 01 Apr 2017
[Docker: 2017 - c], Available at: Paas Docker: Image Docker created with the intention of implementing a Platform as a Service - Paas. Accessed: 02 Apr 2017
[Git: 2017 - a], Available at: Git Essential Getting a Git Repository. Accessed: 01 Apr 2017
[Git: 2017 - b], Available at: Git on Server - Configuring Server. Accessed: 01 Apr 2017
[Christian Posta: 2016], Available in: Microservices for Java Developers: A Hands-on Introduction to Frameworks and Containers. Accessed: 01 Apr 2017
[Markus Eisele: 2016], Available in: Modern Java EE Design Patterns: Building Scalable Architecture for Sustainable Enterprise Development.
[Spring: 2017], Available in: Spring Boot with Docker: Site with tutorial. Accessed: 02 Apr 2017
[Github Pssilva: 2017], Available in: Github Pssilva: Meu modesto repositório Github. Accessed: 02 Apr 2017
[hub.Docker: 2017], Available in: Hub Docker: repository Docker. Accessed: 02 Apr 2017
[Stackoverflow - CI: 2017], Available in: Continuous Integration - IC: repository Docker. Accessed: 02 Apr 2017

  • Do you recommend installing a git server? Why? What is the build integration done in the IDE? Why dockerfile in commented commands (#COPY)?

  • @Murillogoulart, first: the git server understands it to be good practice, and is essential in automation and release management in the build in the Docker container. The commented commands on dockerfile is the installation of Spring Boot CLI. Which is also important for automation of the build process and integration testing. There are other ways to install, but I did it this way to have more control. And I thought I’d leave it out there 'cause I might escape the scope of the answer.

  • Your answer was quite extensive, but still I could not identify the following need: "A next step (and no less important) would be to create an integration of the execution of these steps with an IDE (Netbeans or Eclipse for example) so that this development flow with Docker is transparent during the process."

  • Steps like creating the Docker image cannot be manual, for example.

  • Because the question was this about the flow. So I thought I better focus on the flow. But in the 7. Automation of the Process I approached conceptually the best way to integrate the flow using Jenkins.

  • Creating the image is to demonstrate the power one has to create an image. Soon after I indicate: 6. Send the image Docker: hub.docker.com. With this use the image already ready in automation when necessary.

  • As little as I know, Devops' activities are to prepare the environment so that automated processes run transparently beyond the scope of development work. And developer just follows the scripts running without interfering too much.

  • Thanks for the answer! Gave me a way.

  • 1

    @Rafaelweber, OK ! But I want to improve the answer. As soon as I finish here some tests in the Jenkins environment. And testing integration between Docker containers.

Show 4 more comments

0

I will assume that you have an IDE and minimal resources to compile your Java project into it.

One of the changes I propose is to use the Spring Boot and use embedded Tomcat, which will simplify your deployment, allowing you to run your application with a simple java -jar app.jar.

Dockerize the application

Within your Spring Boot project, we created Dockerfile (src/main/docker/Dockerfile):

FROM openjdk:8-jre-alpine
VOLUME /tmp

ADD *.jar app.jar

ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]

To build the Docker image you need to add a plugin to Maven.

Build a Docker image with Maven

In the pom.xml you must add the following:

<properties>
   <docker.image.prefix>myorganization</docker.image.prefix>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>0.4.10</version>
            <configuration>
                <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
                <dockerDirectory>src/main/docker</dockerDirectory>
                <resources>
                    <resource>
                        <targetPath>/</targetPath>
                        <directory>${project.build.directory}</directory>
                        <include>${project.build.finalName}.jar</include>
                    </resource>
                </resources>
                <forceTags>true</forceTags>
                <imageTags>
                    <imageTag>${project.version}</imageTag>
                    <imageTag>latest</imageTag>
                </imageTags>                    
            </configuration>
        </plugin> 
    </plugins>
</build>    

This stretch has 3 configurations:

  • The image name, which will result in myorganization/myproject
  • The directory where Dockerfile should be found
  • The files (Resources) to copy from the build directory to build the Docker image (in this case only the file . jar is required)

To build the Docker image, you must execute the following command:

mvn package docker:build -DpushImage

You don’t need to push to run the locally created image. But it will allow you to make the image available on Dockerhub, making it easier to deploy automated cloud providers. To do so, you must change the setting docker.image.prefix for your own Docker ID.

Running the application with Docker

docker run -p 8080:8080 -t myorganization/myproject
  • Murillo Goulart, thank you for your reply. The idea of Springboot, the embedded Tomcat is excellent but it is not always possible to change the architecture of the project, often for practical or corporate reasons. The idea of including Docker settings in pom.xml also does not appeal to me because the idea is that the solution meets any project that uses Maven + Tomcat without the need for additional changes.

  • Finally, I would not consider compiling on the machine itself, because in a client code deployment scenario, it would be interesting for the application/container to be staless and fully autonomous. For deployment purposes there would be a container that would download the code and perform the compilation step on the client itself. Through this it would be possible to update a deploy running only a specific container.

  • @Rogériofonseca with pushImage, the image is published in dockerhub, being able to be easily automated the deploy from this.

  • But each system upgrade would depend on a push image on the Docker hub, wouldn’t it? The way I thought it would be enough to push a git repository and the git downloadable container. Understand?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.