Configuración UDP y TCP del balanceador mod_cluster

por | enero 9, 2019

Ya habíamos visto anteriormente las distintas maneras de instalar mod_cluster en nuestro Apache. Ahora toca configurarlo para balancear carga con los servidores backend de Jboss.

Como vimos durante la instalación, mod_cluster se sirve de diversos módulos que cumplen una función específica:

LoadModule cluster_slotmem_module modules/mod_cluster_slotmem.so
LoadModule manager_module modules/mod_manager.so
LoadModule proxy_cluster_module modules/mod_proxy_cluster.so
LoadModule advertise_module modules/mod_advertise.so

Cada uno tiene estas funciones:

  • Gestión de memoria y caché con mod_cluster_slotmem
  • Canal conector (mod_proxy_cluster): como su nombre indica es el módulo que hace de proxy y se encarga de dirigir el tráfico del frontend al backend utilizando protocolo AJP, HTTP o HTTPS.
  • Canal de administración (mod_manager): por defecto escucha en el puerto 6666 y mediante el protocolo MCMP se encarga de mantener la lógica del balanceador entre el fronted y el backend: nodos dados de alta en cada balanceador, políticas balanceo por round robin o carga, activación/desactivación de nodos, etc…
  • Canal advertise (mod_advertise): recibe las comunicaciones de los nodos backend para unirse al balanceador (discovery)

Podemos configurar mod_cluster por TCP o UDP. Esto afecta principalmente al proceso de discovery de nodos del backend al frontend.

En ambos casos es preferible aislar en un VirtualHost independiente la parte de administración que utiliza el protocolo MCMP para gestiones internas del balanceador. De esta manera en los VirtualHosts de aplicación sólo nos tenemos que preocupar por dar de alta balanceadores que actuarán de proxy para las peticiones a nuestras aplicaciones.

Para el artículo he utilizado un Jboss EAP 7.1 y Apache 2.4

Configuración de mod_cluster con protocolo UDP

Frontend Apache

Creamos dos VirtualHost, uno para la administración escuchando en el puerto 6666 y otro para nuestra aplicación por el puerto 80.

VirtualHost de administración: limitamos el acceso por IP y especificamos el fichero de cache utilizado por mod_cluster:

MemManagerFile cache/mod_cluster

Listen 192.168.1.2:6666
<VirtualHost 192.168.1.2:6666>          

    <Location />
        Require ip 192.168.1
    </Location>

    # Configuration
    ServerAdvertise On
    AdvertiseFrequency 5
    AdvertiseGroup 224.0.1.105:23364
    EnableMCPMReceive On

    <Location /mcm>
        SetHandler mod_cluster-manager
        Require ip 192.168.1
   </Location>

   CustomLog /var/log/apache2/mod_cluster.log common

</VirtualHost>

VirtualHost de aplicación: le damos un nombre al balanceador con la directiva ManagerBalancerName y con ProxyPass le pasamos las peticiones a ese balanceador.

Listen 192.168.1.2:80              
<VirtualHost 192.168.1.2:80>

    ServerName   app1.jota.com
    ServerAlias  app1
    DocumentRoot "/srv/app1/docs"

    <Directory />
        Require all granted
    </Directory>

    ManagerBalancerName lb_app1
    ProxyPass / balancer://lb_app1/ stickysession=JSESSIONID|jsessionid nofailover=On

    ErrorLog          /var/log/apache2/app1/error.log
    CustomLog         /var/log/apache2/app1/access.log common

</VirtualHost>

Backend Jboss

Tendremos que iniciar Jboss con un perfil ha o full-ha ya que son los que contienen por defecto el subsistema mod-cluster. En el subsistema mod-cluster añadimos la siguiente variable balancer que indicará el nombre del balanceador de mod_cluster que anteriormente especificamos en el Apache con la directiva ManagerBalancerName

<subsystem xmlns="urn:jboss:domain:modcluster:3.0">
    <mod-cluster-config advertise-socket="modcluster" balancer="${modcluster.balancer}" connector="ajp">
        <dynamic-load-provider>
            <load-metric type="cpu"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

Después al arrancar con Jboss indicamos el profile y el valor de la variable:

./bin/standalone.sh -c standalone-ha.xml -Dmodcluster.balancer=lb_app1 -Djboss.bind.address.management=192.168.1.2 -Djboss.bind.address=192.168.1.2

Si visitamos nuestra URL deberíamos acceder a nuestra aplicación, por ejemplo si tuviera el contexto /example:

# Backend
http://192.168.1.2:8080/example

# Frotend (proxy a Jboss)
http://192.168.1.2/example

Si accedemos a la URL de administración http://192.168.1.2:6666/mcm podemos ver información del balanceador:

Configuración de mod_cluster con protocolo TCP

Frontend Apache

En este caso tenemos que deshabilitar ServerAdvertise ya que no utilizaremos un discovery por multicast UDP. Las directivas AdvertiseFrequency y AdvertiseGroup no vamos a utilizarlas así que o las comentamos o las eliminamos:

MemManagerFile cache/mod_cluster

Listen 192.168.1.2:6666
<VirtualHost 192.168.1.2:6666>        

    <Location />
        Require ip 192.168.1
    </Location>

    # Configuration
    ServerAdvertise Off
    #AdvertiseFrequency 5
    #AdvertiseGroup 224.0.1.105:23364
    EnableMCPMReceive On

    <Location /mcm>
        SetHandler mod_cluster-manager
        Require ip 192.168.1
   </Location>

   CustomLog /var/log/apache2/mod_cluster.log common

</VirtualHost>

En el VirtualHost para la aplicación no hace falta cambiar nada, simplemente dejamos nuestro balanceador dado de alta con la directiva ManagerBalancerName como vimos anteriormente.

Backend Jboss

En Jboss EAP 7.1 tenemos que crear un socket-binding por cada Apache que va a hacer de proxy. En nuestro caso:

<outbound-socket-binding name="apache1">
    <remote-destination host="192.168.1.2" port="6666"/>
</outbound-socket-binding>

En el subsistema mod-cluster hay que introducir el atributo proxies pasándole el socket o sockets de proxy:

<subsystem xmlns="urn:jboss:domain:modcluster:3.0">
    <mod-cluster-config advertise-socket="modcluster" balancer="${modcluster.balancer}" proxies="apache1" connector="ajp">         
        <dynamic-load-provider>
            <load-metric type="cpu"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

El arranque de Jboss es idéntico que con UDP:

./bin/standalone.sh -c standalone-ha.xml -Dmodcluster.balancer=lb_app1 -Djboss.bind.address.management=192.168.1.2 -Djboss.bind.address=192.168.1.2

Si tuviéramos dos Apache frontales haciendo de proxy, bastaría con crear otro socket-binding:

<outbound-socket-binding name="apache1">
    <remote-destination host="192.168.1.2" port="6666"/>
</outbound-socket-binding>
<outbound-socket-binding name="apache2">
    <remote-destination host="192.168.1.3" port="6666"/>
</outbound-socket-binding>

Y luego en el subsistema pasamos la lista sin comas:

<subsystem xmlns="urn:jboss:domain:modcluster:3.0">
    <mod-cluster-config advertise-socket="modcluster" balancer="${modcluster.balancer}" proxies="apache1 apache2" connector="ajp">      
        <dynamic-load-provider>
            <load-metric type="cpu"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

La creación del socket-binding no era necesaria en Jboss EAP 6.4, donde directamente le pasábamos a la variable proxy-list la lista de proxies como valor, por ejemplo en caso de tener 2:

<subsystem xmlns="urn:jboss:domain:modcluster:1.2">
    <mod-cluster-config advertise-socket="modcluster" advertise="false" balancer="${jboss.modcluster.balancer}" proxy-list="192.168.1.2:6666,192.168.1.3:6666" connector="ajp">
        <dynamic-load-provider>
            <load-metric type="busyness"/>
            <load-metric type="mem" weight="2" capacity="1"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

Logging

Frontend Apache

En el log mod_cluster.log del VirtualHost de mod_cluster (puerto 6666) podremos ver los distintos métodos HTTP utilizados por mod_cluster, desde el discovery de aplicaciones, al heartbeat «status» cada 10 segundos, etc…:

192.168.1.2 - - [30/Dec/2018:13:55:56 +0100] "INFO / HTTP/1.1" 200 226
192.168.1.2 - - [30/Dec/2018:13:55:56 +0100] "CONFIG / HTTP/1.1" 200 224
192.168.1.2 - - [30/Dec/2018:13:55:56 +0100] "STATUS / HTTP/1.1" 200 316
192.168.1.2 - - [30/Dec/2018:13:55:56 +0100] "ENABLE-APP / HTTP/1.1" 200 224
192.168.1.2 - - [30/Dec/2018:13:56:06 +0100] "STATUS / HTTP/1.1" 200 317
192.168.1.2 - - [30/Dec/2018:13:56:16 +0100] "STATUS / HTTP/1.1" 200 317

Backend Jboss

En Jboss al arrancar veremos:

9:38:16,983 INFO  [org.jboss.modcluster] (ServerService Thread Pool -- 54) MODCLUSTER000001: Initializing mod_cluster version 1.2.11.Final-redhat-1
19:38:16,996 INFO  [org.jboss.modcluster] (ServerService Thread Pool -- 54) MODCLUSTER000032: Listening to proxy advertisements on /224.0.1.105:23364

Si conecta bien con el balanceador de Apache veremos:

18:55:10,451 INFO  [org.jboss.modcluster] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) MODCLUSTER000011: jboss.web will use 37413346-7a3f-3689-8bf7-d6ce78172e60 as jvm-route

El subsistema mod_cluster envía periódicamente un mensaje de STATUS al Apache. Si el frontal no está disponible o no consigue conectar veremos mensajes de error de este tipo:

19:41:49,720 ERROR [org.jboss.modcluster] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) MODCLUSTER000043: Failed to send STATUS to 192.168.1.2/192.168.1.2:6666: java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:210) [rt.jar:1.8.0_181]
	at java.net.SocketInputStream.read(SocketInputStream.java:141) [rt.jar:1.8.0_181]
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) [rt.jar:1.8.0_181]
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) [rt.jar:1.8.0_181]
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) [rt.jar:1.8.0_181]
	at java.io.InputStreamReader.read(InputStreamReader.java:184) [rt.jar:1.8.0_181]
	at java.io.BufferedReader.fill(BufferedReader.java:161) [rt.jar:1.8.0_181]
	at java.io.BufferedReader.readLine(BufferedReader.java:324) [rt.jar:1.8.0_181]
	at java.io.BufferedReader.readLine(BufferedReader.java:389) [rt.jar:1.8.0_181]
	at org.jboss.modcluster.mcmp.impl.DefaultMCMPHandler.sendRequest(DefaultMCMPHandler.java:514)
	at org.jboss.modcluster.mcmp.impl.DefaultMCMPHandler.sendRequest(DefaultMCMPHandler.java:600)
	at org.jboss.modcluster.mcmp.impl.DefaultMCMPHandler.sendRequest(DefaultMCMPHandler.java:414)
	at org.jboss.modcluster.ModClusterService.status(ModClusterService.java:468)
	at org.jboss.modcluster.container.catalina.CatalinaEventHandlerAdapter.lifecycleEvent(CatalinaEventHandlerAdapter.java:249)
	at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:115) [jbossweb-7.5.7.Final-redhat-1.jar:7.5.7.Final-redhat-1]
	at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1323) [jbossweb-7.5.7.Final-redhat-1.jar:7.5.7.Final-redhat-1]
	at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1588) [jbossweb-7.5.7.Final-redhat-1.jar:7.5.7.Final-redhat-1]
	at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1574) [jbossweb-7.5.7.Final-redhat-1.jar:7.5.7.Final-redhat-1]
	at java.lang.Thread.run(Thread.java:748) [rt.jar:1.8.0_181]

Hasta aquí llegamos. Como siempre, los escenarios pueden variar. Sin ir más lejos si tenemos un dominio de Jboss la configuración habrá que hacerla a nivel de profile de server group. Conviene por tanto echar un vistazo a la documentación oficial del proyecto y estar al tanto de las últimas novedades de Jboss. Aunque la configuración que subyace de una versión a otra suele ser la misma, pueden cambiar los nombres de los parámetros, atributos, etc… como hemos visto en la configuración por TCP entre Jboss EAP 6.4 y Jboss EAP 7.1