T-CREATOR

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

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

#LokiElasticsearch
1ログメタデータにインデックスを作成全文検索用のインデックスを作成
2軽量でストレージ効率が高い高度な検索・集計が可能
3Grafana との統合が最適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

設定項目の詳細を解説しますね。

#項目説明
1Host / PortLoki サーバーのエンドポイント
2Labels固定ラベル(ジョブ名、ホスト名など)
3Label_keysログのフィールドからラベルとして抽出するキー
4Remove_keysLoki に送信しないフィールド
5Line_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

設定項目の詳細は以下の通りです。

#項目説明
1Host / PortElasticsearch のエンドポイント
2Indexインデックス名(Logstash_Format が Off の場合)
3Logstash_Format日付ベースのインデックス作成を有効化
4Logstash_Prefixインデックスのプレフィックス
5Logstash_DateFormat日付形式(日次、月次などを指定可能)
6Retry_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 サーバーが起動していない
  • ネットワーク設定が不正
  • ファイアウォールでポートがブロックされている

解決方法:

  1. Loki サーバーの起動状態を確認
bashdocker ps | grep loki
  1. Loki のヘルスチェック
bashcurl http://loki:3100/ready
  1. 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]"}}

発生条件:

  • フィールドの型が不一致
  • インデックステンプレートが正しく適用されていない

解決方法:

  1. 既存インデックスを削除して再作成
bashcurl -X DELETE http://elasticsearch:9200/nginx-access-*
  1. インデックステンプレートの再適用
bashcurl -X PUT http://elasticsearch:9200/_index_template/nginx-access -H 'Content-Type: application/json' -d @template.json
  1. 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 値の参照

解決方法:

  1. Lua スクリプトの構文チェック
bashluac -p sampling.lua
  1. nil チェックの追加
lualocal status = tonumber(record["status"])
if not status then
    return 1, timestamp, record  -- status が nil の場合は保持
end
  1. デバッグログの追加
luafunction cb_filter(tag, timestamp, record)
    print("Processing record: " .. require("cjson").encode(record))
    -- 処理内容
end

まとめ

本記事では、Nginx のログを Fluent Bit で収集し、Loki と Elasticsearch に集約する実践的な方法を解説しました。ログ管理における重要なポイントを改めて整理しましょう。

アーキテクチャ設計の要点:

#ポイント理由
1軽量なログコレクターの選択リソース消費を最小限に抑える
2JSON 形式でのログ出力パース処理の簡素化とメンテナンス性向上
3用途別のストレージ使い分けコストと機能のバランス最適化
4柔軟なサンプリング戦略ストレージコストの削減と重要データの保持

運用における推奨事項:

  • エラーログは常に全件保存 して、障害調査時の情報欠落を防ぎましょう
  • ヘルスチェックログは大幅にサンプリング して、ノイズを削減できます
  • 時間帯やレスポンスタイムに応じたサンプリング で、効率的なデータ保持が実現できますね
  • Loki でリアルタイム監視、Elasticsearch で詳細分析 という使い分けが効果的です

本記事で紹介した構成を導入することで、月間数 TB のログを効率的に管理しつつ、必要な情報は確実に保存できる仕組みが構築できるでしょう。Docker Compose を使ったローカル環境での検証から始めて、本番環境への展開を進めてみてください。

ログ管理は、サービスの安定運用において極めて重要な要素となっています。適切なツールと戦略を組み合わせることで、コストを抑えながら高品質な監視・分析環境を実現できますね。

関連リンク