nginxでのSlashdot効果の検出


10

リファラーからのヒットがしきい値を超えた場合にNginxに通知させる方法はありますか?

たとえば、私のウェブサイトがスラッシュドットで特集されていて、突然1時間に2Kヒットが来る場合、1Kヒットが1時間を超えたときに通知を受ける必要があります。

Nginxでこれを行うことは可能ですか?おそらくルアなし?(私の製品はluaコンパイルされていないため)


4
「スラッシュドット」とは?
ewwhite 2012

私はngixでddosを検出するためにこのようなことをしました。アクセスログを解析して実現しました。私はcronジョブを実行して、アクセスログを解析し、1時間あたりの一意のIP接続をカウントしました。
Hex

8
nginxがDiceに購入されたかどうかを検出できるようにしたいですか?
MDMarra 2012

1
@Hexそれ(そしておそらくあなたのスクリプトからのいくつかのスニペット)はこの質問への素晴らしい答えを作るでしょう:)
voretaq7

3
おそらくもうスラッシュドット化を心配する必要はありません。ウェブサーバーは、1時間に4つの接続を処理できる必要があります。しかし、Reddittedになることを心配したいかもしれません...
HopelessN00b

回答:


3

最も効率的なソリューションtail -faccess.log、を実行し、$http_refererフィールドを追跡するデーモンを作成することです。

ただし、迅速でダーティなソリューションは、追加のaccess_logファイルを追加$http_refererし、カスタムlog_formatで変数のみをログに記録し、ログをX分ごとに自動的にローテーションすることです。

  • これは、ファイルを再度開くためにnginxの適切な再起動を必要とする可能性がある標準のlogrotateスクリプトを使用して実行できます(たとえば、標準の手順では、SOの/ a / 15183322を見て、簡単な時間をとります-ベースのスクリプト)…

  • または、内access_logで変数を使用することによって$time_iso8601mapまたはif(ディレクティブを配置する場所に応じて)ディレクティブの助けを借りて、分単位の仕様を取り除くことができますaccess_log

したがって、上記の場合http_referer.Txx{0,1,2,3,4,5}x.log、たとえば、各ファイルを区別するために分の最初の桁を取得することにより、それぞれが10分の期間をカバーする6つのログファイルを持つことができます。

今、あなたがしなければならないことは、10分ごとに実行できる単純なシェルスクリプトcat、上記のすべてのファイルを一緒にパイプしsort、それをにパイプしuniq -c、にsort -rn、にhead -16、そして、そして最も一般的な16のRefererバリエーションのリストを持っていることです—数値とフィールドの組み合わせが基準を超えているかどうかを自由に判断し、通知を実行します。

その後、単一の正常な通知の後で、これらの6つのファイルすべてを削除し、その後の実行では、6つのファイルすべて(および/または、適切と思われる他の特定の番号)が存在しない限り、通知を発行しません。


これはとても便利に見えます。質問が多すぎるかもしれませんが、以前の回答のように、スクリプトを手伝っていただけませんか。
Quintin Par 2017

@QuintinParそれはカリキュラムのように聞こえます!;-)必要に応じて、私はレンタルおよびコンサルティングをご利用いただけます。私のメールはcnst++@FreeBSD.orgで、これもConstantine.SUにあります
cnst

完全に理解します。これまでのすべての助けに感謝します。私はいつかあなたに余裕があることを願っています:-)
Quintin Par

1
@QuintinParどういたしまして!心配ありません。上記の仕様を備えた非常に単純なスクリプトである必要があります。基本的には、テスト、構成、およびパッケージ化の問題だけです。:)
cnst 2017

1
あなたはスーパーヒーローです!
Quintin Par

13

これは、logtailとgrepを使用した方がはるかに良いと思います。インラインでluaを使用することは可能ですが、すべてのリクエストでそのオーバーヘッドが発生することは望ましくありません。また、スラッシュドット化されている場合は特にそうしたくありません。

これは5秒バージョンです。スクリプトに貼り付け、読みやすいテキストを周囲に配置すれば、あなたは黄金色になります。

5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"

もちろん、これはreddit.comやfacebook.com、そして大量のトラフィックを送信する可能性のある他のすべてのサイトを完全に無視します。言うまでもなく、100の異なるサイトがそれぞれ20の訪問者を送ります。リファラーに関係なく、おそらくあなたに電子メールが送信される原因となる単純な古いトラフィックしきい値があるはずです。


1
問題は、積極的に取り組むことです。どのサイトからも知る必要があります。もう1つの質問は、しきい値をどこに置くかです。追加のログ解析を意味しましたか?また、fourmilab.ch / webtools / logtailに
Quintin Par

しきい値は、サーバーが処理できるトラフィックの量によって異なります。あなただけが設定できます。より迅速な通知が必要な場合は、1時間ごとではなく5分ごとに実行し、しきい値を12で割ります。この-o オプションはオフセットファイル用で、次回どこから読み取りを開始するかがわかります
Ladadadada 2012

@Ladadadada、オーバーヘッドがかなり大きいことに同意しません。私の解決策を参照してください— serverfault.com/a/870537/110020 —これが適切に実装されている場合、特に(1)、バックエンドが本当に遅い場合、このオーバーヘッドは無視できます、または、(2)バックエンドが既にかなり細かく、および/または適切にキャッシュされている場合、そもそもトラフィック処理にほとんど問題がなく、少し余分な負荷がかかるはずです。 tへこみを作ります。全体として、この質問には2つの使用例があるように思われます。(1)通知されているだけ、および(2)自動スケーリングです。
cnst 2017

4

nginx limit_req_zoneディレクティブは、$ http_referrerを含む任意の変数に基づいてゾーンを作成できます。

http {
    limit_req_zone  $http_referrer  zone=one:10m   rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req   zone=one  burst=5;
        }

ただし、リファラーヘッダーは非常に長く多様であり、無限の多様性が見られる場合があるため、Webサーバーで必要な状態の量を制限するために何かを実行することもできます。nginx split_clients機能を使用して、リファラーヘッダーのハッシュに基づくすべてのリクエストの変数を設定できます。以下の例では10バケットしか使用していませんが、1000でも簡単に実行できます。したがって、スラッシュドットを取得した場合、参照元がスラッシュドットURLと同じバケットにハッシュしてしまうユーザーもブロックされますが、split_clientsで1000バケットを使用することで、訪問者の0.1%に制限できます。

これは次のようになります(まったくテストされていませんが、方向は正しいです)。

http {

split_clients $http_referrer $refhash {
               10%               x01;
               10%               x02;
               10%               x03;
               10%               x04;
               10%               x05;
               10%               x06;
               10%               x07;
               10%               x08;
               10%               x09;
               *                 x10;
               }

limit_req_zone  $refhash  zone=one:10m   rate=1r/s;

...

server {

    ...

    location /search/ {
        limit_req   zone=one  burst=5;
    }

これは興味深いアプローチです。ただし、スラッシュドット効果が発生しているときの自動アラートに関する質問だと思います。あなたの解決策は、ランダムに約10%のユーザーをブロックすることで解決するようです。さらに、私はあなたの使用理由split_clientsが誤った情報である可能性があると信じています— limit_req「漏出バケット」に基づいています。つまり、全体的な状態が指定されたゾーンのサイズを超えてはなりません。
cnst 2017

2

はい、もちろんそれはNGINXで可能です!

あなたができることは、次のDFAを実装することです:

  1. に基づいてレート制限を実装し、値を正規化$http_refererするmapためにa からregexを使用する可能性があります。制限を超えると、内部エラーページが表示さerror_page、関連する質問に従ってハンドラーを介してキャッチできます。内部リダイレクトとして新しい内部ロケーションに移動します(クライアントには表示されません)。

  2. 上記の場所で制限を超えている場合は、アラートリクエストを実行して、外部ロジックに通知を実行させます。このリクエストはその後キャッシュされ、指定された時間枠ごとに1つの一意のリクエストのみが確実に取得されます。

  3. 以前のリクエストのHTTPステータスコードをキャッチし(ステータスコード≥300を返しproxy_intercept_errors on、を使用するか、not-built-by-defaultをauth_request使用add_after_bodyするか、「無料」のサブリクエストを作成して)、あたかも元のリクエストを完了します。前のステップは関与していませんでした。これを機能させるには、再帰的なerror_page処理を有効にする必要があることに注意してください。

これが私のPoCとMVPです。これもhttps://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.confにあります

limit_req_zone $http_referer zone=slash:10m rate=1r/m;  # XXX: how many req/minute?
server {
    listen 2636;
    location / {
        limit_req zone=slash nodelay;
        #limit_req_status 429;  #nginx 1.3.15
        #error_page 429 = @dot;
        error_page 503 = @dot;
        proxy_pass http://localhost:2635;
        # an outright `return 200` has a higher precedence over the limit
    }
    recursive_error_pages on;
    location @dot {
        proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
        # if you don't have `resolver`, no URI modification is allowed:
        #proxy_pass http://localhost:2637;
        proxy_intercept_errors on;
        error_page 429 = @slash;
    }
    location @slash {
        # XXX: placeholder for your content:
        return 200 "$uri: we're too fast!\n";
    }
}
server {
    listen 2635;
    # XXX: placeholder for your content:
    return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
        max_size=64m keys_zone=slashdotted:10m;
server {
    # we need to flip the 200 status into the one >=300, so that
    # we can then catch it through proxy_intercept_errors above
    listen 2637;
    error_page 429 @/.;
    return 429;
    location @/. {
        proxy_cache slashdotted;
        proxy_cache_valid 200 60s;  # XXX: how often to get notifications?
        proxy_pass http://localhost:2638;
    }
}
server {
    # IRL this would be an actual script, or
    # a proxy_pass redirect to an HTTP to SMS or SMTP gateway
    listen 2638;
    return 200 authorities_alerted\n;
}

これは期待どおりに機能することに注意してください。

% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!

127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%

あなたは予想通り1台のフロントエンドと1つのバックエンドでの最初の要求の結果は、ヒットすることを確認することができます(私が持っている場所にダミーのバックエンドを追加する必要がありましたlimit_reqので、return 200限界よりも優先だろう、本当のバックエンドは必要ありません残りの処理)。

2番目のリクエストは制限を超えているため、アラート(getting 200)を送信してキャッシュし、戻ります429(これは、前述の制限のため、300未満のリクエストはキャッチできないために必要です)。その後、フロントエンドによってキャッチされます、これは無料です。今は自由に好きなことを行うことができます。

3番目のリクエストはまだ制限を超えていますが、アラートはすでに送信されているため、新しいアラートは送信されません。

できた! GitHubでフォークすることを忘れないでください!


2つのレート制限条件が一緒に機能できますか?私は今これを使用しています:serverfault.com/a/869793/26763
Quintin Par '27

@QuintinPar :-)私はそれをどのように使用するかに依存すると思います—明らかな問題は、どの制限の条件を導入した単一の場所で区別することでしょう。しかし、これがlimit_reqであり、もう一方がであるlimit_conn場合は、limit_req_status 429上記を使用するだけで(非常に新しいnginxが必要)、あなたは黄金であるべきだと思います。他のオプションがあるかもしれません(確かに機能するのはnginx w /をチェーンすることですset_real_ip_fromが、何をしたいかによっては、より効率的な選択肢があるかもしれません)。
cnst 2017

@QuintinPar私の答えに欠けているものがあれば、知らせてください。ところで、制限に達してスクリプトが呼び出されると、そのようなスクリプトがnginxによって適切にキャッシュされるまで、コンテンツが遅延する可能性があることに注意してください。たとえば、スクリプトをのようgolangに非同期的に実装したり、アップストリームのタイムアウトオプションを調べたりすることができます。また、proxy_cache_lock on同様に使用したい場合があり、スクリプトが失敗した場合に何をすべきかについてエラー処理を追加することもできます(たとえば、error_page同様に使用proxy_intercept_errors)。私のPOCは良いスタートだと思います。:)
cnst 2017

試していただきありがとうございます。それでも私にとって大きな問題の1つは、limit_reqとlimit_connをすでにhttpレベルで使用していて、それが私が持っているすべてのWebサイトに当てはまることです。これは上書きできません。そのため、このソリューションでは、他の機能を使用しています。このソリューションに対する他のアプローチはありますか?
Quintin Par

@QuintinParネストされたnginxインスタンスがある場合はどうでしょうか。それぞれがlimit_req/の単一セットを使用しlimit_connますか?たとえば、上記の構成を現在のフロントエンドサーバーの前に配置するだけです。set_real_ip_from上流のnginxで使用して、IPが正しく処理されることを確認できます。それ以外の場合、それでも適合しない場合は、厳密な制約と仕様をより明確に明確にする必要があると思います-どのトラフィックレベルについて話しているのですか?統計はどれくらいの頻度で実行する必要がありますか(1分/ 5分/ 1時間)?古いlogtailソリューションの何が問題になっていますか?
cnst 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.