Ricarica aggraziata HAProxy senza perdita di pacchetti


42

Sto eseguendo un server di bilanciamento del carico HAProxy per bilanciare il carico su più server Apache. Ho bisogno di ricaricare HAProxy in qualsiasi momento per cambiare l'algoritmo di bilanciamento del carico.

Tutto funziona bene, tranne per il fatto che devo ricaricare il server senza perdere un singolo pacchetto (al momento un ricaricamento mi sta dando un successo del 99,76% in media, con 1000 richieste al secondo per 5 secondi). Ho fatto molte ore di ricerca su questo e ho trovato il seguente comando per "ricaricare con garbo" il server HAProxy:

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

Tuttavia, questo ha un effetto scarso o nullo rispetto al vecchio normale service haproxy reload, in media continua a scendere dello 0,24%.

Esiste un modo per ricaricare il file di configurazione HAProxy senza un singolo pacchetto rilasciato da nessun utente?


6
Se hai bisogno di tanta affidabilità, una soluzione migliore sarebbe quella di eseguire più di un'istanza di HAproxy in cui è possibile mettere fuori servizio uno per ricaricarlo, rimetterlo e ripeterlo per gli altri.
Yoonix

Risposte:


32

Secondo https://github.com/aws/opsworks-cookbooks/pull/40 e conseguentemente http://www.mail-archive.com/haproxy@formilux.org/msg06885.html è possibile:

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

Ciò ha l'effetto di far cadere SYN prima di un riavvio, in modo che i client rinvieranno questo SYN fino a quando non raggiunge il nuovo processo.



entrambi questi comandi mi hanno dato questo: iptables v1.4.14: invalid port/service --syn 'specificato'
Dmitri DB

5
@DmitriDB che dovresti sostituire $PORTcon la porta effettiva haproxysu cui stai ascoltando. Se HAProxy è in ascolto su più porte, sostituire scrittura --dport $PORTcon --dports $PORTS_SEPARATED_BY_COMMAS, ad esempio, --dports 80,443.
pepoluan,

1
iptables 1.4.7 (Centos 6.7) - devi anche specificare -m mulitport se vuoi usare --dports. Quindi il suo "iptables -I INPUT -p tcp -m multiport --dports 80.443 --syn -j DROP" e similmente per il -D
carpii

25

Yelp ha condiviso un approccio più sofisticato basato su test meticolosi. L'articolo del blog è un tuffo profondo e vale la pena investire tempo per apprezzarlo appieno.

Ricarichi HAProxy True Zero Downtime

tl; dr usa Linux tc (controllo del traffico) e iptables per mettere temporaneamente in coda i pacchetti SYN mentre HAProxy si sta ricaricando e ha due pid collegati alla stessa porta ( SO_REUSEPORT).

Non mi sento di ripubblicare l'intero articolo su ServerFault; tuttavia, ecco alcuni estratti per suscitare il tuo interesse:

Ritardando i pacchetti SYN che entrano nei nostri sistemi di bilanciamento del carico HAProxy in esecuzione su ogni macchina, siamo in grado di avere un impatto minimo sul traffico durante i ricarichi HAProxy, il che ci consente di aggiungere, rimuovere e modificare i back-end di servizio all'interno della nostra SOA senza timore di avere un impatto significativo sul traffico degli utenti.

# 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

Saluti a Yelp per aver condiviso idee così sorprendenti.


Link eccellente! Ma forse ti piacerebbe sintetizzarlo qui nel caso in cui il link scada. Questa è l'unica ragione per non votare.
Matt,

@Matt ha aggiunto alcuni estratti ed esempi di codice
Steve Jansen,

8

Esiste un altro modo molto più semplice per ricaricare il haproxy con tempi di inattività pari a zero reali: si chiama iptables flipping (l'articolo è in realtà Unbounce response to soluzione di Yelp). È più pulito della risposta accettata in quanto non è necessario eliminare alcun pacchetto che potrebbe causare problemi con ricariche prolungate.

In breve, la soluzione consiste nei seguenti passaggi:

  1. Diamo un paio di istanze haproxy: il primo attivo che riceve un traffico e il secondo in standby che non riceve alcun traffico.
  2. Riconfigurare (ricaricare) l'istanza di standby in qualsiasi momento.
  3. Quando lo standby è pronto con la nuova configurazione, tutte le NUOVE connessioni vengono deviate sul nodo di standby che diventa nuovo attivo . Unbounce fornisce lo script bash che esegue il capovolgimento con pochi semplici iptablecomandi .
  4. Per un momento hai due istanze attive. È necessario attendere fino a quando cesseranno le connessioni aperte al vecchio attivo . Il tempo dipende dal comportamento del servizio e dalle impostazioni keep-alive.
  5. Traffico verso i vecchi arresti attivi che diventano nuovi standby - si torna al passaggio 1.

Inoltre, la soluzione può essere adottata per qualsiasi tipo di servizio (nginx, apache ecc.) Ed è più tollerante ai guasti in quanto è possibile testare la configurazione di standby prima che sia online.


4

Modifica: la mia risposta presuppone che il kernel invii solo traffico alla porta più recente da aprire con SO_REUSEPORT, mentre in realtà invia traffico a tutti i processi come descritto in uno dei commenti. In altre parole, la danza iptables è ancora richiesta. :(

Se si utilizza un kernel che supporta SO_REUSEPORT, questo problema non dovrebbe verificarsi.

Il processo che haproxy prende quando si riavvia è:

1) Prova a impostare SO_REUSEPORT all'apertura della porta ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Prova ad aprire la porta (riuscirà con SO_REUSEPORT)

3) Se non è riuscito, segnala al vecchio processo di chiudere la porta, attendere 10 ms e riprovare. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

È stato inizialmente supportato nel kernel Linux 3.9 ma alcune distro lo hanno eseguito il backport. Ad esempio, i kernel EL6 da 2.6.32-417.el6 lo supportano.


Accadrà SO_REUSEPORTin alcuni scenari particolari, specialmente in condizioni di traffico intenso. Quando SYN viene inviato al vecchio processo haproxy e nello stesso momento chiude il socket di ascolto che si traduce in RST. Vedi l'articolo di Yelp menzionato nell'altra risposta sopra.
Gertas,

4
Che schifo ... Solo per riassumere il problema, Linux distribuisce nuove connessioni tra tutti i processi in ascolto su una determinata porta quando viene utilizzato SO_REUSEPORT, quindi c'è poco tempo in cui il vecchio processo continuerà a mettere le connessioni nella sua coda.
Jason Stubbs,

2

Spiegherò il mio setup e come ho risolto i graziosi ricarichi:

Ho una configurazione tipica con 2 nodi con HAproxy e keepalived. Le tracce keepalived si interfacciano con dummy0, quindi posso fare un "ifconfig dummy0 down" per forzare il passaggio.

Il vero problema è che, non so perché, un "ricaricato haproxy" fa cadere ancora tutte le connessioni STABILITE :( Ho provato il "lancio di iptables" proposto da Gertas, ma ho riscontrato alcuni problemi perché esegue un NAT sulla destinazione Indirizzo IP, che non è una soluzione adatta in alcuni scenari.

Invece, ho deciso di utilizzare un hack sporco CONNMARK per contrassegnare i pacchetti appartenenti a NUOVE connessioni e quindi reindirizzare i pacchetti contrassegnati sull'altro nodo.

Ecco il set di regole di 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

Le prime due regole segnano i pacchetti appartenenti ai nuovi flussi (123.123.123.123 è il VIP keepalived usato sull'haproxy per legare i frontend).

La terza e la quarta regola contrassegnano i pacchetti FIN / RST. (Non so perché, il target TEE "ignora" i pacchetti FIN / RST).

La quinta regola invia un duplicato di tutti i pacchetti contrassegnati all'altro HAproxy (192.168.0.2).

La sesta regola elimina i pacchetti appartenenti a nuovi flussi per impedire il raggiungimento della destinazione originale.

Ricorda di disabilitare rp_filter su interfacce o il kernel eliminerà quei pacchetti marziani.

E, ultimo ma non meno importante, attenzione ai pacchetti di ritorno! Nel mio caso c'è un routing asimmetrico (le richieste arrivano al client -> haproxy1 -> haproxy2 -> webserver, e le risposte passano dal webserver -> haproxy1 -> client), ma non influisce. Funziona bene

So che la soluzione più elegante sarebbe quella di usare iproute2 per eseguire il trasferimento, ma ha funzionato solo per il primo pacchetto SYN. Quando ha ricevuto l'ACK (terzo pacchetto dell'handshake a 3 vie), non lo ha contrassegnato :( Non ho potuto dedicare molto tempo a indagare, non appena ho visto che funziona con il target TEE, lo ha lasciato lì. Naturalmente, sentiti libero di provarlo con iproute2.

Fondamentalmente, la "ricarica aggraziata" funziona così:

  1. Abilito il set di regole di iptables e vedo immediatamente le nuove connessioni che vanno all'altro HAproxy.
  2. Tengo d'occhio "netstat -an | grep ESTABLISHED | wc -l" per supervisionare il processo di "drenaggio".
  3. Una volta che ci sono solo poche (o zero) connessioni, "ifconfig dummy0 down" per forzare il keepalived al failover, quindi tutto il traffico passerà all'altro proxy HA.
  4. Rimuovo il set di regole di iptables
  5. (Solo per "keep-prevent config" keepalive config) "ifconfig dummy0 up".

Il set di regole di IPtables può essere facilmente integrato in uno script di avvio / arresto:

#!/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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.