Recarga normal HAProxy sem perda de pacotes


42

Estou executando um servidor de balanceamento de carga HAProxy para equilibrar a carga em vários servidores Apache. 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, uma recarga 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 normalmente" 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, ainda está caindo 0,24% em média.

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


6
Se você precisar de tanta confiabilidade, uma solução melhor seria executar mais de uma instância do HAproxy, na qual você pode levar um fora de serviço para recarregar, coloque-o novamente e repita o procedimento para os outros.
Yoonix 7/03/14

Respostas:


32

De acordo com https://github.com/aws/opsworks-cookbooks/pull/40 e, consequentemente, http://www.mail-archive.com/haproxy@formilux.org/msg06885.html, 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

Isso tem o efeito de eliminar o SYN antes de uma reinicialização, para que os clientes reenviem esse SYN até que ele atinja o novo processo.



ambos os comandos me deram o seguinte: iptables v1.4.14: invalid port/service --syn 'especificado'
Dmitri DB

5
@DmitriDB que você deve substituir $PORTpela porta real que haproxyestá ouvindo. Se o haproxy estiver escutando em várias portas, escreva substituir --dport $PORTpor --dports $PORTS_SEPARATED_BY_COMMAS, por exemplo --dports 80,443,.
pepoluan

11
iptables 1.4.7 (Centos 6.7) - você também deve especificar -m mulitport se quiser usar --dports. Portanto, a sua "iptables ENTRADA -I -p tcp -m multiport --dports 80,443 --syn -j DROP" e da mesma forma para o -D
carpii

25

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

Recarregamentos HAProxy com zero tempo de inatividade verdadeiro

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

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

Atrasando os pacotes SYN que chegam aos nossos balanceadores de carga HAProxy que são executados em cada máquina, podemos impactar minimamente o tráfego durante as recargas do HAProxy, o que nos permite adicionar, remover e alterar as infra-estruturas de serviço em nossa SOA sem medo de afetar significativamente o tráfego do usuário.

# 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

Felicidades ao Yelp por compartilhar idéias incríveis.


Excellent link! Mas talvez você queira resumir aqui, caso o link expire. Essa é a única razão para nenhum voto positivo.
Matt

O @Matt adicionou alguns trechos e exemplos de código #
Steve Jansen

8

Há outra maneira muito mais simples de recarregar a haproxy com o tempo de inatividade zero real - ele é chamado iptables flipping (o artigo é realmente a resposta Unbounce à solução do 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 nas seguintes etapas:

  1. Vamos ter um par de instâncias haproxy - a primeira ativa que recebe um tráfego e a segunda em espera que não recebe nenhum tráfego.
  2. Você reconfigura (recarrega) a instância em 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 tornará novo ativo . O Unbounce fornece o script bash, que faz o flip com alguns iptablecomandos simples .
  4. Por um momento, você tem duas instâncias ativas. Você precisa esperar até que as conexões abertas com o ativo antigo parem. O tempo depende do comportamento do serviço e das configurações de manutenção.
  5. Tráfego para paradas ativas antigas que se tornam um novo modo de 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, pois você pode testar a configuração em espera antes de ficar on-line.


4

Editar: Minha resposta assume que o kernel envia tráfego apenas para a porta mais recente a ser aberta com SO_REUSEPORT, enquanto na verdade envia tráfego para todos os processos, conforme descrito em um dos comentários. Em outras palavras, a dança do iptables ainda é necessária. :(

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

O processo que o haproxy leva ao reiniciar é:

1) Tente definir SO_REUSEPORT ao abrir a porta ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) Tente abrir a porta (terá êxito com SO_REUSEPORT)

3) Se não tiver êxito, sinalize o processo antigo para fechar sua porta, aguarde 10ms e tente tudo novamente. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

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


Isso acontecerá SO_REUSEPORTem algum cenário específico - especialmente sob tráfego pesado. Quando o SYN é enviado para o antigo processo haproxy e, no mesmo momento, fecha o soquete de escuta, o que resulta em RST. Consulte o artigo do Yelp mencionado em outra resposta acima.
gertas

4
Isso é péssimo ... Apenas para resumir o problema, o Linux distribui novas conexões entre todos os processos que escutam em uma porta específica quando SO_REUSEPORT é usado, para que haja um curto período de tempo em que o processo antigo ainda terá conexões colocadas em sua fila.
Jason Stubbs

2

Vou explicar minha configuração e como resolvi as recargas graciosas:

Eu tenho uma configuração típica com 2 nós executando HAproxy e keepalived. As faixas mantidas estão em interface com dummy0, para que eu possa fazer um "ifconfig dummy0 down" para forçar a alternância.

O verdadeiro problema é que, não sei por que, uma "recarga haproxy" ainda descarta todas as conexões ESTABELECIDAS :( Tentei o "iptables flipping" proposto por gertas, mas encontrei alguns problemas porque ele executa um NAT no destino Endereço IP, que não é uma solução adequada em alguns cenários.

Em vez disso, decidi usar um hack sujo do CONNMARK para marcar pacotes pertencentes a novas conexões e 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 de manutenção permanente usado no haproxy para vincular as interfaces).

Terceira e quarta regras marcam pacotes FIN / RST. (Não sei por que, o destino TEE "ignora" pacotes FIN / RST).

A 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 nas interfaces ou o kernel descartará esses pacotes marcianos.

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

Eu sei que a solução mais elegante seria usar o iproute2 para desviar, mas funcionou apenas para o primeiro pacote SYN. Quando recebeu o ACK (terceiro pacote do handshake de três vias), não o marcou :( Não pude gastar muito tempo para investigar, assim que vi que ele trabalha com o alvo TEE, ele o deixou lá. Obviamente, sinta-se à vontade para experimentá-lo com o iproute2.

Basicamente, a "atualização graciosa" funciona assim:

  1. Ativei o conjunto de regras iptables e imediatamente vi as novas conexões indo para o outro HAproxy.
  2. Fico de olho em "netstat -an | grep ESTABELECIDO | wc -l" para supervisionar o processo de "drenagem".
  3. Quando houver apenas algumas conexões (ou zero), "ifconfig dummy0 down" para forçar a manutenção do failover, todo o tráfego será direcionado para o outro HAproxy.
  4. Eu removo o conjunto de regras iptables
  5. (Apenas para "keepalive config" sem antecipação ")" ifconfig dummy0 up ".

O conjunto de regras IPtables pode ser facilmente integrado a um script 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.