web-dev-qa-db-ja.com

パケット損失ゼロのHAProxyグレースフルリロード

HAProxy負荷分散サーバーを実行して、複数のApacheサーバーへの負荷を分散しています。ロードバランシングアルゴリズムを変更するには、いつでもHAProxyをリロードする必要があります。

これはすべて正常に機能しますが、1つのパケットを失うことなくサーバーをリロードする必要があるという事実を除きます(現時点では、リロードによって平均で99.76%の成功が得られ、1秒あたり1000リクエストで5秒間)。私はこれについて何時間もの調査を行った結果、HAProxyサーバーを「正常に再ロード」するための次のコマンドを見つけました。

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

ただし、これは通常のservice haproxy reloadと比較してほとんどまたはまったく効果がなく、平均で0.24%低下しています。

ユーザーからパケットを1つドロップせずにHAProxy構成ファイルをリロードする方法はありますか?

42
Conor Taylor

https://github.com/aws/opsworks-cookbooks/pull/4 によると、その結果 http://www.mail-archive.com/[email protected]/ 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を再送信します。

32
Mxx

Yelpは、綿密なテストに基づいたより洗練されたアプローチを共有しました。ブログ記事は深く掘り下げているので、十分に感謝するために時間を費やす価値があります。

真のゼロダウンタイムHAProxyリロード

tl; dr Linuxのtc(トラフィック制御)とiptablesを使用して、HAProxyがリロードされ、同じポート(SO_REUSEPORT)。

私は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/97e3505a1e92e35de2c

このような驚くべき洞察を共有してくれて、Yelpに乾杯。

27
Steve Jansen

真のダウンタイムなしでhaproxyをリロードする別のはるかに簡単な方法があります-それは iptables flipping という名前です(記事は実際にYelpソリューションへのUnbounce応答です)。長いリロードで問題を引き起こす可能性のあるパケットをドロップする必要がないため、受け入れられた回答よりもきれいです。

簡単に言うと、ソリューションは次のステップで構成されています。

  1. 一対のhaproxyインスタンスがあるとします-最初のアクティブはトラフィックを受信し、2番目のインスタンスはトラフィックを受信しません。
  2. スタンバイインスタンスはいつでも再構成(リロード)できます。
  3. 新しい設定でスタンバイの準備ができたら、new activeになるすべての新しい接続をスタンバイノードに転送します。 Unbounceは いくつかの単純なiptableコマンドでフリップを行うbashスクリプト を提供します。
  4. しばらくの間、2つのアクティブなインスタンスがあります。 old activeへの開かれた接続が終了するまで待機する必要があります。時間は、サービスの動作とキープアライブ設定によって異なります。
  5. 古いアクティブへのトラフィックが停止し、新しいスタンバイになります-戻ってきましたステップ1。

さらに、このソリューションはあらゆる種類のサービス(nginx、Apacheなど)に適用でき、オンラインになる前にスタンバイ構成をテストできるため、フォールトトレラントです。

8
gertas

編集:私の答えは、カーネルがSO_REUSEPORTで開かれる最新のポートにのみトラフィックを送信することを想定していますが、コメントの1つで説明されているように、実際にはすべてのプロセスにトラフィックを送信します。つまり、iptablesダンスはまだ必要です。 :(

SO_REUSEPORTをサポートするカーネルを使用している場合、この問題は発生しません。

再起動時にhaproxyが実行するプロセスは次のとおりです。

1)ポートを開くときにSO_REUSEPORTを設定してみてください( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/proto_tcp.c#L792-L798

2)ポートを開いてみてください(SO_REUSEPORTで成功します)

3)成功しなかった場合は、古いプロセスにシグナルを送信してポートを閉じ、10ミリ秒待ってからもう一度試してください。 ( https://github.com/haproxy/haproxy/blob/3cd0ae963e958d5d5fb838e120f1b0e9361a92f8/src/haproxy.c#L1554-L1577

Linux 3.9カーネルで最初にサポートされましたが、一部のディストリビューションではそれをバックポートしています。たとえば、2.6.32-417.el6のEL6カーネルはそれをサポートしています。

4
Jason Stubbs

私のセットアップと、適切なリロードを解決する方法を説明します。

HAproxyを実行し、keepalivedを実行している2つのノードでの典型的なセットアップがあります。 Keepalivedはインターフェースのdummy0を追跡するので、「ifconfig dummy0 down」を実行して強制的に切り替えます。

本当の問題は、なぜだかわかりませんが、「haproxy reload」がまだすべての確立された接続を落とすことです:(gertasによって提案された「iptablesフリッピング」を試しましたが、NAT宛先IPアドレス。これは、一部のシナリオでは適切なソリューションではありません。

代わりに、CONNMARKダーティーハックを使用して、新しい接続に属するパケットをマークし、マークされたパケットを他のノードにリダイレクトすることにしました。

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

最初の2つのルールは、新しいフローに属するパケットをマークします(123.123.123.123は、フロントエンドをバインドするためにhaproxyで使用されるkeepalived VIPです)。

3番目と4番目のルールは、パケットにFIN/RSTパケットをマークします。 (理由はわかりませんが、TEEターゲットはFIN/RSTパケットを「無視」します)。

5番目のルールは、マークされたすべてのパケットの複製を他のHAproxy(192.168.0.2)に送信します。

6番目のルールは、新しいフローに属するパケットをドロップして、元の宛先に到達しないようにします。

インターフェイスでrp_filterを無効にすることを忘れないでください。そうしないと、カーネルはそれらの火星のパケットをドロップします。

そして最後に重要なことですが、戻ってくるパケットに注意してください!私の場合、非対称ルーティング(リクエストはクライアント-> haproxy1-> haproxy2-> webserverに送られ、応答はwebserver-> haproxy1-> clientから送られます)ですが、影響はありません。正常に動作します。

最もエレガントな解決策はiproute2を使用して転送を行うことですが、最初のSYNパケットに対してのみ機能しました。 ACK(3ウェイハンドシェイクの3番目のパケット)を受信したとき、それをマークしませんでした:(TEEターゲットで動作することを確認するとすぐに、調査に時間をかけることができませんでした。もちろん、iproute2でお試しください。

基本的に、「グレースフルリロード」は次のように機能します。

  1. Iptablesルールセットを有効にすると、他のHAproxyへの新しい接続がすぐに表示されます。
  2. 「排出」プロセスを監視するために、「netstat -an | grep ESTABLISHED | wc -l」を監視しています。
  3. 接続が数個(またはゼロ)になると、 "ifconfig dummy0 down"によってkeepalivedが強制的にフェイルオーバーされるため、すべてのトラフィックが他のHAproxyに送られます。
  4. Iptablesルールセットを削除します
  5. (「プリエンプトしない」キープアライブ構成のみ)「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
2
Vins Vilaplana