編集者– このブログは、NGINX と NGINX Plus を使用したログ記録について説明するブログの 1 つです。 こちらもご覧ください:
これは、NGINX JavaScript モジュールの使用例に関する数多くのブログの 1 つでもあります。 完全なリストについては、 「NGINX JavaScript モジュールの使用例」を参照してください。
NGINX は、あらゆる規模の組織がミッションクリティカルな Web サイト、アプリケーション、API を実行できるように支援します。 規模やデプロイメント インフラストラクチャの選択に関係なく、運用環境で実行するのは簡単ではありません。 この記事では、本番環境へのデプロイメントで難しいことの 1 つであるログ記録について説明します。 具体的には、不要なデータで溢れかえることなく、トラブルシューティングのために適切な量の詳細なログを収集するバランスの取れた行為について説明します。
NGINX は、クライアント要求のアクセス ログと、問題が発生した場合のエラー ログという2 つの異なるログ メカニズムを提供します。 これらのメカニズムは HTTP モジュールとストリーム (TCP/UDP) モジュールの両方で使用できますが、ここでは HTTP トラフィックに焦点を当てます。 (デバッグ重大度レベルを使用する 3 番目のログ記録メカニズムもありますが、ここでは説明しません。)
一般的なデフォルトの NGINX ログ構成は次のようになります。
http {
log_format main
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; # 'main' 形式を使用してログに記録
error_log /var/log/nginx/error.log warn; # 'warn' 重大度レベルまでログに記録
...
}
log_format
ディレクティブは、 access_log
ディレクティブが構成に含まれている場合に作成されるログ エントリの内容と構造を記述します。 上記の例は、多くの Web サーバーで使用されているCommon Log Format (CLF) の拡張です。 error_log
ディレクティブでは、ログに記録するメッセージの重大度レベルを指定しますが、エントリの内容や形式は固定されており、指定できません。 詳細は、次のセクションで説明します。
NGINX ロギングのその他の注目すべき点は次のとおりです。
http
コンテキストのaccess_log
ディレクティブは、すべてのserver{}
ブロックに適用されます。access_log
ディレクティブを使用して、標準の CLF ログ ファイルと 2 番目のより詳細なログの両方を作成できます。一般的に言えば、アクセス ログを使用して分析と使用状況の統計を提供し、エラー ログを使用して障害の検出とトラブルシューティングを行う必要があります。 しかし、生産システムを実行するのはそれほど簡単ではありません。 よくある課題は次のとおりです。
情報
重大度レベルでは詳細な情報を提供しますが、通常の操作には冗長すぎます。さらに、本番環境でログの詳細を追加または削除するために NGINX 設定を変更する場合、変更管理プロセスを経て、構成を再展開する必要がある場合もあります。 完全に安全ですが、「なぜ急上昇が見られるのか」などのライブの問題をトラブルシューティングするときにはやや面倒です。 4xx
/ 5「xx
エラーですか?」。 もちろん、クラスター全体で同じトラフィックを処理する NGINX インスタンスが複数ある場合は、この問題はさらに大きくなります。
アクセス ログの形式をカスタマイズして、各リクエストで収集されたデータを充実させることは、分析を強化するための一般的なアプローチですが、診断やトラブルシューティングには適していません。 メイン アクセス ログに 2 つのジョブを実行するように要求するのは不自然な解決策です。通常、通常の分析よりもトラブルシューティングに多くの情報が必要になるためです。 メイン アクセス ログに多数の変数を追加すると、たまにしか役に立たないデータを含むログ ボリュームが大幅に増加する可能性があります。
代わりに、2 番目のアクセス ログを使用して、デバッグが必要なエラーが発生した場合にのみ書き込むことができます。 access_log
ディレクティブは、 if
パラメータを使用した条件付きログ記録をサポートしています。指定された変数がゼロ以外の空でない値に評価された場合にのみ、リクエストがログに記録されます。
map $status $is_error { 400 1; # 期限切れのクライアント証明書を含む不正なリクエスト 495 1; # クライアント証明書エラー 502 1; # 不正なゲートウェイ (アップストリーム サーバーを選択できませんでした) 504 1; # ゲートウェイ タイムアウト (選択したアップストリームに接続できませんでした) default 0; } access_log /var/log/nginx/access_debug.log access_debug if=$is_error; # 診断ログaccess_log /var/log/nginx/access.log main;
この構成では、 $status
変数をマップ
ブロックに渡して$is_error
変数の値を決定し、その値がaccess_log
ディレクティブのif
パラメータによって評価されます。 $is_errorが
次のように評価された場合1
access_debug.logファイルに特別なログエントリを書き込みます。
ただし、この構成では、リクエスト処理中に発生したエラーは検出されず、最終的に解決されるため、ステータスは200
わかりました
。 そのような例の 1 つは、NGINX が複数のアップストリーム サーバー間で負荷分散を行う場合です。 選択したサーバーでエラーが発生した場合、NGINX はproxy_next_upstream
ディレクティブで設定された条件に従って、リクエストを次のサーバーに渡します。 上流サーバーの1つが正常に応答すれば、クライアントは成功応答を受信し、ステータスとともにログに記録されます。200
。 ただし、再試行によってユーザー エクスペリエンスが低下する可能性があり、上流サーバーが正常でないことがすぐにはわからない可能性があります。 結局、私たちは200
。
NGINX が複数のアップストリーム サーバーにプロキシしようとすると、それらのアドレスはすべて$upstream_addr
変数にキャプチャされます。 他の$upstream_*
変数についても同様です。たとえば、試行された各サーバーからの応答コードをキャプチャする$upstream_status など
です。 したがって、これらの変数に複数のエントリが見られる場合、何か問題が発生したことがわかります。おそらく、上流サーバーの少なくとも 1 つに問題が発生していると考えられます。
リクエストが複数のアップストリーム サーバーにプロキシされたときに、 access_debug.logにも書き込むのはいかがでしょうか?
map $upstream_status $multi_upstreams { "~," 1; # カンマがあります default 0; } map $status $is_error { 400 1; # 期限切れのクライアント証明書を含む不正なリクエスト 495 1; # クライアント証明書エラー 502 1; # 不正なゲートウェイ (上流サーバーを選択できませんでした) 504 1; # ゲートウェイがタイムアウトしました (選択した上流サーバーに接続できませんでした) default $multi_upstreams; # 複数の上流サーバーを試した場合} access_log /var/log/nginx/access_debug.log access_debug if=$is_error; # 診断ログ access_log /var/log/nginx/access.log main; # 通常のログ
ここでは、別のマップ
ブロックを使用して、 $upstream_status
内のカンマ ( ,
) の存在に応じて値が決まる新しい変数 ( $multi_upstreams
) を生成します。 カンマは、ステータス コードが複数あることを意味し、したがって、複数のアップストリーム サーバーに遭遇したことを意味します。 この新しい変数は、 $status が
リストされているエラー コードのいずれでもない場合の$is_error
の値を決定します。
この時点では、通常のアクセス ログと、エラーのあるリクエストを含む特別なaccess_debug.logファイルがありますが、 access_debug
ログ形式はまだ定義されていません。 次に、問題の診断に役立つ必要なデータがすべてaccess_debug.logファイルにあることを確認しましょう。
診断データをaccess_debug.logに取り込むのは難しくありません。 NGINX は HTTP 処理に関連する100 を超える変数を提供しており、必要な数だけそれらをキャプチャする特別なlog_format
ディレクティブを定義できます。 ただし、この目的のために単純なログ形式を構築することには、いくつかの欠点があります。
NGINX JavaScript モジュール <.htmla> (njs) を使用して、JSON などの構造化形式でログ エントリを書き込むことで、これらの課題に対処できます。 JSON形式は、次のようなログ処理システムでも広くサポートされています。 スプランク、 ログスタッシュ、 グレイログ、 そして ログリー。 log_format
構文を JavaScript 関数にオフロードすることで、ネイティブ JSON 構文のメリットを享受し、すべての NGINX 変数とnjs ' r
' オブジェクトからの追加データにアクセスできるようになります。
js_import conf.d/json_log.js;js_set $json_debug_log json_log.debugLog;
log_format access_debug escape=none $json_debug_log; # njs にオフロード
access_log /var/log/nginx/access_debug.log access_debug if=$is_error;
js_import
ディレクティブは、JavaScript コードを含むファイルを指定し、それをモジュールとしてインポートします。 コード自体はここにあります。 access_debug
ログ形式を使用するアクセス ログ エントリを書き込むたびに、 $json_debug_log
変数が評価されます。 この変数は、 js_set
ディレクティブで定義されているdebugLog
JavaScript 関数を実行することによって評価されます。
JavaScript コードと NGINX 構成を組み合わせると、次のような診断ログが生成されます。
$ tail --lines=1 /var/log/nginx/access_debug.log | jq { "タイムスタンプ": "2020-09-21T11:25:55+00:00", "接続": { "リクエスト数": 1、「経過時間」: 0.555、"パイプライン": false、"ssl": { "プロトコル": 「TLSv1.2」、「暗号」: "ECDHE-RSA-AES256-GCM-SHA384", "session_id": "b302f76a70dfec92f6bd72de5732692481ebecbbc69a4d81c900ae4dc928485c", "session_reused": false, "client_cert": { "status": "なし" } } }, "リクエスト": { "クライアント": 「127.0.0.1」、「ポート」: 443、「ホスト」:「foo.example.com」、「メソッド」: 「GET」、「uri」:「/one」、「http_version」:「 1.1、「受信したバイト数」: 87, "ヘッダー": { "ホスト": "foo.example.com:443", "ユーザーエージェント": "curl/7.64.1", "Accept": "*/*" } }, "アップストリーム": [ { "server_addr": "10.37.0.71", "サーバーポート": 443、「接続時間」: null、「ヘッダー時間」: null、「応答時間」: 0.551、「送信バイト数」: 0、「受信したバイト数」: 0、「ステータス」: 504 }, { "server_addr": "10.37.0.72", "サーバーポート": 443、「接続時間」: 0.004、「ヘッダー時間」: 0.004、「応答時間」: 0.004、「送信バイト数」: 92、「受信したバイト数」: 4161、「ステータス」: 200 } ], "応答": { "ステータス": 200、「送信バイト数」: 186、「ヘッダー」: {「コンテンツタイプ」:「text/html」、「コンテンツの長さ」: "4161" } } }
JSON 形式を使用すると、HTTP 接続全体 (SSL/TLS を含む)、リクエスト、アップストリーム、および応答に関連する情報ごとに個別のオブジェクトを作成できます。 最初のアップストリーム(10.37.0.71)がステータスを返したことに注意してください504
(ゲートウェイ
タイムアウト)
NGINX が次のアップストリーム (10.37.0.72) を試行する前に、正常に応答しました。 0.5 秒のタイムアウト ( upstreams
オブジェクトの最初の要素でresponse_time
として報告される) が、この正常な応答 ( connection
オブジェクトでelapsed_time
として報告される) の全体的な遅延の大部分を占めます。
以下は、期限切れのクライアント証明書によって発生したクライアント エラーの (切り捨てられた) ログ エントリの別の例です。
{
「タイムスタンプ」: "2020-09-22T10:20:50+00:00",
"接続": {
"ssl": {
"プロトコル": 「TLSv1.2」、
「暗号」: 「ECDHE-RSA-AES256-GCM-SHA384」、
「セッションID」: "30711efbe047c38a98c2209cc4b5f196988dcf2d7f1f2c269fde7269c370432e",
"session_reused": false,
"client_cert": {
"status": 「失敗: 証明書の有効期限が切れています」
「シリアル」: 「1006」
「指紋」: "0c47cc4bd0fefbc2ac6363345cfbbf295594fe8d",
"件名": "emailAddress=liam@nginx.com,CN=test01,OU=Demo CA,O=nginx,ST=CA,C=US",
"発行者": 「CN=デモ中間 CA、OU=デモ CA、O=nginx、ST=CA、C=US」、
「開始」: 「2019 年 9 月 20 日 12:00:11 GMT」、
「有効期限」: 「2020 年 9 月 20 日 12:00:11 GMT」、
「期限切れ」: true、
...
「応答」: {
「ステータス」: 400,
「送信バイト数」: 283,
「ヘッダー」: {
「コンテンツ タイプ」:「text/html」
「コンテンツの長さ」: 「215」
}
}
エラーが発生した場合にのみ豊富な診断データを生成することで、再構成を実行することなくリアルタイムのトラブルシューティングが可能になります。 最後のバイトがクライアントに送信された後、ログ記録フェーズでエラーが検出された場合にのみ JavaScript コードが実行されるため、成功したリクエストには影響はありません。
完全な構成はGitHubで入手できます。ぜひご自分の環境で試してみてはいかがでしょうか。 NGINX Plus をまだ実行していない場合は、今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、ユースケースについてご相談ください。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"