Wednesday, May 25, 2005

Tomcat and IIS

I've been busy lately deploying my latest Java web application to production. It's been a bit of an adventure since I don't usually get to play the deployer role. Fortunately I already had my Ant script building the WAR (Web ARchive) file so I was only working on deploying to Tomcat 5.5.9. The twist is that I was deploying to four instances of Tomcat on two physical Windows 2003 servers fronted by the latest version of IIS.

The only reason IIS is involved at all is so that I could use the JK 1.2 Tomcat Connector to do the load balancing. This connector works as an ISAPI Filter and intercepts requests to my appliction (based on the URI) and forwards them on to an instance of Tomcat running my application. The connector does a weighted round robin load balancing and ensures that a request that is tied to a particular session on a particular Tomcat instance (from a previous request) is directed back to that same Tomcat instance.

But perhaps I'm a bit ahead of myself. Let's back up to the Tomcat installations. The first thing I wanted to accomplish was the sharing of the Tomcat JAR files among the various instances of Tomcat. By doing that, I know all Tomcat instances are running the same version and upgradable in one shot. The steps were:
  1. Unzip jakarta-tomcat-5.5.9.zip
  2. Rename jakarta-tomcat-5.5.9 to tomcat for simpler upgrading in the future (if only Windows had symbolic links)
  3. Create a directory called tomcat-cluster-node1 that looks something like this (the files in the subdirectories were copied from the standard Tomcat installation):


/tomcat (renamed from jakarta-tomcat-5.5.9)
/tomcat-cluster-node1 (The subirectories' files are copied from the tomcat
installation.)
/bin (contains service.bat)
/conf (contains catalina.policy, catalina.properties, context.xml,
server.xml, tomcat-users.xml, web.xml)
/Catalina
/localhost (contains manager.xml, host-manager.xml, myapp.xml)
/logs (empty)
/temp (empty)
/webapps (empty)
/work (empty)

Because this installation is on a Windows server I decided to follow the adage "When in Rome, do as the Romans do" and make my Tomcat instances run as Windows Services. Fortunately Tomcat is ready to go with a Tomcat5.exe and a batch script to create the registry entries call Service.bat. The only changes I had to make to the batch file were a few lines at the top to set some environment variables:

set CATALINA_BASE=E:\tomcat-cluster-node1
set CATALINA_HOME=E:\tomcat
set JAVA_HOME=E:\jdk1.5.0_03

then I ran "Service.bat install Node1" and I now have a service that I can easily start and stop. You may want to change the service to "Automatic" to ensure that it gets restarted in the event of a server restart.

Now back to the Tomcat connector installation. I like keeping the redirector stuff separate from the tomcat installation so I created a directory called /tomcat-iis-connector that contains the following files:

isapi_redirect.dll (downloaded from the jakarta site)
isapi_redirect.reg (see below)
uriworkermap.properties (start with a copy from the tomcat/conf directory)
workers.properties (ditto)

The reg file contains some registry entries that the DLL uses at runtime to find your configuration information. Mine looks like this:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Jakarta Isapi Redirector\1.0]
"extension_uri"="/jakarta/isapi_redirect.dll"
"log_file"="e:\\tomcat-cluster-node1\\logs\\isapi.log"
"log_level"="error"
"worker_file"="e:\\tomcat-iis-connector\\workers.properties"
"worker_mount_file"="e:\\tomcat-iis-connector\\uriworkermap.properties"

Now you have to actually tell IIS about the redirector DLL (notice the /jakarta/ prefix in the extension_uri entry in the reg file. This becomes important in a moment)
  1. Open IIS Manager and create a new Web Site.
  2. Create a virtual directory called "jakarta". This name must be the same as the prefix from the extension_uri value in the registry, and it must be executable.
  3. Go to the "ISAPI Filters" tab of your website's properties dialog and add a filter that points to the isapi_redirect.dll
  4. In the "Web Sites" properties dialog's Service tab select the "Run WWW service in IIS5.0 isolation mode" checkbox.
  5. Disable session state for your website (Properties dialog->Home Directory tab-> Configuration...->Options tab). Just let Tomcat handle your session timeouts.

Now all you have to do is configure your workers.properties file. Mine looks like this:

workers.tomcat_home=E:\tomcat
workers.java_home=E:\jdk1.5.0_03

ps=
worker.node1.port=8009
worker.node1.host=localhost
worker.node1.type=ajp13
worker.node1.cachesize=10
worker.node1.cache_timeout=600
worker.node1.socket_keepalive=1
worker.node1.recycle_timeout=300

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=node1
worker.loadbalancer.sticky_session=1

worker.list=loadbalancer

And then in the cluster-tomcat-node1/conf/server.xml make sure you identify the server with the same name you used when defining the worker in workers.properties, in this case "node1". Without this the "sticky sessions" won't work.

<engine name="Catalina" defaulthost="localhost" jvmroute="node1">

At this point I've only described the installation of a single Tomcat instance, but in reality I have four tomcat instances. So you can imagine more tomcat-cluster-node directories and a few more entries in workers.properties and the addition of those worker names to worker.loadbalancer.balanced_workers:

worker.loadbalancer.balanced_workers=node1, node2, node3, node4

Next you must define the URIs that should get redirected from IIS to Tomcat in the uriworkermap.properties file. Mine looks like this (of course you'll need to change the value of "mycontext" to the context of your app:

/mycontext/*.jsp=loadbalancer
/mycontext/*.do=loadbalancer
/mycontext/*.exe=loadbalancer
/mycontext/*.jpg=loadbalancer
/mycontext/*.gif=loadbalancer
/mycontext/*.png=loadbalancer
/mycontext/*.css=loadbalancer
/mycontext/*.js=loadbalancer
/mycontext/*.jpeg=loadbalancer
/mycontext/*=loadbalancer

Normally, I would like IIS to serve all my static content but I use struts tags to specify the location of images and the JSTL c:url tag to dynamically generate URLs. These tags rewrite the URL and not only include the name of the context (which is configurable at deployment time) but also include the jsessionid in the URL when not using cookies. Unfortunately IIS can't figure out what to do with the jsessionid so your pages come back without CSS or images. Not only do they look pretty awful but if you use js files some functionality may not even work. So I just map everything to Tomcat. On the plus side, deployment is simpler with the single WAR file.

Finally restart IIS and hope everything works! :-)

2 comments:

Anonymous said...

Dear Darcy, I replicated your experience about multiple instance of Tomcat running as services on Windows server. The question is: what about the Tcp port conflicts? I can change the 8080 port with 80, 8888, 81 and so on for each server but what about the 8005 and 8009 port conflict? Thank you in advance! Ivano Carrara - icarrara@studio5.it

sundog said...

Hi Ivano, If my memory serves me, I believe I left the first tomcat instance with the defaults and then incremented the second digit of each of the port numbers for each subsequent server. So I would have had something like:

Server 1: 8080, 8009, 8005
Server 2: 8180, 8109, 8105

It was just a bit easier to remember that way.