55 votes

Recharge gracieuse de HAProxy avec zéro perte de paquets

J'utilise un serveur d'équilibrage de charge HAProxy pour équilibrer la charge de plusieurs serveurs Apache. Je dois recharger HAProxy à tout moment pour modifier l'algorithme d'équilibrage de la charge.

Tout cela fonctionne bien, sauf que je dois recharger le serveur sans perdre un seul paquet (pour l'instant, un rechargement me donne 99,76% de succès en moyenne, avec 1000 requêtes par seconde pendant 5 secondes). J'ai fait de nombreuses heures de recherche à ce sujet, et j'ai trouvé la commande suivante pour "recharger gracieusement" le serveur HAProxy :

haproxy -D -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

Cependant, cela n'a que peu ou pas d'effet par rapport à l'ancienne méthode service haproxy reload mais il continue de baisser de 0,24 % en moyenne.

Existe-t-il un moyen de recharger le fichier de configuration de HAProxy sans qu'un seul paquet ne soit rejeté par un utilisateur ?

6 votes

I

41voto

Mxx Points 2302

Selon le https://github.com/aws/opsworks-cookbooks/pull/40 et par conséquent http://www.mail-archive.com/haproxy@formilux.org/msg06885.html vous pouvez :

iptables -I INPUT -p tcp --dport $PORT --syn -j DROP
sleep 1
service haproxy restart
iptables -D INPUT -p tcp --dport $PORT --syn -j DROP

Cela a pour effet d'interrompre le SYN avant un redémarrage, de sorte que clients renverront ce SYN jusqu'à ce qu'il atteigne le nouveau processus.

1 votes

s

0 votes

B iptables v1.4.14: invalid port/service -

2 votes

Iptables 1.4.7 (Centos 6.7) - vous devez également spécifier -m mulitport si vous voulez utiliser --dports. Donc c'est "iptables -I INPUT -p tcp -m multiport --dports 80,443 --syn -j DROP" et de même pour le -D

32voto

Steve Jansen Points 413

Yelp a partagé une approche plus sophistiquée basée sur des tests méticuleux. L'article du blog est une plongée en profondeur et mérite que l'on y consacre du temps pour l'apprécier à sa juste valeur.

Rechargements HAProxy sans temps d'arrêt

tl;dr utiliser Linux tc (traffic control) et iptables pour mettre temporairement en file d'attente les paquets SYN pendant que HAProxy se recharge et que deux pids sont attachés au même port ( SO_REUSEPORT ).

Je n'ai pas envie de republier l'intégralité de l'article sur ServerFault ; voici néanmoins quelques extraits pour susciter votre intérêt :

En retardant les paquets SYN entrant dans nos équilibreurs de charge HAProxy qui fonctionnent sur chaque machine, nous sommes en mesure de minimiser l'impact sur le trafic lors des rechargements HAProxy, ce qui nous permet d'ajouter, de supprimer et de modifier des backends de services au sein de notre SOA sans craindre d'affecter de manière significative le trafic des utilisateurs.

# plug_manipulation.sh
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --buffer
service haproxy reload
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

# setup_iptables.sh
iptables -t mangle -I OUTPUT -p tcp -s 169.254.255.254 --syn -j MARK --set-mark 1

# setup_qdisc.sh
## Set up the queuing discipline
tc qdisc add dev lo root handle 1: prio bands 4
tc qdisc add dev lo parent 1:1 handle 10: pfifo limit 1000
tc qdisc add dev lo parent 1:2 handle 20: pfifo limit 1000
tc qdisc add dev lo parent 1:3 handle 30: pfifo limit 1000

## Create a plug qdisc with 1 meg of buffer
nl-qdisc-add --dev=lo --parent=1:4 --id=40: plug --limit 1048576
## Release the plug
nl-qdisc-add --dev=lo --parent=1:4 --id=40: --update plug --release-indefinite

## Set up the filter, any packet marked with “1” will be
## directed to the plug
tc filter add dev lo protocol ip parent 1:0 prio 1 handle 1 fw classid 1:4

Gist : https://gist.github.com/jolynch/97e3505a1e92e35de2c0

Bravo à Yelp pour le partage de ces informations étonnantes.

0 votes

Excellent lien ! Mais peut-être voudriez-vous le résumer ici au cas où le lien expirerait. C'est la seule raison pour laquelle il n'y a pas eu d'upvote.

0 votes

@Matt a ajouté quelques extraits et échantillons de code

10voto

Someone Points 111

Il existe un autre moyen beaucoup plus simple de recharger haproxy avec un véritable temps d'arrêt zéro - il s'appelle iptables flipping (l'article est en fait la réponse d'Unbounce à la solution de Yelp). C'est plus propre que la réponse acceptée car il n'y a pas besoin d'abandonner des paquets, ce qui peut causer des problèmes avec de longs rechargements.

En bref, la solution consiste en les étapes suivantes :

  1. Prenons une paire d'instances haproxy - la première active qui reçoit du trafic et la seconde en veille qui ne reçoit pas de trafic.
  2. Vous pouvez reconfigurer (recharger) l'instance de secours à tout moment.
  3. Une fois que le nœud de secours est prêt avec la nouvelle configuration, vous détournez toutes les NOUVELLES connexions vers le nœud de secours qui devient nouveau actif . Unbounce fournit bash script qui fait le retournement avec quelques simples iptable commandes .
  4. Pendant un moment, vous avez deux instances actives. Vous devez attendre que les connexions ouvertes à ancien actif cessera. Le délai dépend du comportement de votre service et des paramètres de maintien en vie.
  5. Trafic vers ancien actif s'arrête qui devient nouveau standby - vous revenez à l'étape 1.

En outre, la solution peut être adoptée pour tout type de service (nginx, apache, etc.) et est plus tolérante aux pannes car vous pouvez tester la configuration de secours avant qu'elle ne soit mise en ligne.

4voto

Jason Stubbs Points 41

Edit : Ma réponse part du principe que le noyau n'envoie du trafic qu'au port le plus récent ouvert avec SO_REUSEPORT, alors qu'il envoie en fait du trafic à tous les processus, comme décrit dans l'un des commentaires. En d'autres termes, la danse iptables est toujours nécessaire :(

Si vous utilisez un noyau qui supporte SO_REUSEPORT, ce problème ne devrait pas se produire.

Le processus que suit haproxy lorsqu'il redémarre est le suivant :

1) Essayez de définir SO_REUSEPORT lors de l'ouverture du port ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Essayez d'ouvrir le port (vous y parviendrez avec SO_REUSEPORT)

3) En cas d'échec, signaler à l'ancien processus de fermer son port, attendre 10 ms et recommencer. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Il a été pris en charge pour la première fois dans le noyau Linux 3.9, mais certaines distros l'ont rétroporté. Par exemple, les noyaux EL6 à partir de 2.6.32-417.el6 le supportent.

0 votes

Cela se produira avec SO_REUSEPORT dans certains scénarios particuliers, notamment en cas de trafic intense. Lorsque SYN est envoyé à l'ancien processus haproxy et qu'au même moment il ferme le socket d'écoute, il en résulte un RST. Voir l'article de Yelp mentionné dans l'autre réponse ci-dessus.

4 votes

Ça craint... Pour résumer le problème, Linux répartit les nouvelles connexions entre tous les processus écoutant sur un port particulier lorsque SO_REUSEPORT est utilisé, de sorte qu'il y a un court laps de temps pendant lequel l'ancien processus recevra encore des connexions dans sa file d'attente.

2voto

Vins Vilaplana Points 21

Je vais expliquer ma configuration et comment j'ai résolu le problème des recharges gracieuses :

J'ai une configuration typique avec 2 nœuds utilisant HAproxy et keepalived. Keepalived suit l'interface dummy0, de sorte que je peux faire un "ifconfig dummy0 down" pour forcer le basculement.

Le vrai problème est que, je ne sais pas pourquoi, un "haproxy reload" laisse encore tomber toutes les connexions ESTABLISHED :( J'ai essayé le "iptables flipping" proposé par gertas, mais j'ai trouvé quelques problèmes parce qu'il effectue un NAT sur l'adresse IP de destination, ce qui n'est pas une solution appropriée dans certains scénarios.

Au lieu de cela, j'ai décidé d'utiliser un CONNMARK dirty hack pour marquer les paquets appartenant à de NOUVELLES connexions, et ensuite rediriger ces paquets marqués vers l'autre nœud.

Voici le jeu de règles iptables :

iptables -t mangle -A PREROUTING -i eth1 -d 123.123.123.123/32 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP

Les deux premières règles marquent les paquets appartenant aux nouveaux flux (123.123.123.123 est le VIP keepalived utilisé sur l'haproxy pour lier les frontends).

Les troisième et quatrième règles marquent les paquets FIN/RST. (Je ne sais pas pourquoi, la cible TEE "ignore" les paquets FIN/RST).

La cinquième règle envoie un duplicata de tous les paquets marqués à l'autre HAproxy (192.168.0.2).

La sixième règle laisse tomber les paquets appartenant à de nouveaux flux pour éviter qu'ils n'atteignent leur destination initiale.

N'oubliez pas de désactiver rp_filter sur les interfaces ou le noyau laissera tomber ces paquets martiens.

Enfin, il faut faire attention aux paquets de retour ! Dans mon cas, il y a un routage asymétrique (les demandes arrivent au client -> haproxy1 -> haproxy2 -> serveur web, et les réponses vont du serveur web -> haproxy1 -> client), mais cela n'a pas d'incidence. Le système fonctionne parfaitement.

Je sais que la solution la plus élégante serait d'utiliser iproute2 pour faire le détournement, mais cela ne fonctionne que pour le premier paquet SYN. Quand il a reçu l'ACK (3ème paquet de la poignée de main à 3 voies), il ne l'a pas marqué :( Je n'ai pas pu passer beaucoup de temps à enquêter, dès que j'ai vu que cela fonctionnait avec la cible TEE, je l'ai laissé là. Bien sûr, n'hésitez pas à essayer avec iproute2.

Fondamentalement, le "rechargement gracieux" fonctionne comme suit :

  1. J'active le jeu de règles iptables et je vois immédiatement les nouvelles connexions aller vers l'autre HAproxy.
  2. Je garde un œil sur "netstat -an | grep ESTABLISHED | wc -l" pour superviser le processus de "vidange".
  3. Une fois qu'il n'y a plus que quelques connexions (ou zéro), "ifconfig dummy0 down" pour forcer keepalived à basculer, afin que tout le trafic aille vers l'autre HAproxy.
  4. Je supprime le jeu de règles iptables
  5. (Uniquement pour les configurations keepalive "non préemptées") "ifconfig dummy0 up".

L'ensemble de règles IPtables peut être facilement intégré dans un script :

#!/bin/sh

case $1 in
start)
        echo Redirection for new sessions is enabled

#       echo 0 > /proc/sys/net/ipv4/tcp_fwmark_accept
        for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
        iptables -t mangle -A PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1
        iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -A PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -A PREROUTING -i eth1 -m mark --mark 1 -j DROP
        ;;
stop)
        iptables -t mangle -D PREROUTING -i eth1 -m mark --mark 1 -j DROP
        iptables -t mangle -D PREROUTING -i eth1 -m mark ! --mark 0 -j TEE --gateway 192.168.0.2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags RST RST -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -i eth1 -p tcp --tcp-flags FIN FIN -j MARK --set-mark 2
        iptables -t mangle -D PREROUTING -j CONNMARK --restore-mark
        iptables -t mangle -D PREROUTING -i eth1 ! -d 123.123.123.123 -m conntrack --ctstate NEW -j CONNMARK --set-mark 1

        echo Redirection for new sessions is disabled
        ;;
esac

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X