HAProxy z gracją przeładowuje z zerową utratą pakietów

42

Korzystam z serwera równoważenia obciążenia HAProxy, aby zrównoważyć obciążenie wielu serwerów Apache. Muszę ponownie załadować HAProxy w dowolnym momencie, aby zmienić algorytm równoważenia obciążenia.

To wszystko działa dobrze, z wyjątkiem faktu, że muszę ponownie załadować serwer bez utraty jednego pakietu (w tej chwili przeładowanie daje mi średnio 99,76% sukcesu przy 1000 żądaniach na sekundę przez 5 sekund). Przeprowadziłem wiele godzin badań na ten temat i znalazłem następujące polecenie „płynnego przeładowania” serwera HAProxy:

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

Ma to jednak niewielki lub żaden efekt w porównaniu do zwykłego starego service haproxy reload, wciąż spada średnio o 0,24%.

Czy jest jakiś sposób na ponowne załadowanie pliku konfiguracyjnego HAProxy bez jednego upuszczonego pakietu od dowolnego użytkownika?

Conor Taylor
źródło
6
Jeśli potrzebujesz takiej niezawodności, lepszym rozwiązaniem byłoby uruchomienie więcej niż jednej instancji HAproxy, w której możesz wyłączyć jedną z usług w celu ponownego załadowania, włóż ją z powrotem i powtórz dla pozostałych.
yoonix

Odpowiedzi:

32

Zgodnie z https://github.com/aws/opsworks-cookbooks/pull/40, a zatem http://www.mail-archive.com/[email protected]/msg06885.html możesz:

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

Powoduje to upuszczenie SYN przed ponownym uruchomieniem, dzięki czemu klienci będą ponownie wysyłać ten SYN, dopóki nie osiągnie nowego procesu.

Mxx
źródło
oba te polecenia dały mi to: - iptables v1.4.14: invalid port/service syn „określono”
Dmitri DB
5
@DmitriDB, który należy zastąpić $PORTfaktycznym portem, haproxyna którym nasłuchuje. Jeśli haproxy nasłuchuje na wielu portach, zapisu zastąpić --dport $PORTz --dports $PORTS_SEPARATED_BY_COMMASnp --dports 80,443.
pepoluan
1
iptables 1.4.7 (Centos 6.7) - musisz także podać -m mulitport, jeśli chcesz używać --dports. Więc jego „iptables -I INPUT -p tcp -m multiport --ports 80,443 --syn -j DROP” i podobnie dla -D
carpii
25

Yelp podzieliło bardziej wyrafinowane podejście oparte na skrupulatnych testach. Artykuł na blogu to głębokie nurkowanie i warte czasu, aby w pełni go docenić.

Odświeżanie HAProxy True Zero Przestoju

tl; dr używa Linux tc (kontrola ruchu) i iptables do tymczasowego umieszczania w kolejce pakietów SYN podczas przeładowywania HAProxy i podłączania dwóch pid do tego samego portu ( SO_REUSEPORT).

Nie czuję się komfortowo ponownie publikując cały artykuł o ServerFault; Niemniej jednak oto kilka fragmentów, które wzbudzą Twoje zainteresowanie:

Opóźniając pakiety SYN wchodzące w nasze moduły równoważenia obciążenia HAProxy, które działają na każdej maszynie, jesteśmy w stanie minimalnie wpływać na ruch podczas przeładowań HAProxy, co pozwala nam dodawać, usuwać i zmieniać zaplecza usług w ramach SOA bez obawy, że znacząco wpłyną na ruch użytkowników.

# 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

Pozdrawiam Yelp za dzielenie się tak niesamowitymi spostrzeżeniami.

Steve Jansen
źródło
Doskonały link! Ale może chciałbyś to tutaj podsumować na wypadek, gdyby link wygasł. To jedyny powód braku aprobaty.
Matt
@Matt dodał kilka fragmentów i przykładów kodu
Steve Jansen
8

Istnieje jeszcze inny, znacznie prostszy sposób przeładowywania haproxy z prawdziwym zerowym przestojem - nazywa się to iptables flipping (artykuł jest w rzeczywistości odpowiedzią Unbounce na rozwiązanie Yelp). Jest to czystsze niż zaakceptowana odpowiedź, ponieważ nie trzeba upuszczać żadnych pakietów, które mogą powodować problemy z długimi przeładowaniami.

W skrócie, rozwiązanie składa się z następujących kroków:

  1. Załóżmy parę instancji haproxy - pierwsza aktywna, która odbiera ruch, a druga w trybie gotowości, która nie odbiera żadnego ruchu.
  2. W dowolnym momencie możesz ponownie skonfigurować (ponownie załadować) instancję rezerwową.
  3. Gdy tryb gotowości jest gotowy z nową konfiguracją, przekierowujesz wszystkie NOWE połączenia do węzła gotowości, który staje się nowy aktywny . Unbounce zapewnia skrypt bash, który wykonuje rzut za pomocą kilku prostych iptablepoleceń .
  4. Przez chwilę masz dwie aktywne instancje. Musisz poczekać, aż ustaną otwarte połączenia ze starymi aktywnymi . Czas zależy od zachowania usługi i ustawień utrzymania.
  5. Ruch do starych aktywnych przystanków, które stają się nowym trybem gotowości - wrócisz do kroku 1.

Co więcej, rozwiązanie można dostosować do dowolnej usługi (nginx, apache itp.) I jest bardziej odporne na awarie, ponieważ można przetestować konfigurację trybu gotowości przed przejściem do trybu online.

gertas
źródło
4

Edycja: Moja odpowiedź zakłada, że ​​jądro wysyła ruch tylko do najnowszego portu, który ma zostać otwarty za pomocą SO_REUSEPORT, podczas gdy faktycznie wysyła ruch do wszystkich procesów, jak opisano w jednym z komentarzy. Innymi słowy, taniec iptables jest nadal wymagany. :(

Jeśli korzystasz z jądra obsługującego SO_REUSEPORT, ten problem nie powinien się zdarzyć.

Proces, który wykonuje haproxy po ponownym uruchomieniu, to:

1) Spróbuj ustawić SO_REUSEPORT podczas otwierania portu ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Spróbuj otworzyć port (zakończy się sukcesem w przypadku SO_REUSEPORT)

3) Jeśli to się nie powiedzie, zasygnalizuj, że stary proces zamknął port, odczekaj 10 ms i spróbuj ponownie. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Po raz pierwszy był obsługiwany w jądrze Linuksa 3.9, ale niektóre dystrybucje go przeportowały. Na przykład jądra EL6 z wersji 2.6.32-417.el6 obsługują to.

Jason Stubbs
źródło
Stanie się tak w przypadku SO_REUSEPORTniektórych szczególnych scenariuszy - szczególnie przy dużym natężeniu ruchu. Kiedy SYN jest wysyłany do starego procesu haproxy iw tym samym momencie zamyka gniazdo nasłuchiwania, co powoduje RST. Zobacz artykuł Yelp wspomniany w innej odpowiedzi powyżej.
gertas
4
To jest do kitu ... Podsumowując problem, Linux rozdziela nowe połączenia między wszystkimi procesami nasłuchującymi na danym porcie, gdy używany jest SO_REUSEPORT, więc jest krótki czas, w którym stary proces nadal będzie umieszczał połączenia w kolejce.
Jason Stubbs
2

Wyjaśnię moją konfigurację i jak rozwiązałem pełne wdzięku przeładowania:

Mam typową konfigurację z 2 węzłami z uruchomionym HAproxy i keepalived. Interfejs Keepalived śledzi interfejs dummy0, więc mogę zrobić „manekina ifconfig w dół”, aby wymusić przełączenie.

Prawdziwy problem polega na tym, że nie wiem, dlaczego „haproxy reload” nadal usuwa wszystkie ESTABLISHED połączenia :( Próbowałem „iptables flipping” zaproponowanego przez gertas, ale znalazłem pewne problemy, ponieważ wykonuje NAT w miejscu docelowym Adres IP, który nie jest odpowiednim rozwiązaniem w niektórych scenariuszach.

Zamiast tego zdecydowałem się użyć brudnego hacka CONNMARK do oznaczenia pakietów należących do NOWYCH połączeń, a następnie przekierować te oznaczone pakiety do innego węzła.

Oto zestaw reguł 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

Dwie pierwsze reguły oznaczają pakiety należące do nowych przepływów (123.123.123.123 jest utrzymywanym VIP-em używanym w haproxy do wiązania frontendów).

Trzecia i czwarta zasada oznacza pakiety pakiety FIN / RST. (Nie wiem dlaczego, cel TEE „ignoruje” pakiety FIN / RST).

Piąta reguła wysyła duplikat wszystkich oznaczonych pakietów do drugiego HAproxy (192.168.0.2).

Szósta reguła odrzuca pakiety należące do nowych przepływów, aby uniemożliwić dotarcie do pierwotnego celu.

Pamiętaj, aby wyłączyć rp_filter na interfejsach, w przeciwnym razie jądro usunie te marsjańskie pakiety.

I na koniec, pamiętaj o powracających pakietach! W moim przypadku istnieje routing asymetryczny (żądania przychodzą do klienta -> haproxy1 -> haproxy2 -> serwer WWW, a odpowiedzi wychodzą z serwera www -> haproxy1 -> klient), ale to nie ma wpływu. To działa dobrze.

Wiem, że najbardziej eleganckim rozwiązaniem byłoby użycie iproute2 do przekierowania, ale działało to tylko dla pierwszego pakietu SYN. Kiedy otrzymał ACK (trzeci pakiet 3-kierunkowego uścisku dłoni), nie oznaczył go :( Nie mogłem poświęcić dużo czasu na badanie, jak tylko zobaczyłem, że działa z celem TEE, zostawiłem go tam. Oczywiście możesz spróbować z iproute2.

Zasadniczo „płynne przeładowanie” działa w następujący sposób:

  1. Włączam zestaw reguł iptables i natychmiast widzę nowe połączenia przechodzące do drugiego HAproxy.
  2. Obserwuję „netstat -an | grep ESTABLISHED | wc -l”, aby nadzorować proces „opróżniania”.
  3. Gdy będzie tylko kilka (lub zero) połączeń, „ifconfig dummy0 down”, aby wymusić utrzymanie źródła do przełączenia awaryjnego, aby cały ruch trafił do innego HAproxy.
  4. Usuwam zestaw reguł iptables
  5. (Tylko dla „zachowawczej” konfiguracji keepalive) „ifconfig dummy0 up”.

Zestaw reguł IPtables można łatwo zintegrować ze skryptem start / stop:

#!/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
źródło