EL Problems on Tomcat 6.0.35

Yesterday I wanted to start a new JSF archetype and I got this exception:

java.lang.LinkageError: loader constraint violation: when resolving interface method "javax.servlet.jsp.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory;" the class loader (instance of org/apache/catalina/loader/WebappClassLoader) of the current class, com/sun/faces/config/ConfigureListener, and the class loader (instance of org/apache/catalina/loader/StandardClassLoader) for resolved class, javax/servlet/jsp/JspApplicationContext, have different Class objects for the type javax/el/ExpressionFactory used in the signature

This happens if you have an “el-api.jar” in your project. The same jar file is already in the tomcat “lib” directory. I just removed the “el-api.jar” from my libs and than it worked out for me.

Tomcat 6 Cluster

Die folgende Beschreibung bezieht sich auf Tomcat 6.0.14 mit Java 5.
Eeine Anleitung zum Aufbauen eines Tomcat-Clusters mit Session-Replikation ist in der offiziellen Tomca-Dokumentation zu finden: http://tomcat.apache.org/tomcat-6.0-doc/cluster-howto.html
Ich beschreibe hier die Probleme auf die gestoßen bin beim Aufbau eines solchen Clusters.

Bei einem Cluster werden die HttpSessions auf alle Nodes (Teilnehmer/Server) im Cluster repliziert (kopiert). Somit ist es egal auf welche Node der Loadbalancer vor dem Cluster weiterleitet, da alle Sessions auf allen Nodes zur Verfuegung stehen. Das hat den Vorteil der hohen Verfuegbarkeit aber den Nachteil eines hohen Speicherverbrauchs.

Wenn sich ein Kunde auf eine Webapplikation einloggt die auf so einem Cluster laeuft, dann kriegt er nichts davon wenn der Server auf dem er gerade arbeitet abstuerzt. Der Loadbalancer leitet den naechsten Request vom Kunden auf einen anderen Server im Cluster um, wo er mit seiner alten Session weiter arbeiten kann. Bei einem einfachen Loadbalancing ohne Cluster und ohne Session-Replikation muesste sich der Kunde wieder neue anmelden an der WebApplikation und seine HttpSession waere fuer immer verloren.

Nach jedem Request wird geprueft ob sich eine Aenderung in der HttpSession ergeben hat. Wenn dies der Fall ist, wird das Delta auf alle anderen Nodes im Cluster repliziert. In der offiziellen Doku ist das folgende Stueck Konfigurationscode zu finden, das in die <TOMCAT_HOME>/conf/server.xml eingefuegt werden muss, um Tomcat im Cluster laufen zu lassen.

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4000"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="6"  />

   <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
       <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"  />
   </Sender>
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
 </Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
           tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="false"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Nach dem Starten des Tomcats habe ich dann folgende Fehlermeldung bekommen:

SEVERE: Unable to process request in NioReceiverjava.net.SocketException: Invalid argument

Das Problem kann behoben werden indem man dem Transport einen timeout und einen KeepAliveCount zuweist.

<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000" keepAliveCount="120000"  />

Mein Eintrag in der server.xml sieht so aus.

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4002"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="12"  />

   <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
     <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000" keepAliveCount="120000"  />
   </Sender>
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
   <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
 </Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="false"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Wenn die Nodes alle auf der gleichen Hardware laufen dann muessen die Ports vom Receiver angepasst werden. Die server.xml des zweiten Tomcats auf der gleichen Hardware sieht bei mir so aus:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"  >

 <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true"  />

 <Channel className="org.apache.catalina.tribes.group.GroupChannel"  >
   <Membership className="org.apache.catalina.tribes.membership.McastService"
               address="228.0.0.4"
               port="45564"
               frequency="500"
               dropTime="3000"  />
   <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
             address="auto"
             port="4001"
             autoBind="100"
             selectorTimeout="5000"
             maxThreads="12"  />

 <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"  >
   <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"
              timeout="60000"
              keepAliveCount="120000"  />
 </Sender>
 <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"  />
 <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"  />
</Channel>

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""  />
 <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"  />

 <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
           tempDir="/tmp/war-temp/"
           deployDir="/tmp/war-deploy/"
           watchDir="/tmp/war-listen/"
           watchEnabled="true"  />

 <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"  />
 <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"  />
</Cluster>

Der naechste Fehler auf dem ich gestossen bin ist:

java.net.SocketException: Invalid argument

Wenn der Tomcat mit der Option -Djava.net.preferIPv4Stack=true aufgerufen wird, dann tritt der Fehler nicht auf. Unter Linux kann mit folgendem Befehl die Umgebungsvariable “JAVA_OPTS” erweitert werden.

export JAVA_OPTS=$JAVA_OPTS;-Djava.net.preferIPv4Stack=true

bei starten des Tomcats werden alle Optionen aus der JAVA_OPTS gelesen.
Nun lief das Cluster endlich erfolgreich, also habe ich angefangen einige Applikation drauf zu deployen. Damit eine Applikation Clusterfaehig ist muessen alle Objekte die in der HttpSession abgelegt werden Serialisierbar sein. Ausserdem muss der Deploymentdescriptor (web.xml) folgenden Eintrag enthalten.

<distributable/>

Das deployen selber wollte ich ueber den Farmdeployer erledigen. Dabei soll die WAR-File nach “/tmp/war-listen” kopiert werden und der Farmdeployer verteil die Applikation auf alle Nodes im Cluster. Das hat leider nicht funktioniert! Nach einigem forschen im Internet bin ich in der tomcat-mailinglist auf einen Eintrag gestossen, aus dem ich erfahren habe das der farmdeployer noch nicht wirklich funktioniert und die Entwickler noch dran sind.

Also habe ich meine Applikationen per Hand deployt. Bei einer IceFaces Applikation konnte die Session mit folgender Fehlermeldung nicht repliziert werden:

java.io.NotSerializableException: com.icesoft.faces.webapp.http.servlet.ServletExternalContext

Bei einer Struts 2 + Spring 2.5 Anwendung ist die Replikation an folgender Fehlermeldung gescheitert.

java.io.NotSerializableException: org.springframework.jdbc.datasource.DriverManagerDataSource

Alles nicht so wirklich zufriedendstellend. Da gibt man sich muehe alle Seine Objekte Serialisierbar zu halten und kriegt man vom Framework nicht Serailisierbare Objekte in den Weg gelegt. Das bedarf einer intensiven Nachforschung.

Loadbalancing mit Apache2 + Tomcat 6 + mod_proxy

In dem Blogeintrag Loadbalancing mit Apache2 + Tomcat 6 + mod_jk 1.2.X habe ich beschrieben wie ein Loadbalancing mit dem “mod_jk” Module eingerichtet werden kann. Dieser Blogeintrag verfolgt das gleich Ziel, jedoch nicht mit dem “mod_jk” sondern mit dem “mod_proxy” Modul.

Im gegensatz zum “mod_jk” Modul muss das “mod_proxy” Modul nicht von einer externen Quelle heruntergeladen werden, es ist beim apache2 Webserver dabei. Unter Debian muss gegebenenfalls noch folgendes Kommando angesetzt werden.

apt-get install apache-modules

Ein Loadbalancing macht nur Sinn, wenn mindestens zwei Tomcats die gleiche Applikation ausliefern. Der Blogeintrag N Tomcats auf einer Maschine beschreibt wie zwei Tomcats zu konfigurieren sind damit sie parallel auf der selben Maschine laufen.

Gehen wir davon aus das wir eine Webapplikation namens “geo” haben die auf den zwei Tomcats “tomcat1” und “tomcat2” laufen. die “server.xml” von tomcat1 hat folgenden Eintrag:

<Engine name="Catalina"    defaultHost="localhost"    jvmRoute="tomcat1"   >

und die von tomcat2 dementsprechend:

 <Engine name="Catalina"    defaultHost="localhost"    jvmRoute="tomcat2"   >
 

Ganz wichtig hierbei ist das Attribut “jvmRoute”, in dem der Name des jeweiligen Tomcats gesetzt ist. Auf diesen Namen wird aus dem apache2 Webserver heraus referenziert.

In der Konfigurationsdatei des apache2, meisstens “/etc/apache2/httpd.conf” oder “/etc/apache2/apache.conf”, muss das “mod_proxy” Modul geladen werden. Das kann so aussehen:

LoadModule proxy_module libexec/apache2/mod_proxy.so
LoadModule proxy_connect_module libexec/apache2/mod_proxy_connect.so
LoadModule proxy_ftp_module libexec/apache2/mod_proxy_ftp.so
LoadModule proxy_http_module libexec/apache2/mod_proxy_http.so
LoadModule proxy_ajp_module libexec/apache2/mod_proxy_ajp.so
LoadModule proxy_balancer_module libexec/apache2/mod_proxy_balancer.so

Hier wird ein wenig mehr geladen als nur das reine “mod_proxy” Modul. Die Pfade muessen natuerlich an das entsprechende Betriebssystem angepasst sein.

Nach dem erfolgreichen laden des Moduls kann der Loadbalancer konfiguriert werden.

ProxyPass /geo balancer://apacheBalance stickysession=JSESSIONID

<Proxy balancer://apacheBalance>
 BalancerMember ajp://localhost:8009/geo route=tomcat1 loadfactor=50
 BalancerMember ajp://localhost:8010/geo route=tomcat2 loadfactor=50
 </Proxy>

In der ersten Zeile wird der Webserver angewiesen alle Anfragen mit der URI “geo” an den balancer mit dem Namen “apacheBalance” zu delegieren, der im Abschnitt weiter unten definiert ist. Der Balancer kann beliebig viele “BalancerMember” haben und unterstuetzt nicht nur das ajp Protokoll sondern auch http. Das Attribut “route” im BalancerMember referenziert auf den “jvmRoute” des Tomcats, die wir weiter oben eingestellt haben.

Der “loadfactor” muss ein Wert zwischen 0 und 100 sein und legt die Lastverteilung fest. Wenn beide balancer einen Wert von 50 haben, dann wird die Last gleich verteilt. Wuerde der “tomcat1” einen loadfactor von 10 haben und der andere einen loadfactor von 90, dann wuerde “tomcat2” neunmal mehr Last bekommen als “tomcat1”.
Die Applikation ist nun ueber die URL “http://localhost/geo&#8221; erreichbar.

Bei der ersten Anfrage leitet der apache2 auf eines der BalancerMember weiter, z.B. tomcat2. Alle weiteren Anfragen von dem gleichen Client werden ebenfalls an den tomcat2 weitergeleitet, sofern eine HttpSession benutzt wird. Um sich zu merken welche Clients zu welchen Tomcat weitegeleitet werden sollen, legt apache2 eine Cookie beim Cleint an. Dieser Cookie besteht aus der SessionId und dem Namen des jeweiligen Tomcats.

Bei allen weiteren Anfragen wird dieser Cookie mitgeschickt und so weis der apache2 immer genau an welchen Tomcat er die Anfrage weiterleiten soll. Dieses Verhalten macht natuerlich nur Sinn wenn die Webapplikation Kundendaten in einer HttpSession vorhaelt. Wenn das nicht der Fall ist, ist es vollkommen egal an welches der beiden Tomcats die nachfolgenden Anfragen weitergeleitet werden.
Und genau hier liegt der Vorteil vom “mod_proxy” Modul gegenueber dem “mod_jk” Modul. Mod_proxy haengt im Cookie nur dann den Namen des Tomcats an die SessionId wenn die HttpSession nicht leer ist.

Ein weiterer Vorteil des Moduls ist der balancer-manager, eine kleine Webanwendung die den aktuellen Status des Balancers zeigt. Um die Applikation einzubinden muss folgender Eintrag in die apache2 Konfigurationsdatei hinzugefuegt werden.

<Location /balancer-manager>
          SetHandler balancer-manager
</Location>

Nach der Aenderung muss der apache2 natuerlich neu gestartet werden und dannach ist die Anwendung uber die URL “http://localhost/balancer-manager&#8221; erreichbar.

Ueber den Balancer-Manager kann auch zur Laufzeit der loadfactor veraendert werden. Soll einer der beiden Tomcats (tomcat2) aus bestimmten Gruenden vom Netz genommen werden, sollte dieser einen loadfactor von 1 zugewiesen bekommen und der andere einen loadfactor von 99. Damit ist sichergestellt das alle weietern Requests an den Tomcat mit dem loadfactor 99 weitergeleitet werden. Nach einer gewissen Zeit sind auf dem tomcat2 keine HttpSessions mehr offen und er kann problemlos vom Netz genommen werden.

Um den unbefugten Zugriff auf den balancer-manager zu verhindern sollte er durch ein Passwort geschuetzt werden.

<Location /balancer-manager>
          SetHandler balancer-manager

          AuthType Basic
          AuthName "balancer manager "
          AuthUserFile /etc/apache2/htpassbalancer
          Require valid-user
</Location>

Die Passwortdatei kann mit folgendem Kommando angelegt werdne.

htpasswd -c /etc/apache2/htpassbalancer username

N Tomcats auf einer Maschine

Um zwei oder mehr Tomcats auf der selben Maschine laufen zu lassen muessen die Ports in der

<tomcat>/conf/server.xml

angepasst werden, das es sonst zu Portkonflikten kommt. Standardmaessig sind folgende Ports in der server.xml eingetragen.

– server port: 8005
– http port: 8080 redirectPort: 8443
– ajp port: 8009 redirectPort: 8443

Wenn ein zweiter Tomcat im Parallelbetrieb auf der selben Maschine laufen soll, muessen die Ports angepasst werden. Hier ist ein Vorschlag von mir.

– server port: 8006
– http port: 8180 redirectPort: 8444
– ajp port: 8010 redirectPort: 8444

Die server.xml zum ersten Tomcat sieht wie folgt aus.

<Server port="8005"   shutdown="SHUTDOWN"  >

  <Listener className="org.apache.catalina.core.AprLifecycleListener"   SSLEngine="on"   />
  <Listener className="org.apache.catalina.core.JasperListener"   />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"   />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"   />

  <GlobalNamingResources>
    <Resource name="UserDatabase"   auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml"   />
  </GlobalNamingResources>

  <Service name="Catalina"  >

    <Connector port="8080"   protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"   />

    <Connector port="8009"   protocol="AJP/1.3"   redirectPort="8443"   />

    <Engine name="Catalina"   defaultHost="localhost"   jvmRoute="mainworker"  >

       <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"  />

      <Host name="localhost"    appBase="webapps"
            unpackWARs="true"   autoDeploy="true"
            xmlValidation="false"   xmlNamespaceAware="false"  >

      </Host>
    </Engine>
  </Service>
</Server>

Die server.xml fuer den zweiten Tomcat:

<Server port="8006"   shutdown="SHUTDOWN"  >

  <Listener className="org.apache.catalina.core.AprLifecycleListener"   SSLEngine="on"   />
  <Listener className="org.apache.catalina.core.JasperListener"   />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"   />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"   />

  <GlobalNamingResources>
    <Resource name="UserDatabase"   auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml"   />
  </GlobalNamingResources>

  <Service name="Catalina"  >

    <Connector port="8180"   protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8444"   />

    <Connector port="8010"   protocol="AJP/1.3"   redirectPort="8444"   />

    <Engine name="Catalina"   defaultHost="localhost"   jvmRoute="tomcat2"  >

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"  />

      <Host name="localhost"    appBase="webapps"
            unpackWARs="true"   autoDeploy="true"
            xmlValidation="false"   xmlNamespaceAware="false"  >

      </Host>
    </Engine>
  </Service>
</Server>

Nachdem beide server.xml Dateien angepasst und die zwei Tomcats gestartet sind, ist der erste Tomcat unter folgender URL erreichbar:

http://localhost:8080/

Der zweite Tomcat dagegen unter der URL:

http://localhost:8180/

Loadbalancing mit Apache2 + Tomcat 6 + mod_jk

Die Nachfolgende Beschreibung zeigt wie ein Apache2 Webserver als Loadbalancer fuer zwei oder mehr Tomcats zu konfigurieren ist. Die Beschreibung bezieht sich wieder auf einen Linux Debian 4.1.X Server mit 2.6.X Kernel, auf dem ein apache2 Webserver, Tomcat 6.0.16 und Java 5.0 installiert und betriebsbereit ist.

Die folgende Graphik veranschaulicht die Systemarchitektur die aufgebaut werden soll.

34_balancer2
34_balancer2

Wie der Apache2 Webserver mit dem Tomcat ueber das mod_jk Modul verbunden wird, habe ich bereits in einem vorhergehenden Blogeintrag beschrieben. Darauf werde ich hier nicht mehr eingehen.

apache2 + tomcat 6 + mod_jk

Ein Loadbalancer macht nur Sinn wenn mehr als ein Tomcat die gleiche Applikation ausliefert. Der Loadbalancer kann die Last auch zwischen 2 Tomcats verteilen die auf unterschiedlichen Maschinen laufen. Wie zwei oder mehr Tomcats auf der selben Maschine eingerichtet werden, zeigt der folgende Blogeintrag:

N Tomcats auf einer Maschine

Wenn Sie die zwei oberen Blogeintraege gelesen und verstanden haben, dann ist es nur noch ein kleiner Schritt zum Loadbalancer. Als erstes muss die /etc/apache2/workers.properties angepasst werden. Hier ist ein Bespiel:

# Tomcat an Java configuration
#

workers.tomcat_home=/opt/tomcat/
workers.java_home=/opt/java/
ps=/

worker.list=loadbalancer

worker.mainworker.type=ajp13
worker.mainworker.port=8009
worker.mainworker.cachesize=20
worker.mainworker.lbfactor=1
worker.mainworker.host=localhost

worker.tomcat2.type=ajp13
worker.tomcat2.port=8010
worker.tomcat2.cachesize=20
worker.tomcat2.lbfactor=1
worker.tomcat2.host=localhost

worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=mainworker,tomcat2
worker.loadbalancer.method=Session

Die worker.list verweist auf den worker “loadbalancer”, welcher vom Typ “lb” (loadbalancing) ist und auf die zwei worker “mainworker” und “tomcat2” verweist. Der lbfactor gibt an wieviel Last ein worker uebernehmen soll. Wenn der mainworker einen lbfactor von 5 haette und der “tomcat2” nur einen von 1, dann wuerde der “mainworker” fuenf mal soviele Anfragen zugewiesen bekommen wie der “tomcat2”.
Eine vollstaendige Dokumentation des workers ist hier zu finden:

http://tomcat.apache.org/connectors-doc/reference/workers.html

Die worker Namen “mainworker” und “tomcat2” muessen natuerlich in den jeweiligen <tomcat>/conf/server.xml Dateien zu finden sein. Der eine Tomcat muss dafuer folgende Zeile in seiner server.xml enthalten:

<Engine name="Catalina"   defaultHost="localhost"   jvmRoute="tomcat2"  >

und der andere dementsprechend die Zeile:

<Engine name="Catalina"   defaultHost="localhost"   jvmRoute="mainworker"  >

Zum Schluss muss noch in der Datei /etc/apache2/sites-enabled/000-default der JkMount-Eintrag auf die gewuenschte Applikation angepasst werden:

JkMount /testApp loadbalancer

Wenn alles korrekt konfiguriert ist kann die Applikation durch folgende URL augerufen werden:

http://localhost/testApp/

Der Apache Webserver wird die Anfrage an eines der zwei Tomcats weiterleiten. Wenn der eine Tomcat runtergefahren wird, ist die Applikation immer noch auf dem zweiten Tomcat verfuegbar. Der Webserver leitet dann alle Anfragen automatisch an den einzig noch verbliebenen Tomcat, so das der Benutzer davon nichts mitkriegt.
Auf diese Art und Weise koennen Applikation ausfallsicherer gemacht werden.

Apache2 + Tomcat 6.0.x + mod_jk 1.2.x

Nachfolgend wird hier beschrieben wie man den apache2 Webserver und den Tomcat 6.0.16 Servletcontainer mit dem mod_jk 1.2.26 verbinden kann. Die Beschreibung bezieht sich auf einen Linux Debian 4.1.X Server mit 2.6.X Kernel, auf dem ein apache2 Webserver, Tomcat 6.0.16 und Java 5.0 installiert und betriebsbereit ist.

Der Tomcat liefert standardmaessig seine Applikationen unter dem Port 8080 aus. Diese Eigenschaft hat Nachteile, weil in vielen Firmennetzen der Port 8080 durch eine Firewall geblockt wird. Der Standard HTTP Port 80 dagegen ist in fast allen Netzen offen.
Der Tomcat laesst sich natuerlich auch so konfigurieren das er auf dem Port 80 horcht. Wenn auf dem gleichen Server aber schon ein Webserver installiert ist, kommt es schnell zu Konflikten. Die eleganteste Loesung besteht darin, den Webserver so zu konfigurieren, dass er bestimmte Domainanfragen an den Tomcat weiterleitet und die Antwort vom Tomcat an den Client weiterschickt.

Um den Apache2 Webserver und den Tomcat Sevletcontainer miteinander zu verheiraten muss man das mod_jk 1.2.26 installieren. Die Entwicklung des mod_jk 2.0 wurde wegen zu komplexer Konfiguration eingestellt, statt dessen wurde die alte Version komplett ueberarbeitet. Die aktuelle Version ist NICHT die 2.0 Serie sondern die mod_jk 1.2.26, welche unter folgender URL zum download bereit steht:

http://tomcat.apache.org/download-connectors.cgi

Da es fuer Debian zu Zeit keine mod_jk binary gibt, muss man sich die Source runter laden und compilieren. Nach dem Download sollte man mit folgenden Befehlen das Packet entpacken und in das native Verzeichnis wechseln.

debian:~# tar -xzvf tomcat-connectors-1.2.26-src.tar.gz
debian:~# chmod -R 755 tomcat-connectors-1.2.26-src
debian:~# cd tomcat-connectors-1.2.26-src/native

Anschliessend muessen fuer die Konfiguration, Kompilierung und Installation folgende drei Befehle ausgefuehrt werden.

debian:~# ./configure --with-apxs=/usr/bin/apxs2
debian:~# make
debian:~# make install

Wenn bei den letzten drei Befehlen Fehler auftreten dann liegt das vielleicht daran das Entwickler-Tools auf dem Server fehlen. Folgender Befehl kann Abhilfe schaffen:

debian:~# apt-get install libtool autoconf gcc apache2-prefork-dev g++

Nach der erfolgreichen Installation des mod_jk Modules muss als naechstes der apache2 Webserver konfiguriert werden. Als erstes muss eine workers.properties Datei erstellt werden.

debian:~# emacs /etc/apache2/workers.properties

Meine workers.properties Datei hat folgenden Inhalt

# Tomcat an Java configuration
#
workers.tomcat_home=/opt/tomcat/
workers.java_home=/opt/java/
ps=/worker.list=mainworker

# Definition for local worker using AJP 1.3
#
worker.mainworker.type=ajp13
worker.mainworker.port=8009
worker.mainworker.cachesize=20

Hier wird der aktuelle Pfad zu tomcat und zu java festgelegt und eine worker Liste beschrieben. In den letzten drei Zeilen wird der mainworker konfiguriert. Der Port 8009 ist ein Standardport fuer den ajp13-Connector von Tomcat. Dieser Wert ist nach dem entpacken von Tomcat standardmaessig in der <tomcat>conf/server.xml eingetragen. Wenn hier in der workers.properties ein anderer Wert eingetragen wird, dann muss die Portnummer in der server.xml vom Tomcat angepasst werden.
Der Name “mainworker” muss in die server.xml noch eingetragen werden.

<Engine name="Catalina"  defaultHost="localhost"  jvmRoute="mainworker"  >

Die eben gezeigte Zeile ist in der server.xml schon vorhanden. Sie muss lediglich um das Attribut jmvRoute=”mainworker” ergaenzt werden. Als naechstes oeffnen wir mit folgendem Befehl die apache2 Konfigurationsdatei:

debian:~# emacs /etc/apache2/apache2.conf

An letzter Stelle muss folgende Zeile hinzugefuegt werden

Include /etc/apache2/sites-enabled/

In dem Verzeichnis /etc/apache2/sites-enables/ befindet sich eine Datei mit dem Namen “000-default”. Die Datei ist in wirklichkeit ein Symlink auf “/etc/apache2/sites-available/default”, aber das soll uns nicht weiter stoeren. Die Datei kann mit jedem beliebigen Texteditor geoeffnet werden und muss um die folgenden Zeilen an erster Stelle ergaenzt werden.

<IfModule !mod_jk.c>
LoadModule jk_module "/usr/lib/apache2/modules/mod_jk.so"
</IfModule>

JkWorkersFile "/etc/apache2/workers.properties"
JkLogFile "/opt/tomcat/logs/mod_jk.log"
JkLogLevel info

In der gleichen Datei befindet sich ein Abschnitt mit VirtualHost, der um die JkMount Eintraege ergaenzt werden muss.
NameVirtualHost *

<VirtualHost *>
ServerAdmin webmaster@localhost
ServerAlias ploin.de www.ploin.de

DocumentRoot /var/www/
..
..
..
..

JkMount /robertReiz mainworker
JkMount /robertReiz/* mainworker

</VirtualHost>

Die letzten zwei Zeilen im VirtualHost Abschnitt teilen den Webserver mit das er alle Anfragen die mit robertReiz beginnen an den mainworker weiter leiten soll.

Nun muessen nur noch der Tomcat und der apche2 Webserver neu gestartet werden. Damit ist die Konfiguration abgeschlossen.