ブログ | NGINX

NGINX 条件付きログによるリクエストのサンプリング

NGINX-F5 水平黒タイプ RGB の一部
オーウェン・ギャレット サムネイル
オーウェン・ギャレット
2019年4月24日公開

NGINX は、処理するすべてのトランザクションの非常に詳細なログを記録できます。 このようなログはアクセス ログと呼ばれ、カスタマイズ可能なログ ファイル形式を使用して、さまざまなサービスや場所について記録される詳細を微調整できます。

デフォルトでは、NGINX は処理するすべてのトランザクションをログに記録します。 これはコンプライアンスやセキュリティ上の理由で必要な場合もありますが、混雑した Web サイトの場合、生成されるデータの量が膨大になる可能性があります。 この記事では、さまざまな基準に基づいてトランザクションを選択的にログに記録する方法と、その知識を使用してリクエストに関するデータ ポイントを迅速かつ軽量にサンプリングする方法を説明します。

特に記載のない限り、この投稿は NGINX Open Source と NGINX Plus の両方に適用されます。 読みやすくするために、全体を通じてNGINXを参照します。

背景 – NGINX アクセス ログ構成の概要

NGINX アクセス ログは、 log_formatディレクティブを使用して定義されます。 いくつかの異なる名前付きログ形式を定義できます。たとえば、 mainという名前の完全なログ形式と、要求に関する 3 つのデータ ポイントを記録するnotesという名前の短縮ログ形式です。

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

log_format notes '$remote_addr "$request" $status';

ログ形式では、 NGINX 変数やログ記録時に計算されたその他の値を参照できます。

次に、 access_logディレクティブを使用して、トランザクションが完了したら NGINX にトランザクションをログに記録するように指示します。 このディレクティブは、ログ ファイルの場所と使用するログ形式を指定します。

access_log /var/log/nginx/access.log メイン;

デフォルトでは、NGINX は次の構成を使用してすべてのトランザクションをログに記録します。

access_log ログ/access.log を結合します。

独自の access_log を定義すると、デフォルトのアクセス ログが上書き (置換) されます。

条件付きログ

場合によっては、特定のリクエストのみをログに記録したい場合があります。 これは、次のように条件付きログ記録を使用して行われます。

server {
listen 80;

set $logme 0;
if ( $uri ~ ^/secure ) {
set $logme 1;
}

# 監査人は /secure へのリクエストに対して追加のログを必要とします
access_log /var/log/nginx/secure.log notes if=$logme;

# グローバル アクセス ログがある場合は、ここで再宣言する必要があります
access_log /var/log/nginx/access.log main;

location / {
# ...
}
}

アクセスログは継承されない

アクセス ログ設定はスタックまたは継承されません。1 つのコンテキストのaccess_logディレクティブは、親コンテキストで宣言されたアクセス ログを上書き (置き換え) します。

たとえば、URI /secureへのトラフィックに関する追加情報をログに記録する場合は、 location /secure {...}ブロックにアクセス ログを定義できます。 このアクセス ログは、構成内の他の場所で定義された一般的なアクセス ログを置き換えます。

前のセクションの例ではこの問題に対処しています。 同じコンテキストで 2 つのアクセス ログを使用し、 /secureへの要求を専用のログ ファイルに記録する条件付きログ記録を使用します。

アクセスログの課題

ウェブサイトへのトラフィックに関する統計情報を調べたいとします。

  • ユーザーの典型的な地理的分布はどのようなものですか?
  • ユーザーはどの SSL/TLS 暗号とプロトコルを使用していますか?
  • ウェブブラウザの分布はどうなっていますか?

一般的なアクセス ログは、この情報を記録するのに適切な場所ではないことがよくあります。 調査に必要な追加フィールドでアクセス ログを汚染したくないかもしれません。また、混雑したサイトでは、すべてのトランザクションをログに記録するオーバーヘッドが高すぎる可能性があります。

この場合、限定されたフィールドのセットを専用のログに記録できます。 システムの負荷を軽減するために、リクエストのサブセットをサンプリングすることもできます。

サンプリング技術

リクエストの1%からのサンプリング

次の構成では、各リクエストの一意の識別子として$request_id変数を使用します。 split_clientsブロックを使用して、リクエストの 1% のみをログに記録してデータをサンプリングします。

split_clients $request_id $logme {
1% 1;
* 0;
}

server {
listen 80;

access_log /var/log/nginx/secure.log notes if=$logme;

# ...
}

ユニークユーザーの1%からのサンプリング

User-Agentヘッダーなど、各ユーザー (またはユーザーの 1%) から 1 つのデータ ポイントをサンプリングするとします。 大量のリクエストを生成するユーザーはデータ内で過剰に代表されるため、すべてのリクエストから単純にサンプルを採取することはできません。

マップブロックを使用してセッション クッキーの存在を検出します。これにより、リクエストが新しいユーザーからのものか、以前に確認したユーザーからのものかがわかります。 次に、新規ユーザーからのリクエストのみをサンプリングします。

map $cookie_SESSION $logme {
"" $perhaps; # クッキーが見つからない場合は、$perhaps をログに記録します
default 0;
}

split_clients $request_id $perhaps {
1% 1; # $perhaps は 1% の確率で true になります
* 0;
}

server {
listen 80;

access_log /var/log/nginx/secure.log notes if=$logme;

# オプション: アプリケーションがセッション Cookie を生成しない場合は、
# 独自の Cookie を生成します
add_header Set-Cookie SESSION=1;

# ...
}

ユニークなものを試食する

ただし、すべてのクライアントがセッション クッキーを尊重するわけではありません。 たとえば、Web スパイダーは Cookie を無視する可能性があるため、発行されるすべてのリクエストは新しいユーザーからのリクエストとして識別され、結果が歪められます。

新しいものを初めて見るときに、リクエストからサンプルを採取できたら素晴らしいと思いませんか? それは、新しい IP アドレス、新しいセッション Cookie 値、新しいUser-Agentヘッダー、これまでに見たことのないホスト ヘッダー、またはこれらの組み合わせである可能性があります。 この方法では、それぞれのデータを 1 回だけサンプリングします。

明らかに、状態(これまでに見たもののリスト)を保存する必要があり、そのために NGINX Plus のキー値ストアを使用します。 キーバリューストアは、変数を使用して NGINX Plus 設定からアクセスできるメモリ内のキーバリューデータベースを維持します。データベースは、オプションでエントリの自動有効期限 (タイムアウトパラメータ)、永続ストレージ (状態)、およびクラスタ同期 (同期) をサポートします。 ストアにまだ存在しないものごとに、リクエストをログ記録し、再度ログに記録されないようにストアに追加します。

NGINX Plus R18以降では、トランザクション処理中にキーと値のペアを設定するのは非常に簡単です。

# 適切なパラメータを使用して keyval ゾーンを定義しますkeyval_zone zone=clients:80m timeout=3600s;

# 一意の $remote_addr ごとに変数 $seen を作成します
keyval $remote_addr $seen zone=clients;

log_format notes '$remote_addr "$request" $status';

server {
listen 80;

# $seen が空の場合は、keyval を更新し (set $seen 1;)、このリクエストをログに記録します (set $logme 1;)

# それ以外の場合は、$logme は設定されず、リクエストはログに記録されません
# 設定されたタイムアウト後に $seen が "" にリセットされることに注意してください
if ($seen = "") {
set $seen 1;
set $logme 1;
}
access_log /var/log/nginx/secure.log notes if=$logme;

location / {
return 200 "すべて正常: -$seen-$logme-\n";
}

location /api {
api;
}
}

実際の例 – TLS パラメータのサンプリング

この記事は、現実世界の問題から着想を得ました。レガシーデバイスを使用しているユーザーを排除せずに、ベストプラクティスに従って TLS を構成するにはどうすればよいでしょうか?

TLS のベストプラクティスは変化するターゲットです。 TLS 1.3 は 1 年前に承認されましたが、多くのクライアントは以前の TLS バージョンのみと通信します。暗号は「安全でない」と宣言され、廃止されていますが、古い実装はそれらに依存しています。ECC 証明書は RSA よりも優れたパフォーマンスを提供しますが、すべてのクライアントが ECC を受け入れることができるわけではありません。多くの TLS 攻撃は、暗号ネゴシエーション ハンドシェイクを傍受し、クライアントとサーバーに安全性の低い暗号を選択させる「中間者」に依存しています。 したがって、弱い暗号やレガシー暗号をサポートしないように NGINX Plus を構成することが重要ですが、そうするとレガシー クライアントが除外される可能性があります。

次の構成例では、各 TLS クライアントをサンプリングし、SSL プロトコル、暗号、およびUser-Agentヘッダーをログに記録します。 各クライアントがサポートする最新のプロトコルと最も安全な暗号を選択すると仮定すると、サンプル データを評価し、古いプロトコルと暗号のサポートを削除した場合に除外されるクライアントの割合を判断できます。

各クライアントは IP アドレスとUser-Agentの一意の組み合わせによって識別されますが、セッション Cookie または別の方法でクライアントを識別することも同様に機能します。

log_format sslparams '$ssl_protocol $ssl_cipher '
'$remote_addr "$http_user_agent"';

# 適切なパラメータを使用して keyval ゾーンを定義します
keyval_zone zone=clients:80m timeout=3600s;

# $remote_addr と 'User-Agent' ヘッダーの一意の組み合わせごとに変数 $seen を作成します
keyval $remote_addr:$http_user_agent $seen zone=clients;

server {
listen 443 ssl;

# デフォルトの NGINX SSL 構成
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

if ($seen = "") {
set $seen 1;
set $logme 1;
}
access_log /tmp/sslparams.log sslparams if=$logme;

# ...
}

これにより、次のようなエントリを含むログ ファイルが生成されます。

TLSv1.2 AES128-SHA 1.1.1.1 "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0" TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 2.2.2.2 "Mozilla/5.0 ( iPhone; CPU iPhone OS 9_1(Mac OS X など) AppleWebKit/601.1.46(KHTML、Gecko など) バージョン/9.0 モバイル/13B143 Safari/601.1" TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 "Mozilla/ 5.0 (Windows NT 6.1; WOW64; rv:58.0) Gecko/20100101 Firefox/58.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 4.4.4.4 "Mozilla/5.0 (Android 4.4.2; タブレット; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1 AES128-SHA 5.5 .5.5 "Mozilla/5.0 (Android 4.4.2; タブレット; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305 6.6.6.6 "Mozilla/5.0 (Linux; U; Android 5.0. 2; en-US; XT1068 ビルド/LXB22.46-28) AppleWebKit/537.36 (KHTML、Gecko など) バージョン/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36"

次に、さまざまな方法を使用してファイルを処理して、データの拡散を判断できます。

$ cat /tmp/sslparams.log | cut -d ' ' -f 2,2 | sort | uniq -c | sort -rn | perl -ane 'printf "%30s %s\n", $F[1], "="x$F[0];' ECDHE-RSA-AES128-GCM-SHA256 ========================= ECDHE-RSA-AES256-GCM-SHA384 ======== AES128-SHA ==== ECDHE-RSA-CHACHA20-POLY1305 == ECDHE-RSA-AES256-SHA384 ==

使用量が少なく安全性の低い暗号を特定し、ログをチェックしてどのクライアントがそれを使用しているかを判断し、情報に基づいた判断で NGINX Plus 構成から暗号を削除します。

結論

NGINX の条件付きログ記録を使用すると、NGINX が管理するリクエストのサブセットをサンプリングし、標準ログまたは特殊目的のログを書き込むことができます。 この手法は、SSL パラメータの分布を判断するなど、統計分析のためにトラフィックの簡単なサンプルを取得する必要がある場合に役立ちます。

忙しいユーザーやスパイダーが過剰に表れないように、データのサンプリング方法について考慮する必要があります。 NGINX 構成で変数をmapおよびsplit_clientsディレクティブとともに使用して、リクエストを選択およびフィルタリングできます。

決定がより複雑な状況や、高い精度の信頼性が求められる状況では、NGINX 構成で洗練されたセレクターを構築できます。 NGINX Plus のキー値ストアを使用すると、状態を蓄積し、必要に応じてクラスター内の NGINX Plus インスタンス間で共有することができます。

NGINX Plus を使用したリクエスト サンプリングをぜひお試しください。今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、ユースケースについてご相談ください


「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"