リファラーからのヒットがしきい値を超えた場合にNginxに通知させる方法はありますか?
たとえば、私のウェブサイトがスラッシュドットで特集されていて、突然1時間に2Kヒットが来る場合、1Kヒットが1時間を超えたときに通知を受ける必要があります。
Nginxでこれを行うことは可能ですか?おそらくルアなし?(私の製品はluaコンパイルされていないため)
リファラーからのヒットがしきい値を超えた場合にNginxに通知させる方法はありますか?
たとえば、私のウェブサイトがスラッシュドットで特集されていて、突然1時間に2Kヒットが来る場合、1Kヒットが1時間を超えたときに通知を受ける必要があります。
Nginxでこれを行うことは可能ですか?おそらくルアなし?(私の製品はluaコンパイルされていないため)
回答:
最も効率的なソリューションtail -f
はaccess.log
、を実行し、$http_referer
フィールドを追跡するデーモンを作成することです。
ただし、迅速でダーティなソリューションは、追加のaccess_log
ファイルを追加$http_referer
し、カスタムlog_format
で変数のみをログに記録し、ログをX分ごとに自動的にローテーションすることです。
これは、ファイルを再度開くためにnginxの適切な再起動を必要とする可能性がある標準のlogrotateスクリプトを使用して実行できます(たとえば、標準の手順では、SOの/ a / 15183322を見て、簡単な時間をとります-ベースのスクリプト)…
または、内access_log
で変数を使用することによって$time_iso8601
、map
または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つのファイルすべて(および/または、適切と思われる他の特定の番号)が存在しない限り、通知を発行しません。
これは、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の訪問者を送ります。リファラーに関係なく、おそらくあなたに電子メールが送信される原因となる単純な古いトラフィックしきい値があるはずです。
-o
オプションはオフセットファイル用で、次回どこから読み取りを開始するかがわかります。
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;
}
split_clients
が誤った情報である可能性があると信じています— limit_req
「漏出バケット」に基づいています。つまり、全体的な状態が指定されたゾーンのサイズを超えてはなりません。
はい、もちろんそれはNGINXで可能です!
あなたができることは、次のDFAを実装することです:
に基づいてレート制限を実装し、値を正規化$http_referer
するmap
ためにa からregexを使用する可能性があります。制限を超えると、内部エラーページが表示さerror_page
れ、関連する質問に従ってハンドラーを介してキャッチできます。内部リダイレクトとして新しい内部ロケーションに移動します(クライアントには表示されません)。
上記の場所で制限を超えている場合は、アラートリクエストを実行して、外部ロジックに通知を実行させます。このリクエストはその後キャッシュされ、指定された時間枠ごとに1つの一意のリクエストのみが確実に取得されます。
以前のリクエストの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番目のリクエストはまだ制限を超えていますが、アラートはすでに送信されているため、新しいアラートは送信されません。
limit_req
であり、もう一方がであるlimit_conn
場合は、limit_req_status 429
上記を使用するだけで(非常に新しいnginxが必要)、あなたは黄金であるべきだと思います。他のオプションがあるかもしれません(確かに機能するのはnginx w /をチェーンすることですset_real_ip_from
が、何をしたいかによっては、より効率的な選択肢があるかもしれません)。
golang
に非同期的に実装したり、アップストリームのタイムアウトオプションを調べたりすることができます。また、proxy_cache_lock on
同様に使用したい場合があり、スクリプトが失敗した場合に何をすべきかについてエラー処理を追加することもできます(たとえば、error_page
同様に使用proxy_intercept_errors
)。私のPOCは良いスタートだと思います。:)
limit_req
/の単一セットを使用しlimit_conn
ますか?たとえば、上記の構成を現在のフロントエンドサーバーの前に配置するだけです。set_real_ip_from
上流のnginxで使用して、IPが正しく処理されることを確認できます。それ以外の場合、それでも適合しない場合は、厳密な制約と仕様をより明確に明確にする必要があると思います-どのトラフィックレベルについて話しているのですか?統計はどれくらいの頻度で実行する必要がありますか(1分/ 5分/ 1時間)?古いlogtail
ソリューションの何が問題になっていますか?