Frage HAProxy-Neuladen mit null Paketverlust


Ich betreibe einen HAProxy Load Balancing Server, um die Last auf mehrere Apache Server zu verteilen. Ich muss HAProxy jederzeit neu laden, um den Lastausgleichsalgorithmus zu ändern.

Das alles funktioniert gut, abgesehen von der Tatsache, dass ich den Server neu laden muss, ohne ein einzelnes Paket zu verlieren (im Moment gibt ein Reload mir durchschnittlich 99,76% Erfolg, mit 1000 Anfragen pro Sekunde für 5 Sekunden). Ich habe viele Stunden damit zu tun gehabt und den folgenden Befehl gefunden, um den HAProxy-Server "neu zu laden":

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

Dies hat jedoch wenig oder keine Wirkung gegenüber dem einfachen alten service haproxy reloadEs fällt immer noch 0,24% im Durchschnitt.

Gibt es eine Möglichkeit, die HAProxy-Konfigurationsdatei ohne ein verlorenes Paket von einem Benutzer neu zu laden?


35
2018-03-07 19:00


Ursprung


Wenn Sie so viel Zuverlässigkeit benötigen, wäre eine bessere Lösung, mehr als eine Instanz von HAproxy zu betreiben, bei der Sie eine außer Betrieb nehmen können, um sie neu zu laden, wieder einzusetzen und für die andere (n) zu wiederholen. - yoonix


Antworten:


Gemäß https://github.com/aws/opsworks-cookbooks/pull/40 und folglich http://www.mail-archive.com/haproxy@formilux.org/msg06885.html Sie können:

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 den Effekt, dass das SYN vor einem Neustart gelöscht wird   Clients senden dieses SYN erneut, bis es den neuen Prozess erreicht.


29
2018-03-07 21:48



serverfault.com/questions/627988/ ... - Mike Curry
Beide Befehle gaben mir folgendes: iptables v1.4.14: invalid port/service --syn "spezifiziert" - Dmitri DB
@DmitriDB du sollst ersetzen $PORT mit dem tatsächlichen Port haproxy hört zu. Wenn haproxy mehrere Ports überwacht, schreiben Sie replace --dport $PORT mit --dports $PORTS_SEPARATED_BY_COMMAS, z.B., --dports 80,443. - pepoluan
iptables 1.4.7 (Centos 6.7) - Sie müssen auch -m multiport angeben, wenn Sie --dports verwenden möchten. Also seine "iptables -I INPUT -p tcp -m Multiport - Portionen 80,443 - SYN-J DROP" und ebenso für die -D - carpii


Yelp hat einen anspruchsvolleren Ansatz basierend auf sorgfältigen Tests geteilt. Der Blog-Artikel ist ein tiefer Tauchgang, und es lohnt sich die Zeit zu investieren, um ihn zu schätzen.

True Zero Downtime HAProxy lädt neu

tl; dr verwendet Linux tc (Traffic Control) und iptables, um SYN-Pakete vorübergehend in der Warteschlange zu speichern, während HAProxy neu geladen wird und zwei PIDs an denselben Port angeschlossen sind (SO_REUSEPORT).

Ich bin nicht zufrieden damit, den gesamten Artikel auf ServerFault erneut zu veröffentlichen; Dennoch, hier sind ein paar Auszüge, um Ihr Interesse zu wecken:

Durch die Verzögerung von SYN-Paketen in unseren HAProxy Load Balancern, die auf jedem Rechner laufen, können wir den Traffic während HAProxy Reloads minimal beeinflussen. Dadurch können wir Service Backends innerhalb unserer SOA hinzufügen, entfernen und ändern, ohne den Benutzerverkehr erheblich zu beeinträchtigen.

# 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

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

Cheers to Yelp für den Austausch dieser erstaunlichen Einblicke.


24
2018-05-20 22:49



Ausgezeichnete Verbindung! Aber vielleicht möchten Sie es hier für den Fall, dass der Link abläuft, zusammenfassen. Das ist der einzige Grund für keinen upvote. - Matt
@Matt hat einige Auszüge und Codebeispiele hinzugefügt - Steve Jansen


Es gibt eine andere, viel einfachere Möglichkeit, Haproxy mit echter Null-Downtime neu zu laden - es heißt iptables spiegeln (Der Artikel ist eigentlich Unbounce Antwort auf Yelp-Lösung). Es ist sauberer als angenommene Antwort, da es keine Notwendigkeit gibt, irgendwelche Pakete fallen zu lassen, die Probleme mit langen Nachladungen verursachen können.

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

  1. Lassen Sie uns ein Paar haproxy-Instanzen haben - der erste aktiv, der einen Verkehr empfängt und der zweite, der keinen Verkehr empfängt.
  2. Sie können die Standby-Instanz jederzeit neu konfigurieren (neu laden).
  3. Sobald der Bereitschaftsmodus mit der neuen Konfiguration fertig ist, leiten Sie alle neuen Verbindungen zum Standby-Knoten um, der dann wird neu aktiv. Unbounce bietet Bash-Skript, das den Flip mit wenigen einfachen tut iptable Befehle.
  4. Für einen Moment haben Sie zwei aktive Instanzen. Sie müssen warten, bis Verbindungen zu alt aktiv wird aufhören. Die Zeit hängt von Ihrem Dienstverhalten und den Keep-Alive-Einstellungen ab.
  5. Verkehr nach alt aktiv stoppt, was wird neuer Standby - Sie sind zurück in Schritt 1.

Darüber hinaus kann die Lösung für jede Art von Service (Nginx, Apache usw.) verwendet werden und ist fehlertoleranter, da Sie die Standby-Konfiguration testen können, bevor sie online geht.


7
2017-12-31 00:22





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

Der Prozess, den haproxy beim Neustart benötigt, ist:

1) Versuchen Sie SO_REUSEPORT beim Öffnen des Ports einzustellen (https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798)

2) Versuchen Sie, den Port zu öffnen (wird mit SO_REUSEPORT gelingen)

3) Wenn es nicht erfolgreich war, signalisieren Sie dem alten Prozess, den Port zu schließen, warten Sie 10ms und versuchen Sie es erneut. (https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577)

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


3
2018-03-17 04:01



Es wird mit passieren SO_REUSEPORT in einem bestimmten Szenario - vor allem unter starkem Verkehr. Wenn SYN an den alten Haproxy-Prozess gesendet wird und im selben Moment, schließt es den Listening-Socket, was zu RST führt. Siehe Yelp-Artikel, der in der anderen Antwort oben erwähnt wurde. - gertas
Das ist stinkend ... Um das Problem zusammenzufassen: Linux verteilt neue Verbindungen zwischen allen Prozessen, die auf einem bestimmten Port warten, wenn SO_REUSEPORT verwendet wird, so dass es kurze Zeit dauert, bis der alte Prozess Verbindungen in seine Warteschlange aufnimmt. - Jason Stubbs


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

Ich habe ein typisches Setup mit 2 Knoten, auf denen HAproxy ausgeführt wird und ich bleibe am Leben. Keepalived Tracks Schnittstelle dummy0, so kann ich eine "ifconfig dummy0 down" tun, um die Umschaltung zu erzwingen.

Das eigentliche Problem ist, dass, ich weiß nicht warum, ein "haproxy reload" noch alle ESTABLISHED-Verbindungen löscht :( Ich habe versucht das "iptables flipping" von Gertas vorgeschlagen, aber ich habe einige Probleme, weil es eine NAT auf dem Ziel führt IP-Adresse, die in einigen Szenarien keine geeignete Lösung ist.

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

Hier ist das Iptables-Regelwerk:

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 markieren die Pakete, die zu den neuen Flüssen gehören (123.123.123.123 ist der VIP, der auf der haproxy verwendet wird, um die Frontends zu binden).

Dritte und vierte Regeln 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 Schnittstellen zu deaktivieren oder der Kernel wird diese Mars-Pakete löschen.

Und nicht zu vergessen, die zurückkehrenden Pakete! In meinem Fall gibt es asymmetrisches Routing (Anfragen kommen zu Client -> haproxy1 -> haproxy2 -> Webserver, und Antworten gehen von Webserver -> Haproxy1 -> Client), aber es betrifft nicht. Es funktioniert gut.

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

Grundsätzlich funktioniert das "graceful reload" wie folgt:

  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 "draining" -Prozess zu überwachen.
  3. Sobald nur noch wenige (oder null) Verbindungen vorhanden sind, wird "ifconfig dummy0 down" aktiviert, um das Failover von Keepalived zu erzwingen, sodass der gesamte Datenverkehr an den anderen HAproxy weitergeleitet wird.
  4. Ich entferne den iptables-Regelsatz
  5. (Nur für "nicht 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

1
2018-05-03 17:05