Archive for October 6th, 2008

06
Oct

Connecting Apache to Multiple Tomcat Instances on Windows

I had to configure a Windows server with Apache, Tomcat, and MySQL in order to host a number of Clearspace instances. Each instance resides on its own subdomain. For instance, we have one Clearspace instance on cma.somedomain.com and another on cts.somedomain.com. I also had the requirement to make each Tomcat instance easily identifiable in the process list.

For this installation I grabbed

  • Apache 2.2.9
  • Tomcat 6.0.16
  • mod_jk 1.2.26
  • MySQL Java connector 5.1.6

The J2sdk 1.6.0_07 had already been loaded on the machine. Here is a reason to load the SDK instead of the JRE. The JAVA_HOME environment variable is required to start Tomcat, so add the entry and point it to the root of your Java install, i.e. C:\Program Files\Java\jdk1.6.0_06.

First, I installed MySQL 5.x on the server. No issues there. I used the Windows installer with MySQL running as a service.

On to Tomcat. Tomcat can host multiple instances of itself under one install. As one of my requirements was to make each instance appear as a separate and uniquely named process I chose to install separate complete instances of Tomcat. To identify the different Tomcat installation directories create a directory for each instance with the naming convention X:\tomcat-subdomain.

Tomcat Directory Structure

Tomcat Directory Structure

Copy the MySQL Java connector into tomcat\lib.

MySQL Java Connector

MySQL Java Connector

Next setup a CATALINA_HOME environment variable for each instance of Tomcat. Point the variable to the root of the directory just created. The naming convention I used is CATALINA_HOME_SUBDOMAIN.

Tomcat Environment Variables

Tomcat Environment Variables

Each Tomcat instance will need to use a unique set of ports to communicate. Modify X:\tomcat-installdir\conf\server.xml to set the ports. You’ll want each server to listen on its own port, so modify the server port accordingly. I incremented ports by 100 where possible for each instance, so my first instance runs on port 8005, my second instance runs on 8105, etc.

Tomcat Server Port

Tomcat Server Port

Tomcat contains its own web server, so modify the HTTP port.

Tomcat HTTP Port

Tomcat HTTP Port

And most importantly, the AJP port. The AJP port is the Apache port for forwarding requests. This port will ensure that Apache and Tomcat can talk to each other.

Tomcat AJP Port

Tomcat AJP Port

Next, modify the bat files so they refer to the current Tomcat instance. To do this, after the opening comments in each file set CATALINA_HOME to the correct HOME environment variable like this

rem
rem $Id: catalina.bat 615987 2008-01-28 18:48:10Z rjung $
rem ----------------------------------------------

set CATALINA_HOME=%CATALINA_HOME_CMA%

Do this for each instance in the following bat files in X:\tomcat-subdomain\bin\

  • catalina.bat
  • digest.bat
  • shutdown.bat
  • startup.bat
  • tool-wrapper.bat
  • version.bat

Now, make sure each instance of Tomcat appears as its own identifiable process in the process list. Rename the Tomcat EXEs to something identifiable in the process list. I followed my convention of tomcat-subdomain.

Tomcat EXEs

Tomcat EXEs

Then modify service.bat to reflect the changes. You’ll see ***MODIFY THIS LINE*** where you need to make modifications.

@echo off
if "%OS%" == "Windows_NT" setlocal

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
set CATALINA_HOME=%CATALINA_HOME_CMA%
rem ********************

rem Guess CATALINA_HOME if not defined
set CURRENT_DIR=%cd%
if not "%CATALINA_HOME%" == "" goto gotHome
set CATALINA_HOME=%cd%

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
if exist "%CATALINA_HOME%\bin\tomcat-cma.exe" goto okHome
rem ********************

rem CD to the upper dir
cd ..
set CATALINA_HOME=%cd%
:gotHome

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
if exist "%CATALINA_HOME%\bin\tomcat-cma.exe" goto okHome
rem ********************

echo The tomcat.exe was not found...
echo The CATALINA_HOME environment variable is not defined correctly.
echo This environment variable is needed to run this program
goto end
rem Make sure prerequisite environment variables are set
if not "%JAVA_HOME%" == "" goto okHome
echo The JAVA_HOME environment variable is not defined
echo This environment variable is needed to run this program
goto end
:okHome
if not "%CATALINA_BASE%" == "" goto gotBase
set CATALINA_BASE=%CATALINA_HOME%
:gotBase

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
set EXECUTABLE=%CATALINA_HOME%\bin\tomcat-cma.exe
rem ********************

rem Set default Service name

rem ********************
rem ***MODIFY THESE LINES***
rem ********************
set SERVICE_NAME=TomcatCMA
set PR_DISPLAYNAME=Apache Tomcat CMA
rem ********************

if "%1" == "" goto displayUsage
if "%2" == "" goto setServiceName
set SERVICE_NAME=%2

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
set PR_DISPLAYNAME=Apache Tomcat CMA
rem ********************

:setServiceName
if %1 == install goto doInstall
if %1 == remove goto doRemove
if %1 == uninstall goto doRemove
echo Unknown parameter "%1"
:displayUsage
echo.
echo Usage: service.bat install/remove [service_name]
goto end

:doRemove
rem Remove the service
"%EXECUTABLE%" //DS//%SERVICE_NAME%
echo The service '%SERVICE_NAME%' has been removed
goto end

:doInstall
rem Install the service
echo Installing the service '%SERVICE_NAME%' ...
echo Using CATALINA_HOME:    %CATALINA_HOME%
echo Using CATALINA_BASE:    %CATALINA_BASE%
echo Using JAVA_HOME:        %JAVA_HOME%

rem Use the environment variables as an example
rem Each command line option is prefixed with PR_

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
set PR_DESCRIPTION=Apache Tomcat Server CMA - http://tomcat.apache.org/
rem ********************

set PR_INSTALL=%EXECUTABLE%
set PR_LOGPATH=%CATALINA_BASE%\logs
set PR_CLASSPATH=%CATALINA_HOME%\bin\bootstrap.jar
rem Set the server jvm from JAVA_HOME
set PR_JVM=%JAVA_HOME%\jre\bin\server\jvm.dll
if exist "%PR_JVM%" goto foundJvm
rem Set the client jvm from JAVA_HOME
set PR_JVM=%JAVA_HOME%\jre\bin\client\jvm.dll
if exist "%PR_JVM%" goto foundJvm
set PR_JVM=auto
:foundJvm
echo Using JVM:              %PR_JVM%
"%EXECUTABLE%" //IS//%SERVICE_NAME% --StartClass org.apache.catalina.startup.Bootstrap --StopClass org.apache.catalina.startup.Bootstrap --StartParams start --StopParams stop
if not errorlevel 1 goto installed
echo Failed installing '%SERVICE_NAME%' service
goto end
:installed
rem Clear the environment variables. They are not needed any more.

rem ********************
rem ***MODIFY THIS LINE***
rem ********************
set PR_DISPLAYNAME=Apache Tomcat CMA
rem ********************

set PR_DESCRIPTION=
set PR_INSTALL=
set PR_LOGPATH=
set PR_CLASSPATH=
set PR_JVM=
rem Set extra parameters
"%EXECUTABLE%" //US//%SERVICE_NAME% --JvmOptions "-Dcatalina.base=%CATALINA_BASE%;-Dcatalina.home=%CATALINA_HOME%;-Djava.endorsed.dirs=%CATALINA_HOME%\endorsed" --StartMode jvm --StopMode jvm
rem More extra parameters
set PR_LOGPATH=%CATALINA_BASE%\logs
set PR_STDOUTPUT=auto
set PR_STDERROR=auto
"%EXECUTABLE%" //US//%SERVICE_NAME% ++JvmOptions "-Djava.io.tmpdir=%CATALINA_BASE%\temp;-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager;-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties" --JvmMs 128 --JvmMx 256
echo The service '%SERVICE_NAME%' has been installed.

:end
cd %CURRENT_DIR%

Next,  modify X:\tomcat-subdomain\conf\tomcat-users.xml to add the manager credentials.

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="manager"/>
  <user username="tomcat" password="securePa$$word" roles="manager"/>
</tomcat-users>

Now run service.bat to install this instance of Tomcat as a service.

x:\tomcat-subdomain\bin\service.bat

Open Windows services and modify the service to run automatically.

Tomcat Services

Tomcat Services

Start your Tomcat service. Each instance of Tomcat should serve files now. You can test this by navigating to Tomcat on the open port like http://somedomain:8180. Each Tomcat instance is easily identifiable in the server’s process list.

Tomcat Process List

Tomcat Process List

Next, I installed Apache 2.2.9 using the windows installer. This was pretty straightforward. Now to configure Apache to communicate with Tomcat.

Rename mod_jk-1.2.26-httpd-2.2.4.so to mod_jk.so and copy it into the Apache modules directory.

Apache mod_jk

Apache mod_jk

Modify httpd.conf to recognize mod_jk.

Apache httpd.conf

Apache httpd.conf

Add a line to load mod_jk in the modules section.

LoadModule jk_module modules/mod_jk.so

Now configure mod_jk to work with Tomcat. Mod_jk requires a workers.properties configuration file that defines how Apache should communicate with each Tomcat worker. Right after the mod_jk LoadModule line in httpd.conf, add a line to tell Apache where to find workers.properties. I also found logging mod_jk output helpful when debugging issues. I added lines to configure logging.

JkWorkersFile conf/workers.properties
JkLogFile "C:/Apache/logs/mod_jk.log"
JkLogLevel info

workers.properties looks something like this

worker.list = cts,cma,lucrumtesting

# cts worker
worker.cts.type=ajp13
worker.cts.host=localhost
worker.cts.port=8209

# cma worker
worker.cma.type=ajp13
worker.cma.host=localhost
worker.cma.port=8409

# lucrumtesting worker
worker.lucrumtesting.type=ajp13
worker.lucrumtesting.host=localhost
worker.lucrumtesting.port=8309

The first line in workers.properties is the worker.list. The worker.list is a comma-separated list that defines which workers Apache should pay attention to. You may have additional worker configurations in the workers.properties file that do not appear in worker.list. These additional workers are ignored. Apache only uses the workers identified in the worker.list.

The remaining values tell Apache to use AJP to connect to the Tomcat instance along with the servername and the AJP port we defined in the Tomcat server.xml files earlier.

Finally, Apache needs to know about each Tomcat instance. We accomplished this by configuring a virtual host for each Tomcat instance.

    ServerName cts.lucrumclearspace.com
    JkMount /* cts

    ServerName cma.lucrumclearspace.com
    JkMount /* cma

    ServerName lucrumtesting.lucrumclearspace.com
    JkMount /* lucrumtesting

JkMount tells Apache how to map to each Tomcat worker instance as an Apache virtual host.

That’s it in terms of getting the configuration working. One final step included a redirect as I could not resolve, say, cma.somedomain. My browser continued to redirect to cma.somedomain/cma. We replaced the Tomcat default document with a redirect to cma.somedomain, and we were done.

I am not a Java app server expert by any means, so please feel free to comment if there are better ways to accomplish some of this. I’m guessing that my redirect is a kludge that someone could recommend alternatives to.

- Andy