dimanche 20 février 2011

Exemple d'utilisation du répartiteur de charge Haproxy

Il y a de ça quelques années, j'ai eu l'occasion de travailler sur le répartiteur de charge HAProxy. Le point fort de cet outil est qu'il est très simple à mettre en oeuvre. Nous l'avions à l'époque mis en place pour une boucle de charge avec des serveurs d'applications Java WebLogic.

L'architecture


L'application pour laquelle nous devions mettre en place cette solution était un Webservice hébergé sur des serveurs d'application Weblogic. Il est important de souligner que cette application n'avait pas de notion de session et la persistance s'appuyait sur la librairie hibernate.

Afin de faire face au trafic (supérieur à 300 transactions/secondes), il avait été décidé d'héberger cette application sur 32 d'instances Weblogics réparties sur 4 machines. Nous devions trouver une solution permettant de répartir la charge tout en offrant des possibilités de maintenance de certains noeuds de la boucle de charge. Ceci nous permettait également de faire des évolutions applicatives en pleine journée.

Pourquoi utiliser ce type de répartiteur de charge


Nous disposions de matériel dédié à la répartition de charge (type Alteon) qui ne gère que la répartition de charge au niveau TCP. Cette situation n'était pas satisfaisante puisque nous avions besoin de pouvoir gérer certaines informations au niveau du HTTP (Keepalive et gestion boucle de charge). De plus, les applications venant consulter ce webservice avaient tendance à garder leur connexion HTTP ouverte empêchant la sortie d'instance Weblogic de la boucle de charge.

C'est là où intervient le HAProxy puisque cet outil de répartition de charge comprend le HTTP. Nous pouvions donc nous en servir pour désactiver le Keepalive et garantir qu'à chaque requête, nos clients passe par une nouvelle connexion.

A noter que la désactivation du Keepalive avait un surcoût au niveau CPU : 1 à 2% (sur un V890 équipé de 8 Ultra IV à 1.3 Ghz). Il est important de qualifier ce coût supplémentaire avec des benchs pour ne pas avoir de mauvaise surprise.

Le HAProxy était capable de scrupter une page présente sur le serveur WebLogic lui indiquant s'il doit le prendre en compte où non. Pour cela nous avions créé une page Web (page JSP dans une WAR) nous permettant de débrayer automatiquement une instance via le dépot d'un fichier dans le répertoire properties de nos instances Weblogic.

Création d'une application Web pour gérer la validité d'une instance


Nous allons tout d'abord créer un petit programme java qui va gérer un état d'une page en fonction d'un fichier properties :

package haproxy;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HaproxyTestServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (isAlive()) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().print("OK");
return;
} else {
resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,"Service non disponible");
}
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (isAlive()) {
resp.setStatus(HttpServletResponse.SC_OK);
} else {
resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
}
private boolean isAlive() {
URL url = this.getClass().getResource("/haproxy.down");
if (url != null) {
return false;
}
return true;
}
}


Il nous faut maintenant mapper cette applet au niveau de l'application Web en alimentant le fichier web.xml avec le contenu suivant :

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp">
<display-name>TestHaproxy</display-name>
<servlet>
<servlet-name>HaproxyTestServlet</servlet-name>
<display-name>HaproxyTestServlet</display-name>
<servlet-class>haproxy.HaproxyTestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HaproxyTestServlet</servlet-name>
<url-pattern>/HaproxyTestServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>


Reste à la mettre en place et de voir le résultat de son déploiement en consultant l'application en rentrant son URL (http://monserver:monport/haproxy/HaproxyTestServlet). Pour désactiver une instance ? Rien de plus simple : créer un fichier haproxy.down dans le répertoire des properties de l'instance. Remettre une instance dans la boucle ? Simplement en supprimant le fichier haproxy.down

Mise en place de la solution


Il faut d'abord créer un fichier de configuration pour notre HAProxy (haproxy.cfg).

Ce dernier contient l'adresse et le port d'écoute (ici host:8888) ainsi que de nombreux paramètres sur son fonctionnement. Ci-dessous un début de fichier de configuration intéressant :

global
maxconn 4096
daemon
nbproc 2
pidfile /var/run/haproxy-private.pid

listen proxy host:8888
log global
mode http
option httplog
option dontlognull
cookie COOKIEID insert indirect nocache
balance roundrobin


La liste des serveurs dans la boucle de répartition de charge est représenté par la liste de ligne commençant par server.

server inst11 web1:7011 check inter 2000 rise 2 fall 5
server inst21 web2:7021 check inter 2000 rise 2 fall 5
server inst31 web3:7031 check inter 2000 rise 2 fall 5
server inst41 web4:7041 check inter 2000 rise 2 fall 5
[...]
server inst18 web1:7018 check inter 2000 rise 2 fall 5
server inst28 web2:7028 check inter 2000 rise 2 fall 5
server inst38 web3:7038 check inter 2000 rise 2 fall 5
server inst48 web4:7048 check inter 2000 rise 2 fall 5

Indiquons également l'emplacement de notre page de test ainsi que nos paramètres de répartition de charge :

monitor-uri /haproxy/HaproxyTestServlet
retries 3
redispatch
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000


Enfin, dans le cas où vous voudriez désactiver le Keepalive HTTP, rajoutez les lignes suivantes :

reqidel ^Connection: # disable keep-alive
reqadd Connection:\ close
rspidel ^Connection:
rspadd Connection:\ close


Il ne nous reste plus qu'à lancer le démon haproxy à l'aide de la commande suivante :
haproxy -D -f haproxy.cfg


Conclusion


Dans cet article, nous n'avons vu qu'une toute petite partie des capacités de ce répartiteur de charge. Son auteur (Willy Tarreau) a également inclu des fonctionnalités très intéressantes comme :
- la gestion d'IPV6 (v1.2)
- la gestion des cookies applicatifs pour garder le contexte d'un client vers un seul serveur (v1.2)
- la reconfiguration à chaud (v1.2)
- la répartition de charge avec poids (v1.2)
- la page de diagnostique (v1.2)
- la gestion des contenus statiques/dynamiques (v1.3)

Pour en savoir plus : http://haproxy.1wt.eu/#desc