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 :
- J'active le jeu de règles iptables et je vois immédiatement les nouvelles connexions aller vers l'autre HAproxy.
- Je garde un œil sur "netstat -an | grep ESTABLISHED | wc -l" pour superviser le processus de "vidange".
- 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.
- Je supprime le jeu de règles iptables
- (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
6 votes
I