パケット損失のない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%低下しています。

どのユーザーからもドロップされたパケットを1つも使わずにHAProxy構成ファイルをリロードする方法はありますか?


6
そのような信頼性が必要な場合、HAproxyの複数のインスタンスを実行して1つのサービスを停止してリロードし、それを元に戻し、他のインスタンスに対して繰り返すことで、より良いソリューションが得られます。
Yoonix 14年

回答:


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
Dmitri DB

5
@DmitriDB は、リッスンし$PORTている実際のポートに置き換えることになっていますhaproxy。haproxyが複数のポートでリッスンしている場合は、たとえばに置き換え--dport $PORTてください。--dports $PORTS_SEPARATED_BY_COMMAS--dports 80,443
ペポル14

1
iptables 1.4.7(Centos 6.7)---dportsを使用する場合は、-m mulitportも指定する必要があります。そのため、「iptables -I INPUT -p tcp -m multiport --dports 80,443 --syn -j DROP」と同様に-D
carpii

25

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

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

tl; drは、Linux Proxy(トラフィック制御)とiptablesを使用して、HAProxyがリロードされ、同じポートに接続された2つのpidを持つSYNパケットを一時的にキューに入れます(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/97e3505a1e92e35de2c0

このような素晴らしい洞察を共有してくれたYelpに乾杯。


素晴らしいリンク!ただし、リンクの有効期限が切れた場合に備えて、ここに要約してください。それが、賛成票を投じない唯一の理由です。
マット

@Mattは一部抜粋とコードサンプルを添加
スティーヴ・ジャンセンを

8

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

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

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

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


4

編集:私の答えは、カーネルが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)成功しなかった場合は、古いプロセスにそのポートを閉じるように信号を送り、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の記事をご覧ください。
ゲルタス

4
問題を要約すると、LinuxはSO_REUSEPORTが使用されているときに特定のポートでリッスンしているすべてのプロセス間に新しい接続を配布するため、古いプロセスが接続をキューに入れる時間が少しあります。
ジェイソンスタッブス

2

セットアップと、グレースフルリロードの解決方法を説明します。

HAproxyとkeepalivedを実行する2つのノードを使用した一般的なセットアップがあります。Keepalivedはインターフェイスdummy0を追跡するため、「ifconfig dummy0 down」を実行して強制的にスイッチオーバーできます。

本当の問題は、「haproxy reload」がまだ確立されたすべての接続をドロップする理由がわからないことです:(gertasが提案した「iptables flipping」を試しましたが、宛先で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番目のパケット)を受信したとき、それをマークしませんでした:(調査するのに多くの時間を費やすことができませんでした。もちろん、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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.