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構成ファイルをリロードする方法はありますか?
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を再送信します。
Yelpは、綿密なテストに基づいたより洗練されたアプローチを共有しました。ブログ記事は深く掘り下げているので、十分に感謝するために時間を費やす価値があります。
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に乾杯。
真のダウンタイムなしでhaproxyをリロードする別のはるかに簡単な方法があります-それは iptables flipping という名前です(記事は実際にYelpソリューションへのUnbounce応答です)。長いリロードで問題を引き起こす可能性のあるパケットをドロップする必要がないため、受け入れられた回答よりもきれいです。
簡単に言うと、ソリューションは次のステップで構成されています。
iptable
コマンドでフリップを行うbashスクリプト を提供します。さらに、このソリューションはあらゆる種類のサービス(nginx、Apacheなど)に適用でき、オンラインになる前にスタンバイ構成をテストできるため、フォールトトレラントです。
編集:私の答えは、カーネルが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カーネルはそれをサポートしています。
私のセットアップと、適切なリロードを解決する方法を説明します。
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でお試しください。
基本的に、「グレースフルリロード」は次のように機能します。
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