패킷 손실이없는 HAProxy 정상 재로드


42

HAProxy로드 밸런싱 서버를 실행하여로드를 여러 Apache 서버로 밸런싱합니다. 로드 밸런싱 알고리즘을 변경하려면 주어진 시간에 HAProxy를 다시로드해야합니다.

단일 패킷을 잃지 않고 서버를 다시로드해야한다는 사실을 제외 하고는이 모든 것이 잘 작동합니다 (재로드가 평균 99.76 %의 성공을 거두며 5 초 동안 초당 1000 건의 요청으로). 이것에 대해 많은 시간을 연구했으며 HAProxy 서버를 "정상적으로 다시로드"하는 다음 명령을 찾았습니다.

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

그러나 이것은 평범한 오래된 것에 비해 거의 또는 전혀 영향을 미치지 않으며 service haproxy reload여전히 평균 0.24 % 감소합니다.

사용자로부터 단일 패킷 손실없이 HAProxy 구성 파일을 다시로드하는 방법이 있습니까?


6
높은 안정성이 필요한 경우 하나 이상의 서비스를 중단하여 다시로드 할 수있는 HAproxy 인스턴스를 두 개 이상 실행하는 것이 더 나은 솔루션 일 것입니다.
yoonix

답변:


32

https://github.com/aws/opsworks-cookbooks/pull/40 및 이에 따라 http://www.mail-archive.com/haproxy@formilux.org/msg06885.html 에 따르면 다음을 수행 할 수 있습니다.

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

이는 다시 시작하기 전에 SYN을 삭제하는 효과가 있으므로 클라이언트는이 SYN이 새 프로세스에 도달 할 때까지 다시 보냅니다.



:이 두 명령으로 저에게이 준 iptables v1.4.14: invalid port/service --syn 'specified`
드미트리 DB

5
@DmitriDB 당신은 $PORT실제 포트 로 대체하기로되어 haproxy있습니다. haproxy가 여러 포트에서 수신 대기중인 경우 다음 --dport $PORT과 같이 바꾸십시오 ( --dports $PORTS_SEPARATED_BY_COMMAS예 :) --dports 80,443.
pepoluan

1
iptables 1.4.7 (Centos 6.7)---dports를 사용하려면 -m mulitport도 지정해야합니다. 그 그래서 "-I INPUT을 iptables는 -p TCP -m 멀티 --dports 80443 --syn -j DROP"는 -D에 대한 마찬가지로 및
carpii

25

Yelp는 세심한 테스트를 기반으로보다 정교한 접근 방식을 공유했습니다. 블로그 기사는 심도 깊은 다이빙이며 시간을 투자하여 가치를 충분히 인정할 가치가 있습니다.

진정한 제로 다운 타임 HAProxy 재로드

tl; dr은 HAProxy가 다시로드되고 동일한 포트 ( SO_REUSEPORT)에 두 개의 pid가 연결되어있는 동안 Linux tc (트래픽 제어) 및 iptables를 사용하여 SYN 패킷을 일시적으로 대기열에 넣습니다 .

ServerFault에 대한 전체 기사를 다시 게시하는 것이 불편합니다. 그럼에도 불구하고 관심을 끌기위한 몇 가지 발췌문이 있습니다.

각 시스템에서 실행되는 HAProxy로드 밸런서로 들어오는 SYN 패킷을 지연시킴으로써 HAProxy 재로드시 트래픽에 미치는 영향을 최소화 할 수 있으므로 사용자 트래픽에 큰 영향을 줄 염려없이 SOA 내에서 서비스 백엔드를 추가, 제거 및 변경할 수 있습니다.

# 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

요점 : https://gist.github.com/jolynch/97e3505a1e92e35de2c0

놀라운 통찰력을 공유 한 Yelp를 응원합니다.


훌륭한 링크! 그러나 링크가 만료되는 경우 여기에 요약하고 싶을 것입니다. 그것이 공의가없는 유일한 이유입니다.
Matt

@Matt는 발췌 및 코드 샘플을 추가했습니다
Steve Jansen

8

진정한 다운 타임없이 haproxy를 다시로드하는 또 다른 훨씬 간단한 방법이 있습니다. iptables flipping 이라는 이름이 붙습니다 (이 기사는 실제로 Yelp 솔루션에 대한 Unbounce 응답입니다). 긴 재로드에 문제를 일으킬 수있는 패킷을 삭제할 필요가 없으므로 허용되는 답변보다 깨끗합니다.

간단히, 솔루션은 다음 단계로 구성됩니다.

  1. 트래픽을 수신하는 첫 번째 활성 인스턴스와 트래픽을 수신하지 않는 대기중인 두 번째 인스턴스 인 haproxy 인스턴스가 있습니다.
  2. 대기 인스턴스는 언제든지 재구성 (재로드)합니다.
  3. 새 구성으로 대기가 준비되면 모든 새로운 연결을 대기 노드로 전환하여 새 활성이 됩니다. Unbounce는 간단한 iptable명령으로 플립을 수행하는 bash 스크립트를 제공 합니다 .
  4. 잠시 동안 두 개의 활성 인스턴스가 있습니다. 이전 활성에 대한 열린 연결 이 중단 될 때까지 기다려야 합니다. 시간은 서비스 동작 및 연결 유지 설정에 따라 다릅니다.
  5. 활성 상태 가되는 이전 활성 중지로의 트래픽 -1 단계로 돌아갑니다.

또한이 솔루션은 모든 종류의 서비스 (nginx, apache 등)에 적용 할 수 있으며 온라인 상태가되기 전에 대기 구성을 테스트 할 수 있으므로 내결함성이 있습니다.


4

편집 : 내 대답은 커널이 SO_REUSEPORT로 열어야 할 가장 최근 포트로 트래픽을 보내는 반면, 실제로는 의견 중 하나에 설명 된 것처럼 모든 프로세스로 트래픽을 보냅니다. 다시 말해, iptables 댄스는 여전히 필요합니다. :(

SO_REUSEPORT를 지원하는 커널을 사용하는 경우이 문제가 발생하지 않아야합니다.

haproxy가 다시 시작될 때 수행하는 프로세스는 다음과 같습니다.

1) 포트를 열 때 SO_REUSEPORT를 설정하십시오 ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798 )

2) 포트를 열어보십시오 (SO_REUSEPORT로 성공)

3) 성공하지 못하면 이전 프로세스에 신호를 보내 포트를 닫고 10ms 동안 기다렸다가 다시 시도하십시오. ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577 )

Linux 3.9 커널에서 처음 지원되었지만 일부 배포판에서는이를 백 포트했습니다. 예를 들어 2.6.32-417.el6의 EL6 커널이이를 지원합니다.


이 일이 일어날 SO_REUSEPORT특히 교통 체증에서 - 일부 특정 시나리오에서. SYN이 오래된 haproxy 프로세스로 전송되면 동시에 청취 소켓을 닫아 RST가 발생합니다. 위의 다른 답변에서 언급 한 Yelp 기사를 참조하십시오.
gertas

4
SO_REUSEPORT를 사용할 때 Linux는 특정 포트에서 수신 대기하는 모든 프로세스간에 새로운 연결을 분배하므로 기존 프로세스가 큐에 연결을 가져 오는 짧은 시간이 있습니다.
Jason Stubbs

2

설정 내용과 우아한 리로드 문제를 해결하는 방법을 설명하겠습니다.

HAproxy를 실행하고 유지하는 2 개의 노드로 일반적인 설정이 있습니다. Keepalived는 dummy0 인터페이스를 추적하므로 "ifconfig dummy0 down"을 수행하여 강제로 전환 할 수 있습니다.

진짜 문제는, 왜 "하프 프록시 리로드"가 여전히 모든 ESTABLISHED 연결을 삭제하는지 모른다는 것입니다. 일부 시나리오에서는 IP 주소가 적합하지 않습니다.

대신 CONNMARK dirty hack을 사용하여 NEW 연결에 속하는 패킷을 표시 한 다음 표시된 패킷을 다른 노드로 리디렉션하기로 결정했습니다.

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

처음 두 규칙은 새 흐름에 속하는 패킷을 표시합니다 (123.123.123.123은 프런트 엔드를 바인딩하기 위해 haproxy에서 사용되는 keepalived VIP입니다).

세 번째 및 네 번째 규칙은 패킷 FIN / RST 패킷을 표시합니다. TEE가 FIN / RST 패킷을 "무시"하는 이유를 모르겠습니다.

다섯 번째 규칙은 표시된 모든 패킷의 복제본을 다른 HAproxy (192.168.0.2)로 보냅니다.

여섯 번째 규칙은 원래 플로우에 도달하지 못하도록 새 플로우에 속하는 패킷을 삭제합니다.

인터페이스에서 rp_filter를 비활성화하면 커널에서 해당 화성 패킷을 삭제합니다.

마지막으로 돌아 오는 패킷을 염두에 두십시오! 필자의 경우 비대칭 라우팅이 있습니다 (요청은 클라이언트-> haproxy1-> haproxy2-> 웹 서버에오고 응답은 웹 서버-> haproxy1-> 클라이언트에서옵니다). 잘 작동합니다.

가장 우아한 해결책은 iproute2를 사용하여 전환을 수행하는 것이지만 첫 번째 SYN 패킷에서만 작동했습니다. ACK (3 방향 핸드 셰이크의 세 번째 패킷)를 받았을 때 표시하지 않았습니다. (TEE 대상과 작동하는 것을 보자 마자 조사에 많은 시간을 할애 할 수 없었습니다. 물론 iproute2로 자유롭게 사용해보십시오.

기본적으로 "유예 재 장전"은 다음과 같이 작동합니다.

  1. iptables 규칙 세트를 활성화하고 새 연결이 다른 HAproxy로 이동하는 것을 즉시 봅니다.
  2. "배수"프로세스를 감독하기 위해 "netstat -an | grep ESTABLISHED | wc -l"을 주시합니다.
  3. 연결이 몇 개 (또는 0 개) 만 있으면 "ifconfig dummy0 down"을 유지하여 강제로 장애 조치를 유지하므로 모든 트래픽이 다른 HAproxy로 이동합니다.
  4. iptables 규칙 세트를 제거합니다
  5. ( "비 선점"keepalive 구성에만 해당) "ifconfig dummy0 up".

IPtables 규칙 세트는 시작 / 중지 스크립트에 쉽게 통합 될 수 있습니다.

#!/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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.