Stichword-Archiv: docker

Putting tomcat into a container

Februar 9, 2017 10:37 pm Veröffentlicht von

As soon as you get in touch with software there is no chance to get around one topic these days: Microservices. And wherever you hear this term there is always a second one around the corner: Docker. I myself have only little experience in productively using docker, and this is mostly restricted to the convenience of packaging stuff and installing it somewhere else via a central repository. But already this is quite cool, so why not try to go further and create a small Java service in a docker container if setting up things is so easy with docker. The goal of this post is to document how I achieved a first response of the service – setting up a Java microservice with docker should be almost no work, right? As I haven’t had much contact with tomcat and maven, I learned all this (not much in the end but it took some time) the hard way – fighting all the way through old forum posts, maven repositories, eclipse UIs, tomcat books and documentations. Please let me know if you see any potential improvements.

Setting things up

Here we go. First of all I created a docker-compose file that shows how my microservice-powered project will look like:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
proxy:
image: nginx
links:
- frontend
- backend
volumes:
- ./proxy/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
frontend:
image: nginx
volumes:
- ./frontend:/usr/share/nginx/html
backend:
image: tomcat
links:
- database
volumes:
- ./backend/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml
database:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=topsecret
- MYSQL_DATABASE=mydb
- MYSQL_USER=mydbuser
- MYSQL_PASSWORD=

I know container-links are not the most modern way to go in the docker universe, but for the ease of use and to show how things are tangled together, I still use them for this small project. As you can see, we have four different containers. One for the frontend, containing the great UI of our application. A second one containing the database with all the data the application will need to run. Don’t worry, I won’t bother you with boring database setup in this blog post. It’s only about the third thing: the tomcat container. For this container we add a link to the database, of course, and we mount a local file to override the user configuration of the tomcat.
This is needed to be able to show the manager application and to deploy remotely to the tomcat server. Here is the config file as I created it. (I copied it out of the container and adjusted it, maybe you should do the same to make sure it’s compatible with your version of tomcat. And, for productive usage, of course you should use a password for the admin user. But configuring maven is more complicated then, so for now it’s ok without for me.)

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<!--
NOTE: By default, no user is included in the "manager-gui" role required
to operate the "/manager/html" web application. If you wish to use this app,
you must define such a user - the username and password are arbitrary. It is
strongly recommended that you do NOT use one of the users in the commented out
section below since they are intended for use with the examples web
application.
-->
<!--
NOTE: The sample user and role entries below are intended for use with the
examples web application. They are wrapped in a comment and thus are ignored
when reading this file. If you wish to configure these users for use with the
examples web application, do not forget to remove the <!.. ..> that surrounds
them. You will also need to set the passwords to something appropriate.
-->
<!--
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
<user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
<user username="role1" password="<must-be-changed>" roles="role1"/>
-->
<role rolename="manager-script"/>
<user username="admin" password="" roles="manager-script"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="topsecret" roles="manager-gui"/>
</tomcat-users>

The last container is used to reverse-proxy the requests of the application either to the frontend or to the backend, depending on the URL. In this example, I configured nginx to lead all the requests starting with /backend (http://localhost/backend) or /manager to the backend container (make sure to set it to the correct port of tomcat, 8080) and all others to the frontend container. So we need to make sure the servlet we are going to create is available under /backend, so they can be routed through the reverse proxy correctly.
We run docker-compose up -d as usual and some seconds later the containers are up and running. If you now go to http://localhost/backend you should already see tomcat greeting you with a 404 error page. Installing tomcat has never been so easy.

Deploying to tomcat

Next, we want to be able to deploy a servlet to the tomcat running in the docker container. To do this, I decided to use maven to build and deploy the project. To get the initial setup, I ran maven with the following command line:
mvn archetype:generate -DgroupId=com.ntlx -DartifactId=backend -DarchetypeArtifactId=maven-archetype-webapp
After the execution of this command you will find your self in a folder structure, containing all the files and folders you always wondered where they come from, and who knows where to put them:

./pom.xml
./src
./src/main
./src/main/resources
./src/main/webapp
./src/main/webapp/index.jsp
./src/main/webapp/WEB-INF
./src/main/webapp/WEB-INF/web.xml

The pom.xml now needs some further adjustments because we want to deploy to a remote tomcat server. Here is the complete file, as it looks for me:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ntlx</groupId>
<artifactId>backend</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>backend Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>


</dependencies>
<build>
<finalName>backend</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<url>http://localhost/manager/text</url>
<server>TomcatServer</server>
<path>/backend</path>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

It already contains the plugins needed to deploy a servlet and it contains the information to deploy onto a remote tomcat. In our case the remote tomcat is localhost, port 80 (remember: reverse-proxied to port 8080 in the tomcat container). /manager/text is needed to deploy our service. That’s why we also created a reverse-proxy for this URL.

That’s nearly it. If we now run
mvn tomcat7:deploy
it should be able to deploy our web application to the tomcat server. Go to http://localhost/backend again to see the hello world page, maven created earlier.

Adding a servlet

To provide our RESTful service in the end, we want to use a servlet. Therefore, we need to create a .java file for our servlet class at the right place: src/main/java. Create this folder, add some subfolders for your package like com/ntlx/ and add a Java Servlet file there. I myself copied the example from the eclipse wizard for servlet creation:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
package com.ntlx;

/**
* Servlet implementation class FirstTest
*/
@WebServlet("/FirstTest")
public class FirstTest extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* Default constructor.
*/
public FirstTest() {
// TODO Auto-generated constructor stub
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}

}

As you can see, we set the URL to /FirstTest. This is the relative address where the servlet will be found. So, after a run of 
mvn tomcat7:redeploy

you should see the response of the servlet at http://localhost/backend/FirstTest

That’s it, now we can start developing the service! I think this is quite handy as you can deploy the same tomcat on a different machine, like the productive one instead of your developer notebook, and it will work as before. Of course we have to think of a process to package the servlet with the docker container, but this shouldn’t be too much effort.