Clustering de Apache Tomcat

4
20369

En este tutorial veremos cómo crear un clúster de instancias de Apache Tomcat, haciendo uso de la extensión mod_jk de Apache Web Server.

0. Índice de contenidos

1. Introducción

Una de las extensiones de Apache Web Server que nos permite implementar un cluster de servidores Tomcat y su correspondiente balanceador de carga es mod_jk, antes conocido como mod_jserv.

Durante este tutorial se explicará como realizar una instalación desde cero de un clúster de servidores Apache Tomcat, la motivación para implementar éste tipo de arquitectura y una pequeña prueba con una aplicación sencilla que nos permita observar el balanceador de carga en funcionamiento.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • MacBook Pro Intel Core 2 Duo 8GB RAM
  • SO: Yosemite 10.10.5
  • IDE: Eclipse Mars 4.5.0
  • JDK 1.7
  • Máquina Virtual: VMWare Fusion 7 corriendo Ubuntu Server 14.04

3. Motivación

Supongamos que tenemos un sistema en producción en una sóla máquina física. Si por cualquier motivo dicho sistema parase de dar servicio (fallo en el hardware, una aplicación bloquea el sistema operativo o hay una subida de tensión y la máquina se apaga), nadie podría acceder a nuestras aplicaciones./p>

¿Cuál podría ser la opción para aumentar la disponibilidad de nuestro sistema?

Una de las ideas básicas, y es la que tendremos en cuenta durante el tutorial, es la replicación de nuestro servidor de aplicaciones. De esta manera, si uno de estos servidores cae, otro tomará su lugar para seguir dando servicio a nuestro sistema.

A este concepto de tener una serie de componentes disjuntos que, desde el exterior, parezcan comportarse como si fueran uno único se le denomina clustering.

Sin embargo, ahora se nos presenta un nuevo problema: si tenemos n instancias de nuestro servidor replicado, ¿cómo sabemos a que dirección o puerto debemos mandar la petición? ¿Están todos los miembros o nodos del clúster disponibles? ¿De verdad debemos nosotros saber todas éstas cosas?

Otro de los componentes de un sistema de clustering es el denominado balanceador de carga o load balancer. Este componente es el que se expondrá a los posibles clientes del sistema, con una única dirección IP y puerto sobre el que escuchar. Él será el encargado de mantener la cuenta de los nodos sanos y los no disponibles, redirigiendo el tráfico a unos u otros.

En este tutorial, utilizaremos Apache Tomcat como servidor de aplicaciones (en realidad se trata de un servidor de servlets), una aplicación JSP de prueba y para el balanceo de carga usaremos Apache Web Server junto con la extensión mod_jk.

4. Instalación de Apache Tomcat, Apache Web Server (httpd) y mod_jk

En Ubuntu Server 14.04, tenemos disponible el gestor de paquetes apt-get, por lo que lo usaremos para hacer la instalación de casi todos los componentes, por su facilidad de uso y delegación de responsabilidades que nos ofrece. En determinados casos realizaremos la descarga manual del software por temas de claridad.

Apache Tomcat

Vamos a la página de descarga de Apache Tomcat 8 y nos descargamos el fichero comprimido con el core del servidor.

Abrimos una terminal y descomprimimos y copiamos el contenido del fichero comprimido a donde deseemos, en nuestro caso en /usr/share/tomcat:

tar zxvf apache-tomcat-<version>.tar.gz
mv apache-tomcat-<version> /usr/share/tomcat

Vamos a arrancar el servidor Tomcat para probar que todo ha salido bien:

cd /usr/share/tomcat
bin/startup.sh

Deberíamos ver la siguiente salida por consola:

Using CATALINA_BASE:   /usr/share/tomcat
Using CATALINA_HOME:   /usr/share/tomcat
Using CATALINA_TMPDIR: /usr/share/tomcat/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar
Tomcat started.

No solo eso, si desde un navegador accedemos a localhost:8080 veremos como Tomcat nos sirve su gestor.

new-tomcat

Paremos ahora la ejecución del servidor, ya que realizaremos una serie de cambios en su configuración:

bin/shutdown.sh

Apache Web Server (httpd)

La forma más sencilla de instalar Apache en Ubuntu Server es hacer uso del gestor de paquetes APT:

apt-get install apache2

Iniciemos el servidor Apache:

/etc/init.d/apache2 start    

Si ahora accedemos a localhost:80, veremos la página de bienvenida de Apache HTTPD.

new-apache

Paremos el servidor:

/etc/init.d/apache2 stop

Conexión mod_jk

Para poder realizar el balanceo de carga, el servidor Apache necesita una conexión específica. Entre las opciones disponibles tenemos las extensiones mod_jk y mod_proxy.

En este tutorial usaremos mod_jk, ya que aunque su uso sea ligeramente más complicado (mod_proxy viene dentro del paquete de Apache HTTP desde la versión 2.2) nos permite tener un control mucho más amplio del balanceo de carga.

La instalación es muy sencilla:

apt-get install libapache2-mod-jk

Comprobamos que se ha instalado correctamente:

ls /usr/lib/apache2/modules/mod_jk.so

Al instalar la extensión, se nos generarn una serie de ficheros que serán los que debemos modificar (lo veremos en su apartado correspondiente):

  • /etc/apache2/mods-enabled/jk.conf: Fichero de configuración de la extensión donde podremos definir los parámetros generales de comportamiento.
  • /etc/apache2/mods-enabled/jk.load: Apunta al servicio instalado para cargarlo cuando se arranca el Apache.
  • /etc/libapache2-mod-jk/workers.properties: Especificación de los nodos del clúster, comportamiento y rutas permitidas.
  • /var/log/apache2/mod_jk.log: Log de ejecución de los eventos de mod_jk.

5. Múltiples instancias de Tomcat

Debido al diseño modular y segregado de la implementación de Apache Tomcat, es posible tener en una misma máquina física múltiples instancias con su propio espacio de aplicaciones y configuraciones.

En el directorio donde tengamos instalado el Tomcat (en el caso de este tutorial, y como hemos visto, en /usr/share/tomcat) tendremos las siguientes carpetas:

  • bin: ejecutables y scripts del Tomcat.
  • conf: ficheros de configuración como los nombres de servidor, puertos para cada tipo de servicio, etc.
  • logs: logs de ejecución del servidor.
  • temp: los ficheros temporales que necesite el Tomcat se crearán aquí.
  • webapps: aquí irán todas las aplicaciones desplegadas en el servidor.
  • work: Tomcat escribirá aquí todo lo que necesite durante la ejecución, como el código generado de los servlets JSP o las sesiones serializadas durante el reinicio del servidor.

Vamos a crear un directorio donde descansarán los ficheros necesarios para cada instancia de Tomcat:

mkdir /usr/tmp/tomcat-instances
cd /usr/tmp/tomcat-instances

Los directorios necesarios para ejecutar una instancia independiente son conf, temp, webapps y logs. Los copiaremos del directorio base de Tomcat a una nueva carpeta que denominaremos tomcat-instance1:

mkdir tomcat-instance1
cp -R /usr/share/tomcat/conf /usr/share/tomcat/temp /usr/share/tomcat/webapps /usr/share/tomcat/logs tomcat-instance1

En el archivo de configuración conf/server.xml se especifican los puertos de cada conexión que usará la instancia. Cada una de las instancias de Tomcat debe tener los siguientes puertos diferentes a todos los demás, como mínimo, para poder funcionar:

  • Shutdown port: Puerto sobre el que se quedar esperando para la señal de apagado.
  • HTTP port: Puerto que entiende HTTP. Es el que se expone al cliente.
  • AJP port: Puerto sobre el que se realizará la comunicación entre componentes para mod_jk mediante el protocolo AJP.

Así, si abrimos el fichero de configuración del servidor de la primera instancia, los puertos que debemos modificar son los siguientes:

tomcat-instance1/conf/server.xml
[...]
<Server port="8005" shutdown="SHUTDOWN">
[...]
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
[...]
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
[...]

Con una herramienta como vim o nano modificaremos estos valores para que no entre en conflictos con otras instancias:

  • Shutdown: 6005
  • HTTP: 6080
  • AJP: 6009

Con esto ya tendríamos preparada la primera instancia de independiente de Tomcat. Repliquemos este árbol de ficheros en una nueva instancia tomcat-instance2:

cp -R tomcat-instance1 tomcat-instance2

A esta nueva instancia le asignaremos los siguientes puertos:

  • Shutdown: 7005
  • HTTP: 7080
  • AJP: 7009

Lo único que nos queda por hacer para tener la configuración preparada de las instancias de Tomcat son los scripts de arranque y apagado de cada una de ellas. Tomcat utiliza 5 variables de entorno a la hora de arrancar: CATALINA_HOME, CATALINA_BASE, CATALINA_TMPDIR, JRE_HOME/JAVA_HOME y CLASSPATH.

De todas ellas, las que tendremos que tener en cuenta para el clustering son:

  • CATALINA_HOME: Apunta a la ruta donde tengamos instalado el Tomcat. Desde aquí se puede acceder a las carpetas bin y lib. Se le da valor de forma automática.
  • CATALINA_BASE: Resulta un complemento de CATALINA_HOME que apunta a las carpetas conf y webapps. Ésta variable es la que deberemos modificar dinámicamente para iniciar una u otra instancia.

Para hacernos las cosas un poco más fciles crearemos una serie de scripts de arranque y apagado de las instancias en bash. Éstos scripts deberan residir en la carpeta base tomcat-instances.

Empezemos con el script de arranque:

startup-instance.sh
#!/bin/bash

NUM_OF_TOMCATS=2

# cambio de directorio al nativo de Tomcat
cd /usr/share/tomcat/bin

if [ $# -eq 0 ]; then
    echo "No args"
else
    if [ $1 = "all" ]; then
        for i in `seq 1 $NUM_OF_TOMCATS`; do
            export CATALINA_BASE=/usr/tmp/tomcat-instances/tomcat-instance$i
            ./startup.sh
        done
    else
        export CATALINA_BASE=/usr/tmp/tomcat-instances/tomcat-instance$1
        ./startup.sh
    fi
fi

Como se puede observar, recibe un único argumento de entrada (el resto, si los hubiera, son ignorados) que determina el número de instancia que queremos levantar. Podemos utilizar la cadena «all» para levantar todas las instancias.

Hay que acordarse siempre de darle permisos de ejecución al script. Si todo ha ido bien, al ejecutar este script, tendremos dos instancias separadas del Tomcat, una sirviendo en el puerto 6080 y otra en el 7080:

chmod +x startup-instance.sh
./startup-instance.sh all

multiple-tomcats

El script de apagado es análogo al de arranque:

shutdown-instance.sh
#!/bin/bash

NUM_OF_TOMCATS=2

# cambio de directorio al nativo de Tomcat
cd /usr/share/tomcat/bin

if [ $# -eq 0 ]; then
        echo "No args"
else
    if [ $1 = "all" ]; then
        for i in `seq 1 $NUM_OF_TOMCATS`; do
            export CATALINA_BASE=/usr/tmp/tomcat-instances/tomcat-instance$i
            ./shutdown.sh
        done
    else
        export CATALINA_BASE=/usr/tmp/tomcat-instances/tomcat-instance$1
        ./shutdown.sh
    fi
fi
chmod +x shutdown-instance.sh

Como comodidad adicional, podemos crear un script de reinicio de instancias que llame a los dos anteriores:

restart-instance.sh
#!/bin/bash

if [ $# -eq 0 ]; then
    echo "No args"
else
    ./shutdown-instance.sh $1
    ./startup-instance.sh $1
fi
chmod +x restart-instance.sh

Si ejecutamos este script de reinicio y no tenemos instancias que apagar nos saltará una excepción del Tomcat, pero proseguirá su ejecución sin problemas. Por ejemplo:

Using CATALINA_BASE:   /usr/tmp/tomcat-instances/tomcat-instance2
Using CATALINA_HOME:   /usr/share/tomcat
Using CATALINA_TMPDIR: /usr/tmp/tomcat-instances/tomcat-instance2/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar
ago 21, 2015 10:19:50 PM org.apache.catalina.startup.Catalina stopServer
GRAVE: Could not contact localhost:7005. Tomcat may not be running.
ago 21, 2015 10:19:50 PM org.apache.catalina.startup.Catalina stopServer
GRAVE: Catalina.stop: 
java.net.ConnectException: Conexión rehusada
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:579)
        at java.net.Socket.connect(Socket.java:528)
        at java.net.Socket.<init>(Socket.java:425)
        at java.net.Socket.<init>(Socket.java:208)
        at org.apache.catalina.startup.Catalina.stopServer(Catalina.java:450)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.apache.catalina.startup.Bootstrap.stopServer(Bootstrap.java:400)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:487)

6. Balanceo de carga con mod_jk

El conector mod_jk permite la comunicación entre servidores Tomcat y Apache haciendo uso del protocolo propio Apache JServ Protocol (AJP).

Dependiendo de la instalación que hayamos hecho tanto de Apache como de mod_jk, los archivos de configuración del balanceador variarán en localización, pero no en contenido. Nosotros usaremos el árbol de directorios que nos crea automáticamente APT al instalar ambos.

La configuración más sencilla de un balanceador está compuesta de los siguientes elementos:

  • workers.properties: fichero de especificación de cada nodo trabajador del clúster, así como el comportamiento del balanceador (si es definido).
  • jk.conf: configuración del propio conector mod_jk (localización de los ficheros de workers, logs, intetrvalo de polling, etc…)

Empezemos por configurar el fichero /etc/libapache2-mod-jk/workers.properties:

workers.properties
worker.list=balancer,stat

worker.tomcat1.type=ajp13
worker.tomcat1.port=6009
worker.tomcat1.host=localhost

worker.tomcat2.type=ajp13
worker.tomcat2.port=7009
worker.tomcat2.host=localhost

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

worker.stat.type=status

Hemos definido dos nodos denominados tomcat1 y tomcat2, uno por cada instancia del Tomcat. La información de cada nodo es la que definimos en su momento en cada uno de los ficheros server.xml de las instancias en el apartado del conector AJP, por ello es que el tipo sea «ajp13» o protocolo AJP v1.3.

Definimos, además, dos trabajadores del mod_jk, uno que se encargará de proporcionarnos una consola de monitorización de la extensión (trabajador «stat») y otro que nos sirva del propio balanceador (trabajador «balancer»). A este último es al que le hemos añadido los dos nodos Tomcat como unidades de proceso.

El siguiente paso sería definir la configuración del Apache para que cargue la extensión mod_jk y sepa donde encontrar el fichero de workers, los configure según la ruta de entrada, etc. Ambos ficheros se encuentran en /etc/apache2/mods-enabled si lo hemos instalado mediante APT o en $APACHE_HOME/httpd.conf si lo hemos hecho de forma manual.

jk.load
# Carga el módulo mod_jk y lo denomina jk_module
LoadModule jk_module /usr/lib/apache2/modules/mod_jk.so
jk.conf
<IfModule jk_module>
    
    # Localización del fichero de trabajadores
    JkWorkersFile /etc/libapache2-mod-jk/workers.properties
    
    # Localización donde escribir los logs de mod_jk
    JkLogFile /var/log/apache2/mod_jk.log
    
    # Nivel de criticidad de los logs (trace,debug,info,warn,error)
    JkLogLevel debug
    
    # Fichero de memoria compartida
    JkShmFile /var/log/apache2/jk-runtime-status
    
    # Cada intervalo realiza tareas internas como
    # prueba de conexiones ociosas, redimensionamiento de
    # la reserva de conexiones, etc...
    JkWatchdogInterval 60
    
    # Definimos un nuevo virtual host en el puerto 80
    # (el de HTTP de Apache) especificando la ruta de la que
    # se encargar el mod_jk
    <VirtualHost *:80>
        ServerName  localhost
        ServerAdmin localhost
        JkMount /status stat
        JkMount /* balancer
    </VirtualHost>

</IfModule>

Guardamos los cambios en ambos ficheros y reiniciamos el servidor Apache para que sean efectivos:

apache2ctl restart

Si todo ha ido bien, podemos acceder al fichero /var/log/apache2/mod_jk.log para comprobar la creación y registro del balanceador y sus nodos, ya que hemos definido el nivel de detalle de dicho log a «debug».

Si accedemos ahora a localhost:80/status nos encontramos con la herramienta de monitorización del balanceador de carga. Desde aquí podremos, además de comprobar el funcionamiento de mod_jk en tiempo real, modificar ciertos valores en caliente, como deshabilitar/eliminar nodos, añadir peso a un nodo o cambiar el número de reintentos antes de dar por caído uno de ellos, por ejemplo.

jk-status

7. Despliegue de la aplicación de prueba

Si ahora accedemos a localhost:80/ será el balanceador el encargado de redirigirnos a uno de los Tomcat levantados:

jk-manager

Para que sea un poco más visual, vamos a instalar una pequeña aplicación web de prueba que nos muestre de forma sencilla cual de las instancias de Tomcat nos está sirviendo el balanceador. En este enlance se puede descargar el WAR ya empaquetado.

La aplicación es muy sencilla, con un único fichero JPA que muestra un mensaje de bienvenida y la ruta donde se encuentra:

<html>
<body>

    <h2>Hello World! from</h2>
    <h3><%=getServletContext().getRealPath("/")%></h3>
    
</body>
</html>

El despligue de la aplicación es terriblemente sencilla. Lo único que deberemos hacer es copiar pruebatomcat.war en la carpeta webapps de cada una de las instancias de Tomcat y, por si acaso, reiniciar ambas.

Ahora, si con un navegador accedemos a localhost:80/pruebatomcat observaremos como mod_jk nos muestra una de las dos instancias de Tomcat. Si refrescamos varias veces la página, veremos como dichos nodos se alternan, ya que, por defecto, la política que sigue el balanceador es Round Robin.

jk-balancer-1

jk-balancer-2

8. Conclusiones

La extensión mod_jk para la intercomunicación entre servidores Apache y Tomcat nos permite una forma sencilla de montar arquitecturas de disponibilidad no demasiado complicadas. Además, tiene la potencia de hacer sistemas mucho más complejos, debido a su capacidad de configuración.

9. Referencias

4 COMENTARIOS

  1. Hola, podrias explicar como se implementa la replicacion de sesiones para que en ambos servidores ocurran las mismas transacciones de manera que si uno se cae el otro pueda reemplazarlo?
    Desde ya gracias.

    • Hola Mario.

      Has logrado algo, me encuentro en la misma situación que tu… Tengo dos jboss corriendo en cluster y mod_jk por encima pero no consigo la persistencia de sesiones.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad