Ordentliches HAProxy-Reload ohne Paketverlust

42

Ich verwende einen HAProxy-Lastausgleichsserver, um die Last auf mehrere Apache-Server zu verteilen. Ich muss HAProxy jederzeit neu laden, um den Algorithmus für den Lastenausgleich zu ändern.

Dies alles funktioniert einwandfrei, mit der Ausnahme, dass ich den Server neu laden muss, ohne ein einziges Paket zu verlieren (im Moment führt ein erneutes Laden zu einem durchschnittlichen Erfolg von 99,76%, mit 1000 Anfragen pro Sekunde für 5 Sekunden). Ich habe viele Stunden lang nachgeforscht und den folgenden Befehl gefunden, um den HAProxy-Server ordnungsgemäß neu zu laden:

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

Dies hat jedoch nur geringe oder keine Auswirkungen im Vergleich zum normalen Alter service haproxy reload, es fällt durchschnittlich immer noch um 0,24%.

Gibt es eine Möglichkeit, die HAProxy-Konfigurationsdatei neu zu laden, ohne dass ein Benutzer ein einzelnes Paket fallen lässt?

Conor Taylor
quelle
6
Wenn Sie so viel Zuverlässigkeit benötigen, ist es eine bessere Lösung, mehr als eine Instanz von HAproxy auszuführen, bei der Sie eine außer Betrieb setzen können, um sie neu zu laden, und sie erneut einzulegen und für die andere (n) zu wiederholen.
Yoonix

Antworten:

32

Laut https://github.com/aws/opsworks-cookbooks/pull/40 und folglich http://www.mail-archive.com/[email protected]/msg06885.html können Sie:

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

Dies hat zur Folge, dass das SYN vor einem Neustart gelöscht wird, sodass Clients dieses SYN erneut senden, bis es den neuen Prozess erreicht.

Mxx
quelle
Diese beiden Befehle gaben mir iptables v1.4.14: invalid port/service Folgendes : --syn "Angegeben"
Dmitri DB
5
@DmitriDB, die Sie durch $PORTden aktuellen Port ersetzen sollen, haproxylauscht. Wenn haproxy mehrere Ports überwacht, schreiben Sie "replace" --dport $PORTmit --dports $PORTS_SEPARATED_BY_COMMASz --dports 80,443.
Pepoluan
1
iptables 1.4.7 (Centos 6.7) - Sie müssen auch -m mulitport angeben, wenn Sie --dports verwenden möchten. So sein "iptables -I INPUT -p tcp -m Mehrtor --dports 80.443 --syn -j DROP" und ebenso für die D
carpii
25

Yelp verfolgte einen differenzierteren Ansatz, der auf sorgfältigen Tests basierte. Der Blog-Artikel ist ein intensiver Tauchgang und es lohnt sich, Zeit zu investieren, um ihn vollständig zu würdigen.

HAProxy-Reloads ohne Ausfallzeiten

Verwenden Sie Linux tc (Traffic Control) und iptables, um SYN-Pakete vorübergehend in die Warteschlange zu stellen, während HAProxy neu lädt und zwei Pids an denselben Port ( SO_REUSEPORT) angeschlossen sind.

Es ist mir unangenehm, den gesamten Artikel auf ServerFault erneut zu veröffentlichen. dennoch sind hier ein paar auszüge, um ihr interesse zu wecken:

Durch die Verzögerung von SYN-Paketen in unseren HAProxy-Load-Balancern, die auf jedem Computer ausgeführt werden, können wir den Datenverkehr beim erneuten Laden von HAProxy-Paketen auf ein Minimum beschränken. Auf diese Weise können wir Service-Backends in unserer SOA hinzufügen, entfernen und ändern, ohne den Datenverkehr der Benutzer erheblich beeinträchtigen zu müssen.

# 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

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

Ein Hoch auf Yelp für den Austausch solch erstaunlicher Einsichten.

Steve Jansen
quelle
Hervorragender Link! Aber vielleicht möchten Sie es hier zusammenfassen, falls der Link abläuft. Das ist der einzige Grund für keine Gegenstimme.
Matt
@Matt fügte einige Auszüge und Codebeispiele hinzu
Steve Jansen
8

Es gibt einen anderen, viel einfacheren Weg, um Haproxy mit einer Ausfallzeit von Null neu zu laden - es heißt iptables flipping (der Artikel ist eigentlich Unbounce-Antwort auf Yelp-Lösung). Es ist sauberer als akzeptierte Antworten, da keine Pakete verworfen werden müssen, die Probleme mit langen Nachladevorgängen verursachen könnten.

Kurz gesagt besteht die Lösung aus den folgenden Schritten:

  1. Nehmen wir ein Paar Haproxy-Instanzen: Die erste aktive Instanz empfängt einen Datenverkehr und die zweite im Standby-Modus empfängt keinen Datenverkehr.
  2. Sie können die Standby-Instanz jederzeit neu konfigurieren (neu laden).
  3. Sobald der Standby-Modus mit der neuen Konfiguration fertig ist, leiten Sie alle NEUEN Verbindungen zum Standby-Knoten um, der neu aktiv wird . Unbounce stellt ein Bash-Skript zur Verfügung, das das Umblättern mit wenigen einfachen iptableBefehlen erledigt .
  4. Für einen Moment haben Sie zwei aktive Instanzen. Sie müssen warten, bis geöffnete Verbindungen zu alten aktiven beendet werden. Die Zeit hängt von Ihrem Serviceverhalten und Ihren Keep-Alive-Einstellungen ab.
  5. Verkehr zu alten aktiven Haltestellen, die zu neuem Standby werden - Sie sind wieder in Schritt 1.

Darüber hinaus kann die Lösung auf alle Arten von Diensten (Nginx, Apache usw.) angewendet werden und ist fehlertoleranter, da Sie die Standby-Konfiguration testen können, bevor sie online geht.

gertas
quelle
4

Bearbeiten: Meine Antwort geht davon aus, dass der Kernel nur Verkehr an den letzten Port sendet, der mit SO_REUSEPORT geöffnet werden soll, während er tatsächlich Verkehr an alle Prozesse sendet, wie in einem der Kommentare beschrieben. Mit anderen Worten, der Iptables-Tanz ist weiterhin erforderlich. :(

Wenn Sie sich in einem Kernel befinden, der SO_REUSEPORT unterstützt, sollte dieses Problem nicht auftreten.

Der Prozess, den Haproxy beim Neustart ausführt, ist:

1) Versuchen Sie, SO_REUSEPORT festzulegen, wenn Sie den Port öffnen ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 ).

2) Versuchen Sie den Port zu öffnen (wird mit SO_REUSEPORT erfolgreich sein)

3) Wenn dies nicht erfolgreich war, signalisieren Sie dem alten Prozess, dass der Port geschlossen werden soll, warten Sie 10 ms und wiederholen Sie den Vorgang. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Es wurde zuerst im Linux 3.9-Kernel unterstützt, aber einige Distributionen haben es zurückportiert. Zum Beispiel unterstützen EL6-Kernel von 2.6.32-417.el6 dies.

Jason Stubbs
quelle
Es wird SO_REUSEPORTunter bestimmten Umständen passieren - insbesondere unter starkem Verkehr. Wenn SYN an den alten Haproxy-Prozess gesendet wird und im selben Moment den Listening-Socket schließt, führt dies zu RST. Siehe oben in einer anderen Antwort genannten Yelp-Artikel.
Gertas
4
Das nervt ... Um das Problem zusammenzufassen, Linux verteilt neue Verbindungen zwischen allen Prozessen, die einen bestimmten Port abhören, wenn SO_REUSEPORT verwendet wird, so dass es eine kurze Zeit gibt, in der der alte Prozess weiterhin Verbindungen in seine Warteschlange stellt.
Jason Stubbs
2

Ich erkläre mein Setup und wie ich die anmutigen Nachladevorgänge gelöst habe:

Ich habe ein typisches Setup mit 2 Knoten mit HAproxy und Keepalived. Keepalived Tracks Interface Dummy0, also kann ich ein "ifconfig Dummy0 down" machen, um das Umschalten zu erzwingen.

Das eigentliche Problem ist, dass bei einem "haproxy reload" immer noch alle eingerichteten Verbindungen getrennt werden :( Ich habe das von gertas vorgeschlagene "iptables flipping" ausprobiert, aber ich habe einige Probleme gefunden, weil es ein NAT auf dem Ziel ausführt IP-Adresse, die in einigen Szenarien nicht geeignet ist.

Stattdessen habe ich mich entschieden, einen CONNMARK-Dirty-Hack zu verwenden, um Pakete zu markieren, die zu NEUEN Verbindungen gehören, und diese markierten Pakete dann auf den anderen Knoten umzuleiten.

Hier ist der Iptables-Regelsatz:

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

Die ersten beiden Regeln kennzeichnen die Pakete, die zu den neuen Flows gehören (123.123.123.123 ist der Keepalived-VIP, der auf dem Haproxy zum Binden der Frontends verwendet wird).

Die dritte und vierte Regel markieren Pakete FIN / RST-Pakete. (Ich weiß nicht warum, TEE-Ziel "ignoriert" FIN / RST-Pakete).

Die fünfte Regel sendet ein Duplikat aller markierten Pakete an den anderen HAproxy (192.168.0.2).

Die sechste Regel verwirft Pakete, die zu neuen Flüssen gehören, um zu verhindern, dass sie ihr ursprüngliches Ziel erreichen.

Denken Sie daran, rp_filter auf Interfaces zu deaktivieren, da der Kernel diese Martian-Pakete sonst verwirft.

Und last but not least, achten Sie auf die zurückgegebenen Pakete! In meinem Fall gibt es asymmetrisches Routing (Anfragen kommen zu Client -> haproxy1 -> haproxy2 -> Webserver und Antworten gehen von Webserver -> haproxy1 -> Client), aber es hat keine Auswirkungen. Es funktioniert gut.

Ich weiß, die eleganteste Lösung wäre, iproute2 für die Umleitung zu verwenden, aber es hat nur für das erste SYN-Paket funktioniert. Als es das ACK (3. Paket des 3-Wege-Handshakes) erhielt, wurde es nicht markiert :( Ich konnte nicht viel Zeit für Nachforschungen aufwenden, sobald ich sah, dass es mit dem TEE-Ziel funktioniert, ließ es es dort. Natürlich können Sie es auch mit iproute2 ausprobieren.

Grundsätzlich funktioniert das "Graceful Reload" so:

  1. Ich aktiviere den iptables-Regelsatz und sehe sofort die neuen Verbindungen zum anderen HAproxy.
  2. Ich behalte "netstat -an | grep ESTABLISHED | wc -l" im Auge, um den "Entleerungs" -Prozess zu überwachen.
  3. Sobald es nur wenige (oder keine) Verbindungen gibt, "ifconfig dummy0 down", um das Keepalived-Failover zu erzwingen, sodass der gesamte Datenverkehr zum anderen HAproxy geleitet wird.
  4. Ich entferne den Iptables-Regelsatz
  5. (Nur für "non-preempting" keepalive config) "ifconfig dummy0 up".

Der IPtables-Regelsatz kann einfach in ein Start / Stopp-Skript integriert werden:

#!/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
Vins Vilaplana
quelle