Recarregar HAProxy gracioso com perda zero de pacotes

35

Estou executando um servidor de balanceamento de carga HAProxy para balancear a carga em vários servidores Apache. Eu preciso recarregar o HAProxy a qualquer momento para alterar o algoritmo de balanceamento de carga.

Isso tudo funciona bem, exceto pelo fato de eu ter que recarregar o servidor sem perder um único pacote (no momento em que um recarregamento está me dando 99,76% de sucesso em média, com 1000 solicitações por segundo por 5 segundos). Eu fiz muitas horas de pesquisa sobre isso, e encontrei o seguinte comando para "recarregar graciosamente" o servidor HAProxy:

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

No entanto, isso tem pouco ou nenhum efeito em relação ao antigo service haproxy reload , e ainda está diminuindo em média 0,24%.

Existe alguma maneira de recarregar o arquivo de configuração HAProxy sem um único pacote descartado de qualquer usuário?

    
por Conor Taylor 07.03.2014 / 20:00

5 respostas

29

De acordo com o link e, consequentemente, link você pode:

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

This has the effect of dropping the SYN before a restart, so that clients will resend this SYN until it reaches the new process.

    
por 07.03.2014 / 22:48
24

O Yelp compartilhou uma abordagem mais sofisticada com base em testes meticulosos. O artigo do blog é um mergulho profundo e valeu a pena o investimento de tempo para apreciá-lo completamente.

Recarregamentos reais do HAProxy de inatividade zero

tl; dr usam Linux tc (controle de tráfego) e iptables para enfileirar temporariamente pacotes SYN enquanto o HAProxy está recarregando e tem dois pids conectados à mesma porta ( SO_REUSEPORT ).

Não me sinto à vontade para publicar novamente o artigo inteiro no ServerFault; No entanto, aqui estão alguns trechos para despertar seu interesse:

By delaying SYN packets coming into our HAProxy load balancers that run on each machine, we are able to minimally impact traffic during HAProxy reloads, which allows us to add, remove, and change service backends within our SOA without fear of significantly impacting user traffic.

# 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: link

Aplausos ao Yelp por compartilhar esses insights incríveis.

    
por 21.05.2015 / 00:49
7

Existe uma outra maneira muito mais simples de recarregar o haproxy com um verdadeiro tempo de inatividade zero - ele é chamado de iptables invertendo (o artigo é, na verdade, a resposta Unbounce à solução Yelp). É mais limpo do que a resposta aceita, pois não há necessidade de descartar pacotes que possam causar problemas com recargas longas.

Resumidamente, a solução consiste nos seguintes passos:

  1. Vamos ter um par de instâncias haproxy - o primeiro ativo que recebe um tráfego e o segundo em espera que não recebe tráfego.
  2. Você reconfigura (recarrega) a instância de espera a qualquer momento.
  3. Quando o modo de espera estiver pronto com a nova configuração, você desviará todas as NOVAS conexões para o nó de espera, que se torna novo ativo . O Unbounce fornece o script bash que faz o flip com alguns comandos simples iptable .
  4. Por um momento você tem duas instâncias ativas. Você precisa esperar até que as conexões abertas para old active sejam encerradas. O tempo depende do seu comportamento de serviço e das configurações de manutenção ativa.
  5. O tráfego para as antigas paradas que se torna nova espera - você está de volta na etapa 1.

Além disso, a solução pode ser adotada para qualquer tipo de serviço (nginx, apache, etc.) e é mais tolerante a falhas, já que você pode testar a configuração de espera antes de ficar on-line.

    
por 31.12.2015 / 01:22
3

Se você estiver em um kernel que suporta SO_REUSEPORT, esse problema não deve acontecer.

O processo que o haproxy toma quando reinicia é:

1) Tente definir SO_REUSEPORT ao abrir a porta ( link )

2) Tente abrir a porta (será bem-sucedida com SO_REUSEPORT)

3) Se não tiver êxito, sinalize o processo antigo para fechar sua porta, aguarde 10ms e tente tudo de novo. ( link )

Foi suportado pela primeira vez no kernel do Linux 3.9, mas algumas distribuições o retrocederam. Por exemplo, os kernels EL6 de 2.6.32-417.el6 suportam.

    
por 17.03.2015 / 05:01
1

Eu explicarei minha configuração e como resolvi as recargas graciosas:

Eu tenho uma configuração típica com 2 nós executando o HAproxy e keepalived. Keepalived segue a interface dummy0, então eu posso fazer um "ifconfig dummy0 down" para forçar a mudança.

O verdadeiro problema é que, não sei por que, um "recarregamento haproxy" ainda abandona todas as conexões ESTABLISHED :( eu tentei o "iptables flipping" proposto pelo gertas, mas encontrei alguns problemas porque ele executa um NAT no endereço IP de destino, o que não é uma solução adequada em alguns cenários.

Em vez disso, decidi usar um ataque CONNMARK para marcar pacotes pertencentes a conexões NOVAS e, em seguida, redirecionar esses pacotes marcados para o outro nó.

Aqui está o conjunto de regras do 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

As duas primeiras regras marcam os pacotes pertencentes aos novos fluxos (123.123.123.123 é o VIP keepalived usado no haproxy para ligar os frontends).

Terceira e quarta regras marcam pacotes FIN / RST. (Eu não sei porque, alvo TEE "ignora" pacotes FIN / RST).

Quinta regra envia uma duplicata de todos os pacotes marcados para o outro HAproxy (192.168.0.2).

A sexta regra descarta pacotes pertencentes a novos fluxos para evitar atingir seu destino original.

Lembre-se de desabilitar o rp_filter em interfaces ou o kernel removerá esses pacotes marcianos.

E por último mas não menos importante, lembre-se dos pacotes que retornam! No meu caso, há roteamento assimétrico (as solicitações chegam ao cliente - > haproxy1 - > haproxy2 - > servidor da Web, e as respostas vão do servidor da Web - > haproxy1 - > cliente), mas isso não afeta. Funciona bem.

Eu sei que a solução mais elegante seria usar o iproute2 para fazer o desvio, mas ele só funcionou para o primeiro pacote SYN. Quando recebeu o ACK (3º pacote do handshake 3-way), ele não marcou :( Eu não poderia gastar muito tempo para investigar, assim que eu vi ele funciona com o alvo TEE, ele deixou lá. Claro, sinta-se à vontade para experimentar com o iproute2.

Basicamente, o "recarregamento normal" funciona assim:

  1. Eu ativo o conjunto de regras iptables e vejo imediatamente as novas conexões indo para o outro HAproxy.
  2. Fico de olho em "netstat -an | grep ESTABLISHED | wc -l" para supervisionar o processo de "drenagem".
  3. Uma vez que há apenas algumas (ou zero) conexões, "ifconfig dummy0 down" para forçar keepalived para failover, então todo o tráfego irá para o outro HAproxy.
  4. eu removo o conjunto de regras iptables
  5. (somente para configuração keepalive "não preempting") "ifconfig dummy0 up".

O conjunto de regras IPtables pode ser facilmente integrado em um script de início / parada:

#!/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
    
por 03.05.2017 / 19:05