Cluster Tomcat HOWTO

Cluster de Apache/Tomcat con Balanceo de Carga, Failover y Replicación de Sesiones en Ubuntu 8.04

En este HowTo mostraremos como instalar y configurar un cluster horizontal (de mas de un servidor físico) con una instancia de Tomcat corriendo en cada uno, y utilizando Apache para balancear la carga entre ellos. El cluster también proporcionara un mecanismo de failover (si una instancia de Tomcat se cae, el Apache redireccionara las solicitudes a otra de las instancias); y por último también replicación de sesiones, por lo que la caída de uno de los Tomcat no afectara la sesion con el servidor, lo que lo hace un mecanismo 100% transparente para el usuario.

Contamos con dos servidores (de aquí en adelante Nodo1 y Nodo2), cada uno con dos interfaces de red, una “publica” (conectada a Internet) y la otra conectada a una de las interfaces del otro servidor mediante un cable UTP cruzado.

Este no es un tutorial de Tomcat por lo tanto evitaremos entrar en detalles que no contribuyan a la configuración del cluster.

Instalamos el JDK de Java:

apt-get install sun-java6-jdk

Instalamos Tomcat 5.5 junto con las aplicaciones de prueba y el panel de administración:

apt-get install tomcat5.5 tomcat5.5-admin tomcat5.5-webapps

Ahora ya tenemos Tomcat instalado en modo “standalone”, lo primero que haremos será definir los roles y usuarios necesarios para la administración de Tomcat. Para ello editamos el archivo /etc/tomcat5.5/tomcat-users.xml de forma que su contenido luzca similar a esto:

<?xml version='1.0' encoding='utf-8'?> <tomcat-users>

 <role rolename="manager"/> 
 <role rolename="tomcat"/> 
 <role rolename="admin"/> 
 <user username="tomcat" password="tomcat" roles="tomcat,admin,manager"/> 

</tomcat-users>

En este caso definimos tres roles (administrador, manager y el de tomcat) y un mismo usuario y password para los tres roles – se recomienda elegir un password mas complejo-.


Para verificar que Tomcat haya quedado correctamente instalado podemos ingresar utilizando un browser al puerto 8180, y veremos la página de bienvenida:

http://192.168.1.102:8180

En el menú de la izquierda hay un link para ingresar al Tomcat Manager con el usuario y contrasena que definimos en el archivo tomcat-users.xml en el paso anterior. Desde allí podremos hacer el deployment de nuestras aplicaciones java.

Una vez que corroboramos la instalación de Tomcat debemos setear la variable de entorno JAVA_HOME al directorio donde se instalo el JDK:

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Es buena idea también agregar esta línea al comienzo del archivo /usr/share/tomcat5.5/bin/catalina.sh para asegurarnos de que siempre este correctamente definida cuando iniciemos el Tomcat.

Ubuntu tiene la particularidad de iniciar Tomcat con restricciones de seguridad un poco excesivas que nos podran traer dolores de cabeza en el futuro. Para evitar esto deshabilitaremos dichas restricciones en script de inicialización /etc/init.d/tomcat5.5, cambiando TOMCAT5_SECURITY=yes a TOMCAT5_SECURITY=no

Luego de estos pasos reiniciamos Tomcat para hacer efectivos los cambios:

/etc/init.d/tomcat5.5 restart

Todos los pasos hasta aquí detallados deberan aplicarse en cada uno de los servidores del cluster. En nuestro caso Nodo1 y Nodo2.

A continuación comenzaremos a configurar cada uno de los Tomcats para que funcionen en cluster.

El archivo donde se encuentran los parámetros de configuración del servidor Tomcat es /etc/tomcat5.5/server.xml . No nos pondremos a detallar cada uno de los parámetros en este HowTo ya que la idea es proporcionar una guía de rápida instalación, por mayor información consultar la documentación original de Apache Tomcat.

Así luce nuestro server.xml en el Nodo1:


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

<Listener className="org.apache.catalina.core.AprLifecycleListener" /> <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>

<GlobalNamingResources> <Environment name="simpleValue" type="java.lang.Integer" value="30"/> <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" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" /> <Connector port="8009" enableLookups="false" redirectPort="8443" protocol="AJP/1.3" /> <Engine name="Standalone" defaultHost="localhost" jvmroute="tomcat1" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" /> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false" /> </Engine> </Service>

</Server>


Prestemos atención al atriburo jvmroute=”tomcat1”, este debera estar presente en forma obligatoria y su valor corresponde al nombre de la instancia en el cluster. En este caso llamamos “tomcat1” a la instancia de Tomcat que corre en el Nodo1. Deberemos colocar este archivo en cada uno de los nodos, y recordar setear jvmroute en cada uno de ellos. Si estamos configurando un cluster vertical (varias instanacias de Tomcat en el mismo servidor físico) también debemos cambiar los puertos de los conectores, pero ese no es nuestro caso.


El otro archivo que nos interesa se llama context.xml y se encuentra en el mismo directorio que server.xml. Así es como se ve el nuestro:

<?xml version="1.0" encoding="UTF-8"?> <Context path="/konacart" reloadable="true" docBase="konacart" distributable="true"> <WatchedResource>WEB-INF/web.xml</WatchedResource> <Manager className="org.apache.catalina.session.PersistentManager" debug="1" saveOnRestart="true" maxActiveSessions="-1" minIdleSwap="-1" maxIdleSwap="-1" maxIdleBackup="-1"> <Store className="org.apache.catalina.session.FileStore" directory="/tmp" /> </Manager> </Context>

Aquí se definen algunos de los parámetros mas importantes para nuestro cluster. En la especificación del contexto indicamos que la aplicación que balancearemos (en nuestro caso se llama konacart la cual ya instalamos utilizando el Tomcat Manager) es distribuible, es decir, puede operar en un entorno distribuido, lo cual implica que todos los atributos de sesion deben implementar java.io.Serializable. Esto lo indicamos con distributable="true".

Ahora debemos especificar como haremos la replicación de sesiones. En esta implementación la lograremos haciendo que ambas instancias de Tomcat almacenen los archivos de sesion en un dispositivo compartido (un dispositivo remoto en nuestro caso, el cual montamos en el directorio /tmp de cada uno de los servidores via NFS). Esto lo especificamos en las secciones Manager y Store asignando las clases PersistentManager y FileStore al atributo className de cada uno de ellos, respectivamente. Además, en la sección de Store, indicaremos donde se guardaran las sesiones compartidas en el atributo directory (en nuestro caso es /tmp pero debera configurarse para el directorio que se desee).

Para balancear la carga entre los dos servidores Tomcat utilizaremos el servidor web Apache, el cual necesitara el modulo mod_jk para poder comunicarse con ellos. Por lo tanto debemos instalar ambos:

apt-get install apache2 libapache2-mod-jk

Para verificar que el Apache haya quedado bien instalado simplemente cargamos la dirección IP del servidor en el browser y si obtenemos un mensaje que dice “It Works!” significa que estamos por buen camino.

Como decíamos Apache actuara no solo como servidor de páginas sino como balanceador de carga, por lo tanto necesitaremos una sola instancia de apache. En nuestro caso lo instalamos en el Nodo1, pero podría ser en cualquiera de los dos nodos, o incluso en un tercero.

En la siguiente sección configuraremos mod_jk para que Apache pueda interactuar con el Tomcat y además incluiremos las directivas para el balanceo de carga.

Ingresamos al directorio de Apache y creamos un archivo de nombre workers.properties con el siguiente contenido:

  1. definimos el directorio de java y los workers que definiremos mas abajo (que se corresponden con los integrantes del cluster, mas el loadbalancer)

workers.tomcat_home=/tomcat1 workers.java_home=$JAVA_HOME ps=/ worker.list=tomcat1,tomcat2,loadbalancer

  1. definimos el primer worker, especificando la dirección IP del Nodo1 en nuestro caso y el puerto del conector (importante setear lbfactor en 1 para que sea incorporado en el balanceo)

worker.tomcat1.port=8009 worker.tomcat1.host=192.168.1.105 worker.tomcat1.type=ajp13 worker.tomcat1.lbfactor=1

  1. definimos otro worker para el segundo nodo, y así sucesivamente para cada uno de los nodos que nos interesa balancear

worker.tomcat2.port=8009 worker.tomcat2.host=192.168.1.106 worker.tomcat2.type=ajp13 worker.tomcat2.lbfactor=1

  1. por último definimos el worker para el loadbalancer especificando cuales workers debera balancear (todos los definidos anteriormente)

worker.loadbalancer.type=lb worker.loadbalancer.balance_workers=tomcat1,tomcat2

A continuación deberemos cargar el mod_jk en apache, indicarle la ubicación del archivo workers.properties y montar nuestra aplicación en en loadbalancer. Por lo tanto editamos el archivo /etc/apache2/mods-available/jk.load y colocamos el siguiente contenido (en este caso “konakart” es el nombre de la aplicación que queremos balancear -recordemos que el deploy de la misma ya debera estar hecho-):

LoadModule jk_module /usr/lib/apache2/modules/mod_jk.so JkWorkersFile "workers.properties" JkLogFile "mod_jk.log" JkMount /konakart loadbalancer JkMount /konakart/* loadbalancer

Acto seguido deberemos reiniciar Tomcat y Apache para que los cambios en la configuración tengan efecto:

/etc/init.d/tomcat5.5 restart /etc/init.d/apache2 restart

Si todo salió bien ya deberíamos tener nuestro cluster funcionando correctamente. Para probarlo (en caso de no tener lista la aplicación aun) podemos utilizar el siguiente script de prueba hecho en JSP, al que llamamos cluster.jsp:


<% session.setAttribute("tomcatcluster","tomcatcluster"); %><html><head><title>Super Cluster de Tomcat</title></head><body>

Tomcat Nodo 1
Session ID : <%=session.getId()%>

</body></html>

Este script debermos colocarlo dentro del directorio raíz de la aplicación que estamos balanceando (en cada uno de los nodos del cluster) y luego lo cargamos en nuestro browser a traves del apache (en nuestro caso el Apache fue instalado en el Nodo1 ):

http://192.168.1.105/konkart/cluster.jsp

Debera mostrarnos algo como lo siguiente:

Tomcat Nodo 1 Session ID : 7E6A7515A38F1C63BD453F200369B1AC

y al refrescar una o dos veces ...

Tomcat Nodo 2 Session ID : 7E6A7515A38F1C63BD453F200369B1AC

Aquí comprobamos dos cosas: por un lado que el balanceo de carga se esta llevando a cabo correctamente, ya que refrescamos el navegador y el Apache fue alternando entre el Nodo1 y el Nodo2, y por otro que las sesiones se están replicando correctamente ya que en ambos casos el script devolvio el mismo nombre de sesion en servidores diferentes.

La siguiente prueba que haremos es detener el Tomcat en uno de los nodos:

/etc/init.d/tomcat5.5 stop

Volvemos a refrescar la URL en nuestro navegador varias veces, y vemos que el balanceador de carga no nos envia al servidor que esta caído, por lo que el mecanismo de Failover también esta funcionando. Con esto concluimos nuestro objetivo :)