Load balancing avec JBoss et Apache 2

noel perez

Les applications réparties étant de plus en plus diffusées auprès des partenaires, des clients et des collaborateurs, elles se doivent de tenir des charges toujours plus importantes. Le seul matériel ne peut répondre à un coût raisonnable. C'est là qu'intervient le « load balancing » en répartissant la charge entre plusieurs serveurs plus modestes. Ce tutoriel doit vous amener à entrevoir ce que ces solutions peuvent vous apporter dans un cas très concret.

Commentez ♪

- Titre : Load balancing avec JBoss et Apache 2

- Auteur : noel perez

- Parution : 23 septembre 2010

- Mise à jour : 11 octobre 2010

- Licence : Copyright ® 2010 noel perez. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Préparation

- II-A. Installation JBoss

- II-B. Installation d'Apache

- III. Configuration du load balancer avec le module mod_jk

- IV. Continuité de service

- V. Optimisation annexe

- VI. Conclusion

- VII. Remerciement

I - Introduction

Suite à la formation JBoss plusieurs voies d'approfondissement s'offrent à moi, mais il y en a une qui me tient plus à cœur : le load balancing. Pourquoi cela? Après tout en tant qu'Architecte logiciel certains considèrent que notre problématique est de savoir que cela existe. La technique restant le domaine de prédilections des administrateurs et architectes SI. Personnellement ce n'est pas ma conception. Même si je ne prétends pas avoir les mêmes compétences, j'aime savoir de quoi je parle.

Tout d'abord une petite définition du load balancing : le « load balancing » ou « répartition de charge » est une technique utilisée en informatique pour distribuer un travail entre plusieurs processus, ordinateurs, disques ou autres ressources (source Wikipédia).

Le load balancing peut se faire de différentes manières :

- matériel : solution permettant de résister à la plus forte charge. Elle aura des problèmes en cas de répartition sur un protocole haut niveau utilisant les sessions. Cette solution a un coût élevé ;

- logiciel : cette solution permet de gérer facilement des répartitions de charge avec respect de session et a un coût faible. Plus lent que le hard, elle dépend du serveur qui l'héberge pour définir ses performances.

Pour ce tutoriel, nous allons nous intéresser uniquement à la partie logicielle qui est plus simple à mettre en œuvre. Nous utiliserons pour ce faire la configuration la plus courante dans le monde Java :

- deux serveurs d'applications JBoss ( avec des configurations différentes, mais une application commune. Habituellement les deux serveurs seront totalement iso ) ;

- un serveur Apache 2 sur lequel nous activerons le module mod_jk.

II - Préparation

Je vous conseille de commencer par créer un répertoire où vous installerez les différents serveurs nécessaires.

II-A - Installation JBoss

Pour ce tutoriel, j'ai utilisé une version 6.0M4 de JBoss que vous pouvez télécharger sur le site officiel de JBoss http://jboss.org/jbossas/downloads/ (La version actuellement la plus diffusée étant la 5.1, vous trouverez dans des encadrés les différences par rapport à la version 6 actuellement disponible).

Une fois l'archive obtenue, décompilez-la dans le répertoire précédemment créé. Vous devez alors avoir un répertoire « jboss-6.0.0.xxxxxxxx-M4 » qui contient un répertoire bin. Dans ce répertoire bin, double cliquez sur le fichier run.bat sous Windows (sous unix, lancez le run.sh).

Si tout se passe bien, vous ne devez pas avoir d'erreur dans les traces et la dernière ligne doit se finir par :

Started in 58s:376ms

Si ce n'est pas le cas, reportez-vous à la documentation très complète de JBoss(1).

Une fois cette étape franchie, il nous reste à dupliquer l'installation pour avoir un deuxième serveur JBoss.

Pour ce faire, arrêtez le serveur en cours ( fermez la fenêtre de commande ), puis recopiez entièrement le répertoire « jboss-6.0.0.xxxxxxxx-M4 » en un répertoire « jboss-6.0.0.xxxxxxxx-M4 -server2 ».

Si maintenant vous lancez les deux serveurs, vous allez avoir une erreur du style :

java.lang.Exception: Port 8083 already in use

Cette erreur est normale ! Effectivement, vous lancez en réalité deux serveurs avec exactement la même configuration. Il va nous falloir changer les ports d'écoutes sur le deuxième serveur. Pour faciliter l'opération et éviter toute erreur nous ajouterons 100 à tous les numéros de ports trouvés.

À cette fin, dans « jboss-6.0.0.xxxxxxxx-M4-server2/server/deploy/(2) » supprimez tous les répertoires sauf «jbossweb-standalone». Renommez «jbossweb-standalone» en « default ». Suite à cela, cherchez le fichier « jboss-6.0.0.xxxxxxxx-M4-server2/server/default/conf/bindingservice.beans/META-INF/bindings-jboss-beans.xml » et incrémentez tous les numéros de port de 100.

Relancez les deux serveurs et ouvrez un navigateur. Vous avez maintenant accès aux URL :

http://localhost:8080

http://localhost:8180

En version 5.x de JBoss, il faudra garder et renommer le server «  web  » en «  default  », puis incrémenter tous les numéros de port dans le fichier «  jboss-5.1.0.GA-server2/server/default/deploy/jbossweb.sar/server.xml  »

Accédez, via la console JMX, à chacune des URL précédentes, afin de vérifier le bon fonctionnement des serveurs.

Pour accéder aux consoles avec la version 5.X de JBoss utilisez le compte admin / admin.

Nous avons donc maintenant deux serveurs d'applications actifs. Il reste à mettre en place notre load balancer, dans notre cas un serveur web.

II-B - Installation d'Apache

Apache s'avèrera plus simple à installer vu que nous n'avons besoin que d'un serveur de ce type. Pour des problèmes de compatibilité avec le module mod_jk que nous allons utiliser, je vous conseille de télécharger la dernière version disponible d'Apache 2.0 ( dans mon cas la 2.0.63 ) sur le site d'Apache http://httpd.apache.org/download.cgi.

Exécutez l'installation et vérifiez en lançant le serveur que tout s'est bien passé.

Remarque 1 : la relance du serveur apache se fait :

Dans votre navigateur, accédez alors à l'URL , http://localhost/ , vous devez avoir un écran comme celui-ci :

Remarque2 : attention sous Windows! Il vous faudra désactiver le serveur IIS qui utilise le port 80 sinon Apache ne pourra se lancer correctement. Pour ce faire allez dans « menu démarrer>panneau de configuration>outils d'administration » et lancez le « gestionnaire de services Internet » ; là, vous aurez tout le loisir d'arrêter complètement IIS. Relancez à nouveau le serveur Apache et tout devrait être bon.

Nous avons maintenant trois serveurs indépendants et nous allons pouvoir entrer dans le cœur du sujet en commençant par une simple répartition de charge.

III - Configuration du load balancer avec le module mod_jk

Nous allons commencer par ajouter le module nécessaire à Apache afin de dialoguer avec les serveurs d'applications. Nous concernant, il existe plusieurs modules pour se connecter à JBoss :

- mod_proxy : module officiel livré avec Apache, il existe depuis 1996 et a été entièrement réécrit pour Apache 2.2 afin de concurrencer les autres modules ;

- mod_cluster : fourni par JBoss, c'est une solution jeune, mais très prometteuse. Configurable dynamiquement, tenant compte de la charge des serveurs et capable de gérer l'arrêt des applications en douceur ;

- mod_jk : longtemps seul module professionnel, il est encore le module recommandé avec tomcat. Comme mod_cluster, il permet la modification à chaud par page web de la configuration.

Nous utiliserons mod_jk qui est le plus répandu. Pour ce faire, rendez-vous sur http://tomcat.apache.org/download-connectors.cgi et téléchargez la version binaire qui correspond à votre plateforme. Copiez le fichier « mod_jk***.so » dans le répertoire « apache2/modules » et renommez-le en « mod_jk.so ».

Ici nous ne toucherons pas aux serveurs d'applications ceux-ci étant considérés comme fonctionnels et à l'écoute de connexions avec le protocole AJP13.

Nous allons commencer par déclarer ces serveurs d'applications à notre load balancer (Apache 2). Pour ce faire, rendez-vous dans « apache2/conf/ » et créez un fichier « workers.properties » dans lequel nous allons inscrire les serveurs de la manière suivante :

Workers.properties

# Define 1 real worker using ajp13
worker.list=loadbalancer,status
# Set properties for worker1 (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
worker.worker1.lbfactor=1
worker.worker1.connection_pool_size=10
# Set properties for worker2 (ajp13)
worker.worker2.type=ajp13
worker.worker2.host=localhost
worker.worker2.port=8109
worker.worker2.lbfactor=1
worker.worker2.connection_pool_size=10
#fonctionnement de l'équilibrage de charge
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=worker1,worker2
worker.loadbalancer.sticky_session=True
worker.status.type=status

Si on analyse ce fichier, on peut voir plusieurs zones :

- une zone bleue qui nous permet de définir les workers (3) qui seront visibles de l'extérieur. Ici le seul à nous intéresser est « loadbalancer » ;

- en vert et rouge, nous voyons la définition de connexion avec les deux serveurs d'applications. On voit que les deux connexions sont de type AJP13 ( standard de communication Apache-Tomcat ). Les deux serveurs sont hébergés sur la machine d'où le localhost, par contre on peut voir que les ports de connexions sont différents.
Ici nous avons réparti de manière égale les requêtes entre les serveurs, car nous avons mis le même lbfactor. On voit aussi que l'on prévoit une dizaine de connexions par serveurs qui seront ouvertes au fur et à mesure ;

- en orange, enfin le vrai loadbalancer. On le reconnaît au « type=lb ». Ensuite on indique tous les serveurs à prendre en compte. Enfin la propriété « sticky_session » indique si le load balancing se fait avec affinité de session.

Remarque 3 : l'affinité de session indique que si une requête est retransmise sur le serveur 1 pour un client toutes les autres requêtes appartenant à la même session de ce client seront retransmises sur le serveur 1.

Il nous suffit maintenant de déclarer l'utilisation du module mod_jk à Apache ainsi que le fichier que nous venons de créer.

Pour cela nous allons modifier le fichier httpd.conf du même répertoire de la manière suivante :

Httpd.conf

…
LoadModule jk_module modules/mod_jk.so
…
# Where to find workers.properties
# Update this path to match your conf directory location (put workers.properties next to httpd.conf)
JkWorkersFile conf/workers.properties
# Where to put jk shared memory
# Update this path to match your local state directory or logs directory
JkShmFile logs/mod_jk.shm
# Where to put jk logs
# Update this path to match your logs directory location (put mod_jk.log next to access_log)
JkLogFile logs/mod_jk.log
# Set the jk log level [debug/error/info]
JkLogLevel info
# Select the timestamp log format
JkLogStampFormat « [%a %b %d %H:%M:%S %Y] »
JkMount /* loadbalancer
<IfModule worker.c>
StartServers2
MaxClients150
MinSpareThreads25
MaxSpareThreads75
ThreadsPerChild25
MaxRequestsPerChild0
</IfModule>

La ligne bleue permet d'ajouter mod_jk à la liste des modules chargés. En orange, nous indiquons le fichier de configuration à prendre en compte. En rouge, les lignes de configurations de sortie du module, à ne pas toucher.

La ligne verte nous intéresse plus, elle permet d'indiquer quelles URL nous redirigeons et vers qui.

Ici toutes les requêtes sont redirigées sur le worker loadbalancer. Elles seront donc, aux vues de notre configuration, réparties équitablement entre les deux serveurs.

Il vous reste un dernier fichier à ajouter dans le répertoire conf pour faire un lien entre le worker status et une URL. Cette page status permettra d'afficher l'état du connecteur mod_jk. Ceci n'est en rien obligatoire, mais conseillé en mode de développement/Préproduction.

Uriworkermap.properties

/status=status

À présent c'est le moment, relancez le serveur Apache pour prendre en compte cette nouvelle configuration (voir remarque 1).

Maintenant, vérifions le bon fonctionnement de notre configuration. Pour ce faire, ouvrez un navigateur et connectez vous sur http://localhost/, vous devez maintenant voir la page d'accueil de JBoss et non plus celle d'Apache. Entrez sur la console JMX comme au début et cliquez sur « JBoss.ws » à gauche puis sur « service=ServerConfig ». Dans la fenêtre ainsi apparue, vous voyez le port d'écoute du serveur : 8080 ou 8180.

Ouvrez alors une nouvelle fenêtre avec un autre navigateur ( si vous travaillez avec le même navigateur, vous aurez la même session et donc accès au même serveur grâce à l'affinité de session ). En reproduisant la même procédure, vous verrez que le serveur sera en écoute sur l'autre port.

IV - Continuité de service

Maintenant que nous sommes capables de répartir la charge d'un site entre plusieurs machines, on peut se demander ce qui se passerait si un des serveurs venait à ne plus fonctionner. Et bien horreur rien du tout ! La communication étant rompue une erreur parviendra au client ce qui vous en conviendrez n'est pas du meilleur effet.

Pour répondre à cela, tout a été prévu. On peut ainsi, au cas où Apache détecterai que le serveur auquel il tente d'accéder n'existe plus, lui dire de rediriger les requêtes vers un autre serveur.

Alors comment cela se passe-t-il ?

Et bien au moment où Apache reçoit une requête, il détermine le serveur qui doit répondre :
-  soit une connexion libre est ouverte vers celui-ci et la requête est lancée ;
-  soit aucune connexion n'est disponible, Apache va alors pinger la machine pour savoir si elle existe ou non. Si le ping échoue, Apache regarde s'il existe une directive de redirection. Si c'est le cas, il va tenter de communiquer avec le serveur de remplacement.

Dans le cas qui nous intéresse, le serveur 1 étant en panne, Apache essaie d'ouvrir une connexion sur le serveur 2. Comme la connexion peut être créée alors la requête est émise vers ce nouveau serveur.

Pour ce faire, nous allons ajouter une ligne au worker1 dans notre fichier workers.properties.

Workers.properties

…
# Set properties for worker1 (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
worker.worker1.lbfactor=1
worker.worker1.connection_pool_size=10
# Define preferred failover node for worker1
worker.worker1.redirect=worker2
…

Nous venons d'indiquer que si le serveur worker1 n'est plus accessible les requêtes sont redirigées sur le worker2.

Pour tester cette configuration, arrêtez le serveur1 et retentez d'accéder depuis les deux navigateurs. L'un des deux navigateurs va afficher une erreur comme suit.

Relancez Apache pour qu'il prenne en compte la nouvelle configuration et refaites la manipulation. Maintenant, si vous vérifiez dans la console JMX les deux navigateurs accèdent au même serveur.

Si nous nous contentons de cela, nous redirigeons toute la charge vers un serveur avec le risque qu'il ne puisse répondre à l'ensemble de la charge. Pour ce faire, on préfère ajouter un worker comme les autres, mais que l'on met en attente. Ainsi, il ne recevra de requêtes que si l'un des serveurs tombe.

Workers.properties

…
# Set properties for worker2 (ajp13)
worker.worker3.type=ajp13
worker.worker3.host=localhost
worker.worker3.port=8209
worker.worker3.lbfactor=1
worker.worker3.connection_pool_size=10
# Disable worker3 for all requests except failover
worker.worker3.activation=disabled
…

Ajoutez le worker3 dans la propriété worker.loadbalancer.balance_workers pour le rendre disponible et bien se rappeler qu'un worker disabled est un worker qui ne reçoit pas de requête directement ; ainsi si vous définissez uniquement deux workers et que l'un est disabled toutes les requêtes iront sur l'autre serveur.

Encore quelques optimisations et nous voici avec un site prêt à répondre à la charge d'une situation réelle.

V - Optimisation annexe

Tout à l'heure nous avions vu une zone d'optimisation des connexions entre Apache et JBoss, il est temps de mieux comprendre à quoi elle sert(4).

Httpd.conf

…
<IfModule worker.c>
StartServers2
MaxClients150
MinSpareThreads25
MaxSpareThreads75
ThreadsPerChild25
MaxRequestsPerChild0
</IfModule>

Cette zone permet de définir le nombre de requêtes qui peuvent être acceptées en parallèle ainsi que le nombre de requêtes pouvant être mises en attente. Ainsi nous indiquons que deux processus serveurs sont créés au démarrage d'Apache, il n'influe que peu, car Apache va au fur et à mesure des requêtes arrêter et lancer de nouveaux processus serveurs. On indique aussi que l'on pourra avoir au maximum 150 clients simultanément.

Remarque 4 : il faut normalement compter 20 Mo par client actif, on peut donc à partir de la mémoire libre définir la valeur de MaxClients à appliquer. Ainsi pour 1Go, on pourra accepter 50 clients parallèles.

Les valeurs MinSpareThreads et MaxSpareThreads permettent d'entretenir des processus serveur de secours en fonction du nombre de requêtes.

Remarque 5 : les valeurs MinSpareThreads et MaxSpareThreads doivent être en cohérence avec les valeurs présentes sur les serveurs d'applications.

ThreadsPerChild définit le nombre de threads au sein de chaque processus enfant.

La propriété MaxRequestPerChild est ici désactivée, elle permet de limiter le nombre de requêtes acceptées par processus serveur. Si ce maximum est atteint, le processus serveur est cloné puis arrêté.

VI - Conclusion

Il ne reste plus qu'à appliquer ce que nous avons vu sur un projet concret et se frotter au réglage plus fin du serveur. Mais là, je ne me fais pas de soucis la documentation existe en nombre, particulièrement pour JBoss et Apache.

VII - Remerciement

Je tiens à remercier mon entourage pour leurs patiences, Christophe pour avoir pris le temps de lire la bêta. Merci à l'équipe de Developpez et plus particulièrement à Kerod de m'avoir aiguillé, longbeach et ra77 pour vos nombreuses remarques et littledaem pour sa relecture bien utile !