Nginx ログ集中管理:Fluent Bit/Loki/Elasticsearch 連携とログサンプリング戦略

Web サーバーのログ管理、みなさんはどのように運用されていますか?Nginx から出力される大量のアクセスログやエラーログを効率的に収集・分析できる仕組みを構築することは、サービスの安定運用とトラブルシューティングにおいて非常に重要です。
本記事では、軽量で高性能なログコレクターである Fluent Bit を使って、Nginx のログを Loki や Elasticsearch に集約する方法を詳しく解説します。さらに、大量のログデータを扱う際に欠かせないログサンプリング戦略についても、実践的な設定例とともにご紹介していきますね。
背景
Nginx ログの役割と重要性
Nginx は高速で軽量な Web サーバー・リバースプロキシとして広く利用されており、その動作状況を把握するためのログは主に 2 種類存在します。
# | ログ種類 | 内容 | 用途 |
---|---|---|---|
1 | アクセスログ | リクエスト元 IP、URL、ステータスコード、レスポンスタイムなど | トラフィック分析、パフォーマンス監視 |
2 | エラーログ | 設定エラー、アップストリーム接続失敗、権限エラーなど | 障害調査、デバッグ |
これらのログを適切に収集・管理することで、以下のような価値が得られるでしょう。
- 障害の早期発見:エラーログから問題を即座に検知できます
- パフォーマンス分析:レスポンスタイムの推移を可視化できますね
- セキュリティ監視:不正アクセスやボット攻撃のパターンを把握できるでしょう
- ビジネス分析:アクセス傾向やユーザー行動を分析可能です
ログ管理アーキテクチャの全体像
複数のサーバーで稼働する Nginx のログを効率的に管理するには、ログの収集・転送・保存・検索を担う各コンポーネントを適切に組み合わせる必要があります。
以下の図は、本記事で構築するログ管理システムの基本構成を示しています。
mermaidflowchart TB
nginx1["Nginx サーバー 1<br/>access.log / error.log"]
nginx2["Nginx サーバー 2<br/>access.log / error.log"]
nginx3["Nginx サーバー 3<br/>access.log / error.log"]
fb1["Fluent Bit<br/>Agent 1"]
fb2["Fluent Bit<br/>Agent 2"]
fb3["Fluent Bit<br/>Agent 3"]
loki["Loki<br/>ログストレージ"]
es["Elasticsearch<br/>検索エンジン"]
grafana["Grafana<br/>可視化"]
kibana["Kibana<br/>可視化・分析"]
nginx1 -->|ログ出力| fb1
nginx2 -->|ログ出力| fb2
nginx3 -->|ログ出力| fb3
fb1 -->|HTTP API| loki
fb2 -->|HTTP API| loki
fb3 -->|HTTP API| loki
fb1 -->|HTTP API| es
fb2 -->|HTTP API| es
fb3 -->|HTTP API| es
loki -->|データソース| grafana
es -->|データソース| kibana
この構成により、各 Nginx サーバーのログが Fluent Bit によって収集され、用途に応じて Loki と Elasticsearch の両方に転送される仕組みとなります。
図で理解できる要点:
- 各 Nginx サーバーに Fluent Bit Agent が配置されている
- ログは複数の保存先(Loki、Elasticsearch)に同時転送可能
- 可視化ツールは保存先に応じて使い分けられる
ログ管理ツールの選択肢
ログ収集・保存・分析には、さまざまなツールが存在しますが、本記事では以下の組み合わせを採用します。
ログコレクター:Fluent Bit
# | 特徴 | メリット |
---|---|---|
1 | 軽量設計 | メモリ使用量が Fluentd の 1/10 程度 |
2 | 高速処理 | C 言語で実装され、高いスループットを実現 |
3 | 豊富なプラグイン | 入力・出力・フィルターの組み合わせが柔軟 |
4 | コンテナ対応 | Docker、Kubernetes との統合が容易 |
ログストレージ:Loki vs Elasticsearch
# | Loki | Elasticsearch |
---|---|---|
1 | ログメタデータにインデックスを作成 | 全文検索用のインデックスを作成 |
2 | 軽量でストレージ効率が高い | 高度な検索・集計が可能 |
3 | Grafana との統合が最適 | Kibana で強力な分析が可能 |
4 | リアルタイム監視に最適 | 複雑なクエリや分析に最適 |
この両者を併用することで、リアルタイム監視と詳細分析の両立が実現できるでしょう。
課題
大量ログによるリソース圧迫
アクセス数の多いサービスでは、Nginx のアクセスログが 1 日あたり数百 GB から数 TB に達することも珍しくありません。このような大量のログをすべて保存・処理しようとすると、以下の問題が発生します。
# | 課題 | 影響 |
---|---|---|
1 | ストレージコストの増大 | クラウドストレージ費用が月額数十万円に |
2 | ネットワーク帯域の圧迫 | ログ転送でアプリケーション通信に影響 |
3 | 検索性能の低下 | インデックスサイズが肥大化し検索が遅延 |
4 | 運用負荷の増加 | データ保持期間管理やディスク容量監視が煩雑に |
ログ形式の多様性と解析の困難さ
Nginx のデフォルトログフォーマットは combined
形式ですが、プロジェクトによってカスタマイズされていることが多く、以下のような課題が生じます。
mermaidflowchart LR
raw["生ログ<br/>カスタム形式"]
parse["パース処理<br/>正規表現/JSON"]
struct["構造化データ<br/>フィールド抽出"]
enrich["エンリッチ<br/>GeoIP / User-Agent"]
store["保存<br/>Loki / ES"]
raw -->|複雑| parse
parse -->|失敗リスク| struct
struct -->|付加情報| enrich
enrich -->|最終形| store
style parse fill:#ffcccc
style struct fill:#ffffcc
style enrich fill:#ccffcc
図で理解できる要点:
- 生ログから保存までに複数の変換ステップが必要
- パース処理の失敗は後続処理に影響する
- エンリッチ処理で付加価値を高められる
特に正規表現によるパースは、ログフォーマットが変更されると即座に失敗してしまうため、メンテナンス性の高い設定が求められます。
複数システムへの転送と運用複雑化
リアルタイム監視には Loki、長期分析には Elasticsearch というように、用途に応じて複数のログストレージを使い分けたい場合、以下の課題が発生するでしょう。
- 転送設定の重複:同じログを複数の出力先に送る設定が煩雑になります
- エラーハンドリング:一方の転送先が停止した際の挙動制御が難しいですね
- サンプリング率の調整:出力先ごとに異なるサンプリング率を適用したい場合、設定が複雑化します
ログサンプリングの戦略不足
すべてのログを保存するのは現実的ではないため、サンプリングが必要ですが、適切な戦略がないと以下の問題が起こります。
# | 問題 | 例 |
---|---|---|
1 | 重要なエラーログが欠落 | 500 エラーをサンプリングで捨ててしまう |
2 | 特定パターンの偏り | ヘルスチェックログばかりが保存される |
3 | デバッグ時の情報不足 | 障害発生時のログが残っていない |
4 | 統計的な偏り | ランダムサンプリングで傾向分析が困難 |
このため、ステータスコードや URL パスに応じた柔軟なサンプリング戦略が必要になります。
解決策
Fluent Bit によるログ収集アーキテクチャ
Fluent Bit は、入力(Input)、パーサー(Parser)、フィルター(Filter)、出力(Output)という 4 つのコンポーネントで構成され、柔軟なログ処理パイプラインを構築できます。
以下の図は、Fluent Bit 内部でのログ処理フローを示しています。
mermaidflowchart LR
input["INPUT<br/>tail プラグイン<br/>ログファイル監視"]
parser["PARSER<br/>nginx フォーマット解析<br/>JSON 変換"]
filter1["FILTER 1<br/>Lua スクリプト<br/>サンプリング"]
filter2["FILTER 2<br/>Modify<br/>フィールド追加"]
output1["OUTPUT 1<br/>Loki<br/>リアルタイム監視"]
output2["OUTPUT 2<br/>Elasticsearch<br/>長期分析"]
input -->|生ログ| parser
parser -->|構造化| filter1
filter1 -->|サンプリング済み| filter2
filter2 -->|エンリッチ済み| output1
filter2 -->|エンリッチ済み| output2
図で理解できる要点:
- 各処理段階が明確に分離されている
- フィルターは複数チェーン可能で処理の柔軟性が高い
- 1 つの入力から複数の出力先に配信できる
Nginx ログのパース設定
Nginx のログを構造化データとして扱うため、Fluent Bit のパーサー機能を活用します。ここでは combined
フォーマットを例に解説しますね。
パーサー設定ファイルの定義
Fluent Bit のパーサー設定では、正規表現または JSON 形式でログをパースします。
parsers.conf[PARSER]
Name nginx_combined
Format regex
Regex ^(?<remote_addr>[^ ]*) - (?<remote_user>[^ ]*) \[(?<time_local>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<status>[^ ]*) (?<body_bytes_sent>[^ ]*)(?: "(?<http_referer>[^\"]*)" "(?<http_user_agent>[^\"]*)")?$
Time_Key time_local
Time_Format %d/%b/%Y:%H:%M:%S %z
この設定により、以下のような生ログが構造化されます。
入力(生ログ):
swift192.168.1.100 - - [21/Oct/2025:10:30:45 +0900] "GET /api/users HTTP/1.1" 200 1234 "https://example.com/" "Mozilla/5.0"
出力(パース後の JSON):
json{
"remote_addr": "192.168.1.100",
"remote_user": "-",
"time_local": "21/Oct/2025:10:30:45 +0900",
"method": "GET",
"path": "/api/users",
"status": "200",
"body_bytes_sent": "1234",
"http_referer": "https://example.com/",
"http_user_agent": "Mozilla/5.0"
}
パーサー設定のポイントは、各フィールドに名前を付けることで後続のフィルター処理やクエリで活用できる点です。
カスタムログフォーマットへの対応
JSON 形式でログを出力している場合は、パース処理がさらにシンプルになります。
Nginx の設定で JSON ログを出力する例:
nginx.conflog_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/access.log json_combined;
この場合、Fluent Bit のパーサー設定は以下のようにシンプルになりますね。
parsers.conf[PARSER]
Name nginx_json
Format json
Time_Key time_local
Time_Format %d/%b/%Y:%H:%M:%S %z
JSON 形式を採用することで、パース失敗のリスクが大幅に低減され、メンテナンス性が向上するでしょう。
ログサンプリング戦略の実装
大量のログを効率的に管理するため、Fluent Bit の Lua フィルタープラグインを使って柔軟なサンプリング戦略を実装します。
サンプリング戦略の設計指針
効果的なサンプリングには、以下の原則を考慮する必要があります。
# | 原則 | 実装方法 |
---|---|---|
1 | エラーログは全件保存 | status >= 400 の場合はサンプリングしない |
2 | ヘルスチェックは大幅削減 | /health などは 99% サンプリング |
3 | 重要 API は高い保存率 | /api/payment などは 100% 保存 |
4 | 通常リクエストは適度に削減 | 90% サンプリング(10% 保存) |
Lua スクリプトによるサンプリング実装
Fluent Bit では、Lua スクリプトを使って柔軟なフィルター処理を記述できます。以下はステータスコードと URL パスに基づくサンプリングの例です。
sampling.lua-- サンプリング判定関数
function cb_filter(tag, timestamp, record)
-- math.random の初期化
math.randomseed(os.time())
local status = tonumber(record["status"])
local path = record["path"]
-- エラーログは全件保持
if status >= 400 then
return 1, timestamp, record
end
-- ヘルスチェックは 1% のみ保持
if string.match(path, "^/health") or string.match(path, "^/ping") then
if math.random(100) <= 1 then
return 1, timestamp, record
else
return -1, timestamp, record -- ドロップ
end
end
-- 重要 API は全件保持
if string.match(path, "^/api/payment") or string.match(path, "^/api/order") then
return 1, timestamp, record
end
-- その他は 10% 保持
if math.random(100) <= 10 then
return 1, timestamp, record
else
return -1, timestamp, record
end
end
このスクリプトでは、return 1
でログを保持、return -1
でログをドロップする仕組みとなっています。
Fluent Bit 設定への組み込み
Lua スクリプトを Fluent Bit の設定ファイルに組み込む方法を見ていきましょう。
fluent-bit.conf[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/nginx/access.log
Parser nginx_json
Tag nginx.access
Refresh_Interval 5
Mem_Buf_Limit 5MB
[FILTER]
Name lua
Match nginx.access
script sampling.lua
call cb_filter
この設定により、Nginx のアクセスログが tail プラグインで読み込まれ、JSON パーサーで構造化された後、Lua スクリプトによってサンプリングされます。
Loki への転送設定
Loki は Grafana Labs が開発したログ集約システムで、Prometheus のようなラベルベースのインデックスを採用しています。
Loki 出力プラグインの設定
Fluent Bit から Loki へログを転送する設定は以下の通りです。
fluent-bit.conf[OUTPUT]
Name loki
Match nginx.access
Host loki.example.com
Port 3100
Labels job=nginx, host=${HOSTNAME}, environment=production
Label_keys $status,$method,$path
Remove_keys time_local,remote_user
Line_format json
Auto_kubernetes_labels off
設定項目の詳細を解説しますね。
# | 項目 | 説明 |
---|---|---|
1 | Host / Port | Loki サーバーのエンドポイント |
2 | Labels | 固定ラベル(ジョブ名、ホスト名など) |
3 | Label_keys | ログのフィールドからラベルとして抽出するキー |
4 | Remove_keys | Loki に送信しないフィールド |
5 | Line_format | ログの形式(json または key_value) |
Loki でのクエリ例
Grafana で Loki のログをクエリする際は、LogQL という専用クエリ言語を使用します。
エラーログのみを抽出:
logql{job="nginx"} | json | status >= 400
特定パスへのリクエストを集計:
logqlsum(rate({job="nginx"} | json | path =~ "/api/.*" [5m])) by (status)
レスポンスタイムの P95 を計算:
logqlquantile_over_time(0.95, {job="nginx"} | json | unwrap request_time [5m])
Loki はラベルベースの検索に最適化されているため、全文検索よりもクエリ性能が高いのが特徴です。
Elasticsearch への転送設定
Elasticsearch は強力な全文検索エンジンで、複雑なクエリや集計処理に優れています。
Elasticsearch 出力プラグインの設定
Fluent Bit から Elasticsearch へログを転送する設定例です。
fluent-bit.conf[OUTPUT]
Name es
Match nginx.access
Host elasticsearch.example.com
Port 9200
Index nginx-access
Type _doc
Logstash_Format On
Logstash_Prefix nginx-access
Logstash_DateFormat %Y.%m.%d
Retry_Limit 5
Trace_Error On
設定項目の詳細は以下の通りです。
# | 項目 | 説明 |
---|---|---|
1 | Host / Port | Elasticsearch のエンドポイント |
2 | Index | インデックス名(Logstash_Format が Off の場合) |
3 | Logstash_Format | 日付ベースのインデックス作成を有効化 |
4 | Logstash_Prefix | インデックスのプレフィックス |
5 | Logstash_DateFormat | 日付形式(日次、月次などを指定可能) |
6 | Retry_Limit | 転送失敗時のリトライ回数 |
インデックステンプレートの設定
Elasticsearch では、インデックステンプレートを定義することで、フィールドのマッピングや設定を自動適用できます。
json{
"index_patterns": ["nginx-access-*"],
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.refresh_interval": "30s"
},
"mappings": {
"properties": {
"remote_addr": { "type": "ip" },
"status": { "type": "short" },
"body_bytes_sent": { "type": "long" },
"request_time": { "type": "float" },
"method": { "type": "keyword" },
"path": {
"type": "text",
"fields": { "keyword": { "type": "keyword" } }
},
"http_user_agent": { "type": "text" },
"@timestamp": { "type": "date" }
}
}
}
このテンプレートにより、新しいインデックスが作成される際、各フィールドに適切な型が自動的に割り当てられます。
Kibana でのクエリと可視化
Elasticsearch に保存されたログは、Kibana で可視化・分析できます。
KQL(Kibana Query Language)の例:
kqlstatus >= 500 and path: "/api/*"
集計クエリの例(ステータスコード別のカウント):
json{
"aggs": {
"status_codes": {
"terms": {
"field": "status",
"size": 10
}
}
}
}
Kibana では、時系列グラフ、円グラフ、ヒートマップなど、多彩な可視化オプションが利用可能です。
複数出力先への並列転送
Loki と Elasticsearch の両方にログを送信したい場合、Fluent Bit の設定で複数の OUTPUT セクションを定義します。
fluent-bit.conf[OUTPUT]
Name loki
Match nginx.access
Host loki.example.com
Port 3100
Labels job=nginx
[OUTPUT]
Name es
Match nginx.access
Host elasticsearch.example.com
Port 9200
Logstash_Format On
Logstash_Prefix nginx-access
この設定により、同じログストリームが両方の出力先に並列で送信されますね。
出力先ごとに異なるサンプリング率を適用
用途に応じて、Loki には全件、Elasticsearch にはサンプリング済みのログを送信したい場合、Tag の活用が有効です。
fluent-bit.conf[FILTER]
Name rewrite_tag
Match nginx.access
Rule $status ^.*$ nginx.loki false
[FILTER]
Name lua
Match nginx.access
script sampling.lua
call cb_filter
[FILTER]
Name rewrite_tag
Match nginx.access
Rule $status ^.*$ nginx.es false
[OUTPUT]
Name loki
Match nginx.loki
Host loki.example.com
Port 3100
[OUTPUT]
Name es
Match nginx.es
Host elasticsearch.example.com
Port 9200
この設定では、nginx.loki
タグには全件が流れ、nginx.es
タグにはサンプリング後のログが流れる仕組みとなっています。
具体例
Docker Compose による環境構築
実際にローカル環境で Nginx + Fluent Bit + Loki + Elasticsearch の統合環境を構築してみましょう。
Docker Compose ファイルの全体構成
以下は、すべてのコンポーネントを含む docker-compose.yml
の例です。
docker-compose.ymlversion: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/logs:/var/log/nginx
depends_on:
- fluent-bit
fluent-bit:
image: fluent/fluent-bit:latest
volumes:
- ./fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
- ./fluent-bit/parsers.conf:/fluent-bit/etc/parsers.conf
- ./fluent-bit/sampling.lua:/fluent-bit/etc/sampling.lua
- ./nginx/logs:/var/log/nginx
depends_on:
- loki
- elasticsearch
この設定では、Nginx のログディレクトリを Fluent Bit コンテナとボリューム共有しています。
Loki と Grafana の起動設定
Loki と Grafana のサービス定義を追加します。
docker-compose.yml loki:
image: grafana/loki:latest
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
depends_on:
- loki
Grafana には http://localhost:3000
でアクセスでき、Loki をデータソースとして追加することで、すぐにログの可視化が可能です。
Elasticsearch と Kibana の起動設定
Elasticsearch と Kibana のサービス定義も追加しましょう。
docker-compose.yml elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.10.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
volumes:
es-data:
driver: local
Kibana には http://localhost:5601
でアクセスでき、Elasticsearch のデータを分析できます。
Nginx のログフォーマット設定
Nginx で JSON 形式のログを出力する設定を行います。
JSON ログフォーマットの定義
nginx.conf
に以下の設定を追加しましょう。
nginx.confhttp {
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"server_protocol":"$server_protocol",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
error_log /var/log/nginx/error.log warn;
}
この設定により、アクセスログが JSON 形式で出力され、Fluent Bit でのパース処理が不要になります。
実際のログ出力例
上記設定で出力されるログの例は以下の通りです。
json{
"time_local": "21/Oct/2025:14:23:11 +0900",
"remote_addr": "192.168.1.50",
"remote_user": "-",
"request_method": "POST",
"request_uri": "/api/users",
"server_protocol": "HTTP/1.1",
"status": 201,
"body_bytes_sent": 342,
"request_time": 0.125,
"upstream_response_time": "0.120",
"http_referer": "https://example.com/signup",
"http_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"http_x_forwarded_for": "203.0.113.45"
}
この形式であれば、Fluent Bit で簡単に各フィールドを抽出できますね。
Fluent Bit の完全な設定例
ここまでの内容を統合した、Fluent Bit の完全な設定ファイルを示します。
メイン設定ファイル(fluent-bit.conf)
fluent-bit.conf[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
[INPUT]
Name tail
Path /var/log/nginx/access.log
Parser nginx_json
Tag nginx.access
Refresh_Interval 5
Mem_Buf_Limit 5MB
Skip_Long_Lines On
[INPUT]
Name tail
Path /var/log/nginx/error.log
Tag nginx.error
Refresh_Interval 5
Mem_Buf_Limit 5MB
この設定では、アクセスログとエラーログを別々のタグで収集しています。
フィルター設定(サンプリングとエンリッチ)
fluent-bit.conf[FILTER]
Name lua
Match nginx.access
script /fluent-bit/etc/sampling.lua
call cb_filter
[FILTER]
Name modify
Match nginx.*
Add hostname ${HOSTNAME}
Add environment production
Modify フィルターを使って、ホスト名や環境情報などのメタデータを追加しています。
出力設定(Loki と Elasticsearch)
fluent-bit.conf[OUTPUT]
Name loki
Match nginx.*
Host loki
Port 3100
Labels job=nginx, environment=production
Label_keys $status,$request_method,$hostname
Line_format json
[OUTPUT]
Name es
Match nginx.access
Host elasticsearch
Port 9200
Logstash_Format On
Logstash_Prefix nginx-access
Logstash_DateFormat %Y.%m.%d
Retry_Limit 5
[OUTPUT]
Name es
Match nginx.error
Host elasticsearch
Port 9200
Logstash_Format On
Logstash_Prefix nginx-error
Logstash_DateFormat %Y.%m.%d
Retry_Limit 5
アクセスログとエラーログを、それぞれ異なるインデックスに保存する設定となっていますね。
高度なサンプリング戦略の実装例
より高度なサンプリング戦略として、時間帯やレスポンスタイムに基づくサンプリングを実装してみましょう。
時間帯別サンプリング
深夜帯はトラフィックが少ないため高いサンプリング率、ピークタイムは低いサンプリング率に調整します。
sampling_advanced.luafunction cb_filter(tag, timestamp, record)
math.randomseed(os.time())
-- 現在の時刻を取得(0-23)
local hour = tonumber(os.date("%H"))
local status = tonumber(record["status"])
local path = record["path"]
-- エラーログは常に全件保持
if status >= 400 then
return 1, timestamp, record
end
-- サンプリング率を時間帯で調整
local sampling_rate
if hour >= 2 and hour < 6 then
-- 深夜帯は 50% 保持
sampling_rate = 50
elseif hour >= 9 and hour < 18 then
-- 営業時間は 5% 保持
sampling_rate = 5
else
-- その他は 20% 保持
sampling_rate = 20
end
if math.random(100) <= sampling_rate then
return 1, timestamp, record
else
return -1, timestamp, record
end
end
この設定により、トラフィック量に応じて柔軟にストレージコストを最適化できます。
レスポンスタイムベースのサンプリング
遅いリクエストは重要な情報を含むため、優先的に保存します。
sampling_response_time.luafunction cb_filter(tag, timestamp, record)
math.randomseed(os.time())
local request_time = tonumber(record["request_time"])
local status = tonumber(record["status"])
-- エラーは全件保持
if status >= 400 then
return 1, timestamp, record
end
-- レスポンスタイムが 1 秒以上は全件保持
if request_time and request_time >= 1.0 then
return 1, timestamp, record
end
-- 0.5 秒以上は 50% 保持
if request_time and request_time >= 0.5 then
if math.random(100) <= 50 then
return 1, timestamp, record
else
return -1, timestamp, record
end
end
-- その他は 10% 保持
if math.random(100) <= 10 then
return 1, timestamp, record
else
return -1, timestamp, record
end
end
パフォーマンス分析に必要なデータを確実に保持しつつ、ストレージを節約できますね。
Grafana でのダッシュボード作成
Loki に保存されたログを Grafana で可視化するダッシュボードを作成します。
リアルタイムエラー監視パネル
エラーログをリアルタイムで監視するクエリ例です。
logqlsum(rate({job="nginx"} | json | status >= 400 [1m])) by (status)
このクエリは、1 分間のエラーレートをステータスコード別に集計します。
レスポンスタイム分布の可視化
logqlhistogram_quantile(0.95,
sum(rate({job="nginx"} | json | unwrap request_time [5m])) by (le)
)
95 パーセンタイルのレスポンスタイムを 5 分間隔で計算し、グラフ化できます。
パス別アクセス数ランキング
logqltopk(10,
sum(rate({job="nginx"} | json [5m])) by (path)
)
アクセス数の多い上位 10 パスを表示するクエリとなっています。
トラブルシューティングとデバッグ
実運用では、ログが正しく転送されないなどの問題が発生することがあります。以下はよくあるエラーとその対処法です。
エラー:Failed to connect to Loki
エラーコード: Connection refused (111)
エラーメッセージ:
css[2025/10/21 15:30:22] [error] [output:loki:loki.0] connection #42 failed to loki:3100: Connection refused
発生条件:
- Loki サーバーが起動していない
- ネットワーク設定が不正
- ファイアウォールでポートがブロックされている
解決方法:
- Loki サーバーの起動状態を確認
bashdocker ps | grep loki
- Loki のヘルスチェック
bashcurl http://loki:3100/ready
- Fluent Bit の設定を確認
fluent-bit.conf[OUTPUT]
Name loki
Host loki # ホスト名が正しいか確認
Port 3100 # ポート番号が正しいか確認
エラー:Elasticsearch index creation failed
エラーコード: 400 Bad Request
エラーメッセージ:
ini[2025/10/21 15:45:10] [error] [output:es:es.0] HTTP status=400 URI=/_bulk, response: {"error":{"type":"mapper_parsing_exception","reason":"failed to parse field [status] of type [long]"}}
発生条件:
- フィールドの型が不一致
- インデックステンプレートが正しく適用されていない
解決方法:
- 既存インデックスを削除して再作成
bashcurl -X DELETE http://elasticsearch:9200/nginx-access-*
- インデックステンプレートの再適用
bashcurl -X PUT http://elasticsearch:9200/_index_template/nginx-access -H 'Content-Type: application/json' -d @template.json
- Fluent Bit のフィルターでフィールド型を修正
fluent-bit.conf[FILTER]
Name modify
Match nginx.access
Add status_code ${status}
Remove status
エラー:Lua script execution failed
エラーコード: LUA_ERRRUN
エラーメッセージ:
css[2025/10/21 16:00:05] [error] [filter:lua:lua.0] error loading script: sampling.lua:15: unexpected symbol near 'end'
発生条件:
- Lua スクリプトの構文エラー
- 予期しない nil 値の参照
解決方法:
- Lua スクリプトの構文チェック
bashluac -p sampling.lua
- nil チェックの追加
lualocal status = tonumber(record["status"])
if not status then
return 1, timestamp, record -- status が nil の場合は保持
end
- デバッグログの追加
luafunction cb_filter(tag, timestamp, record)
print("Processing record: " .. require("cjson").encode(record))
-- 処理内容
end
まとめ
本記事では、Nginx のログを Fluent Bit で収集し、Loki と Elasticsearch に集約する実践的な方法を解説しました。ログ管理における重要なポイントを改めて整理しましょう。
アーキテクチャ設計の要点:
# | ポイント | 理由 |
---|---|---|
1 | 軽量なログコレクターの選択 | リソース消費を最小限に抑える |
2 | JSON 形式でのログ出力 | パース処理の簡素化とメンテナンス性向上 |
3 | 用途別のストレージ使い分け | コストと機能のバランス最適化 |
4 | 柔軟なサンプリング戦略 | ストレージコストの削減と重要データの保持 |
運用における推奨事項:
- エラーログは常に全件保存 して、障害調査時の情報欠落を防ぎましょう
- ヘルスチェックログは大幅にサンプリング して、ノイズを削減できます
- 時間帯やレスポンスタイムに応じたサンプリング で、効率的なデータ保持が実現できますね
- Loki でリアルタイム監視、Elasticsearch で詳細分析 という使い分けが効果的です
本記事で紹介した構成を導入することで、月間数 TB のログを効率的に管理しつつ、必要な情報は確実に保存できる仕組みが構築できるでしょう。Docker Compose を使ったローカル環境での検証から始めて、本番環境への展開を進めてみてください。
ログ管理は、サービスの安定運用において極めて重要な要素となっています。適切なツールと戦略を組み合わせることで、コストを抑えながら高品質な監視・分析環境を実現できますね。
関連リンク
- article
Nginx ログ集中管理:Fluent Bit/Loki/Elasticsearch 連携とログサンプリング戦略
- article
Nginx API ゲートウェイ設計:auth_request/サーキットブレーカ/レート制限の組み合わせ
- article
Nginx ディレクティブ早見表:server/location/if/map の評価順序と落とし穴
- article
Nginx を macOS で本番級に構築:launchd/ログローテーション/権限・署名のベストプラクティス
- article
Nginx Unit と Node(+ PM2)/Passenger を比較:再読み込み・可用性・性能の実測
- article
Nginx 499/444/408 の謎を解く:クライアント切断と各種タイムアウトの実態
- article
Obsidian 日次・週次レビュー運用:テンプレ+ Dataview で継続する仕組み
- article
フィーチャーフラグ運用:Zustand で段階的リリースとリモート設定を実装
- article
Nuxt 本番運用チェックリスト:セキュリティヘッダー・CSP・Cookie 設定を総点検
- article
WordPress 技術アーキテクチャ図解:フック/ループ/クエリの全体像を一枚で理解
- article
Nginx ログ集中管理:Fluent Bit/Loki/Elasticsearch 連携とログサンプリング戦略
- article
WebSocket でリアルタイム在庫表示を実装:購買イベントの即時反映ハンズオン
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来