Skip to content
Published on

Grafana Loki ログ管理完全ガイド:LogQLクエリ・収集パイプライン・アラート設定

Authors
  • Name
    Twitter
Grafana Loki Log Management

はじめに

マイクロサービスアーキテクチャとKubernetesベースのインフラが普及するにつれて、数百のコンテナから溢れ出すログを効率的に収集・分析することが運用の核心課題となっている。ElasticsearchベースのELKスタックは長らくログ管理の標準であったが、大規模環境での高いインフラコストと運用の複雑さが問題として指摘されてきた。

Grafana Lokiは「ログのためのPrometheus」という哲学から生まれたログ収集システムである。ログの全文をインデキシングする代わりにラベルメタデータのみをインデキシングすることで、ストレージコストを劇的に削減しながらも、LogQLという強力なクエリ言語によりリアルタイムのログ分析とメトリクス抽出をサポートする。

本記事では、Lokiのアーキテクチャから、LogQLクエリ構文、収集パイプライン構成、アラート設定、そして実践的な運用パターンまで包括的に解説する。


1. Lokiアーキテクチャ概要

Lokiはマイクロサービスアーキテクチャで設計されており、各コンポーネントを独立して水平スケーリングできる。コアコンポーネントは以下の通りである。

Distributor

収集エージェント(Promtail、Alloyなど)からのログpushリクエストを受信する最初のコンポーネントである。受信したログストリームの妥当性を検証した後、コンシステントハッシュリングを使用して適切なIngesterにルーティングする。レプリケーションファクターに基づいて複数のIngesterに同時に送信し、データ損失を防止する。

Ingester

Distributorから受け取ったログをメモリにバッファリングした後、圧縮されたチャンク形式で長期ストレージ(S3、GCS、Azure Blobなど)に書き込むコンポーネントである。クエリリクエストが届くと、まだフラッシュされていないインメモリデータも併せて返却する。

Querier

LogQLクエリを処理する読み取りパスの中核コンポーネントである。Ingesterのインメモリデータと長期ストレージのチャンクデータをマージしてクエリ結果を生成する。Query Frontendはクエリの分割とキャッシングにより大規模レンジクエリのパフォーマンスを最適化する。

Compactor

長期ストレージに保存されたインデックスファイルを圧縮・最適化するバックグラウンドコンポーネントである。リテンションポリシーの適用と削除処理も担当する。

# Lokiマイクロサービスモード基本設定例
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  path_prefix: /loki
  storage:
    s3:
      endpoint: s3.amazonaws.com
      bucketnames: loki-chunks
      region: ap-northeast-2
      access_key_id: ACCESS_KEY
      secret_access_key: SECRET_KEY
  replication_factor: 3
  ring:
    kvstore:
      store: memberlist

schema_config:
  configs:
    - from: '2024-01-01'
      store: tsdb
      object_store: s3
      schema: v13
      index:
        prefix: index_
        period: 24h

storage_config:
  tsdb_shipper:
    active_index_directory: /loki/tsdb-index
    cache_location: /loki/tsdb-cache

limits_config:
  max_query_parallelism: 32
  max_query_series: 500
  retention_period: 30d

compactor:
  working_directory: /loki/compactor
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h

2. ストレージ構造とインデキシング戦略

Lokiの最大の差別化ポイントはラベルベースインデキシング戦略である。Elasticsearchがログ内容のすべてのトークンを転置インデックスで構築するのに対し、Lokiはラベルメタデータのみをインデキシングし、ログ本文は圧縮されたチャンクとしてオブジェクトストレージに保存する。

インデックスとチャンクの分離

  • インデックス: ラベルの組み合わせと時間範囲をマッピングする少量のメタデータ。TSDB形式で保存
  • チャンク: 実際のログラインをgzip/snappyで圧縮して保存。S3、GCSなど低コストのオブジェクトストレージを活用

このアーキテクチャにより、1日100GBのログを処理する場合、Elasticsearchと比較して約70〜80%のストレージコストを削減できる。

ラベル設計原則

ラベルのカーディナリティが高いとインデックスサイズが爆発的に増大するため、以下の原則を守る必要がある。

  • 静的ラベルの使用:namespace、service、environmentなど値の種類が限定された属性
  • 動的値の禁止:user_id、request_id、IPアドレスなど無限に増加する値はラベルとして使用しない
  • パーシングで代替:動的属性はLogQLパイプラインで抽出してフィルタリング

3. LogQLクエリ構文の深掘り

LogQLはPromQLにインスパイアされたLokiのクエリ言語で、ログストリームセレクタとパイプラインステージで構成される。

3.1 ログストリームセレクタ

波括弧内にラベルマッチャーを指定して対象のログストリームを選択する。

# 完全一致
{namespace="production", app="api-gateway"}

# 否定一致
{namespace="production", app!="debug-tool"}

# 正規表現マッチング
{namespace="production", app=~"api-.+"}

# 正規表現除外
{namespace=~"prod|staging", app!~"test-.+"}

3.2 パイプラインステージ

ストリームセレクタの後にパイプ(|)記号で複数の処理段階を連結する。

ラインフィルタ

# 文字列含有フィルタ
{app="api-gateway"} |= "error"

# 文字列非含有フィルタ
{app="api-gateway"} != "healthcheck"

# 正規表現フィルタ
{app="api-gateway"} |~ "status=[45]\\d{2}"

# 正規表現除外フィルタ
{app="api-gateway"} !~ "GET /health"

パーサー

# JSONログパーシング - すべてのJSONフィールドをラベルとして抽出
{app="api-gateway"} | json

# 特定のJSONフィールドのみ抽出
{app="api-gateway"} | json level, method, duration

# logfmt形式パーシング
{app="api-gateway"} | logfmt

# 正規表現パーシング - パターンマッチングでフィールド抽出
{app="nginx"} | regexp `(?P<ip>\\S+) - - \\[(?P<ts>.+?)\\] "(?P<method>\\S+) (?P<path>\\S+)"`

# patternパーサー - 簡潔なパターンマッチング
{app="nginx"} | pattern `<ip> - - [<_>] "<method> <path> <_>" <status> <size>`

ラベルフィルタ

# パーシング後の抽出ラベルでフィルタリング
{app="api-gateway"} | json | level="error"
{app="api-gateway"} | json | duration > 500ms
{app="api-gateway"} | json | status >= 400 and method="POST"

3.3 メトリクスクエリ

LogQLではログストリームをメトリクスに変換して時系列データを生成できる。Grafanaダッシュボードとアラートルールで不可欠な機能である。

# 秒間ログ発生率(ログレンジ集約)
rate({app="api-gateway"} |= "error" [5m])

# 特定ステータスコードの秒間発生率
sum(rate({app="api-gateway"} | json | status >= 500 [5m])) by (method)

# レスポンスタイム分布(quantile抽出)
quantile_over_time(0.99, {app="api-gateway"} | json | unwrap duration [5m]) by (method)

# バイト単位転送量合計
sum(bytes_over_time({app="nginx"} [1h])) by (namespace)

# エラー率計算(エラーログ数 / 全ログ数)
sum(rate({app="api-gateway"} | json | level="error" [5m]))
/
sum(rate({app="api-gateway"} [5m]))

4. PromtailとGrafana Alloy収集パイプライン

Promtail(レガシーエージェント)

PromtailはLoki専用のログ収集エージェントで、各ノードでDaemonSetとして実行されログファイルを監視しLokiに送信する。2025年2月に公式LTS(Long-Term Support)モードに移行し、2026年3月にEOLが予定されている。

# Promtail設定例
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki-gateway:3100/loki/api/v1/push
    tenant_id: default

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # Namespaceラベルの追加
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      # Pod名ラベルの追加
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      # コンテナ名ラベルの追加
      - source_labels: [__meta_kubernetes_pod_container_name]
        target_label: container
    pipeline_stages:
      # Dockerログ形式のパーシング
      - docker: {}
      # JSONログのパーシング
      - json:
          expressions:
            level: level
            msg: message
      # ラベルの設定
      - labels:
          level:
      # タイムスタンプの抽出
      - timestamp:
          source: time
          format: RFC3339Nano

Grafana Alloy(次世代エージェント)

Grafana AlloyはPromtailの後継であり、ログだけでなくメトリクス、トレース、プロファイリングまで単一エージェントで収集するOpenTelemetryベースの統合テレメトリコレクタである。

// Grafana Alloy設定例(River構文)
discovery.kubernetes "pods" {
  role = "pod"
}

discovery.relabel "pod_logs" {
  targets = discovery.kubernetes.pods.targets

  rule {
    source_labels = ["__meta_kubernetes_namespace"]
    target_label  = "namespace"
  }

  rule {
    source_labels = ["__meta_kubernetes_pod_name"]
    target_label  = "pod"
  }

  rule {
    source_labels = ["__meta_kubernetes_pod_container_name"]
    target_label  = "container"
  }
}

loki.source.kubernetes "pod_logs" {
  targets    = discovery.relabel.pod_logs.output
  forward_to = [loki.process.pipeline.receiver]
}

loki.process "pipeline" {
  stage.json {
    expressions = {
      level   = "level",
      message = "msg",
    }
  }

  stage.labels {
    values = {
      level = "",
    }
  }

  forward_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url = "http://loki-gateway:3100/loki/api/v1/push"
  }
}

5. Kubernetes環境でのログ収集

Kubernetes環境でLokiをデプロイする際は、Helmチャートを使用するのが標準的なアプローチである。

# Loki Helmチャートインストール(Simple Scalableモード)
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm install loki grafana/loki \
  --namespace observability \
  --create-namespace \
  --values loki-values.yaml

# Grafana Alloy DaemonSetインストール
helm install alloy grafana/alloy \
  --namespace observability \
  --values alloy-values.yaml

Kubernetes環境で注意すべき収集設定のポイントは以下の通りである。

  • Namespaceフィルタリング: 不要なシステムログ(kube-systemなど)を除外してコスト削減
  • マルチテナンシー: X-Scope-OrgIDヘッダーを活用してチーム別にログを分離
  • リソース制限: IngesterとQuerierのメモリ制限を適切に設定してOOMを防止
  • PVC管理: IngesterのWAL(Write-Ahead Log)用の永続ボリュームを確保

6. アラートルール設定(Loki Ruler)

Loki RulerはLogQLメトリクスクエリを定期的に評価し、閾値を超過した場合にAlertmanagerにアラートを送信する。PrometheusのアラートルールとYAML形式で互換性がある。

# loki-alert-rules.yaml
groups:
  - name: application-errors
    rules:
      # HTTP 5xxエラー急増の検知
      - alert: HighHTTP5xxRate
        expr: |
          sum(rate({namespace="production"} | json | status >= 500 [5m])) by (app)
          > 10
        for: 5m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: 'HTTP 5xxエラー率の急増を検知'
          description: 'アプリ {{ .Labels.app }} で5分間に秒間10件以上の5xxエラーが発生しています。'

      # エラーログ比率の監視
      - alert: HighErrorLogRatio
        expr: |
          sum(rate({namespace="production"} | json | level="error" [10m])) by (app)
          /
          sum(rate({namespace="production"} [10m])) by (app)
          > 0.05
        for: 10m
        labels:
          severity: warning
          team: platform
        annotations:
          summary: 'エラーログ比率が5%超過'
          description: 'アプリ {{ .Labels.app }} のエラーログ比率が10分間5%を超過しました。'

      # ログ収集停止の検知
      - alert: LogIngestionStopped
        expr: |
          sum(rate({namespace="production"} [15m])) by (app) == 0
        for: 15m
        labels:
          severity: critical
          team: platform
        annotations:
          summary: 'ログ収集停止を検知'
          description: 'アプリ {{ .Labels.app }} から15分間ログが収集されていません。'

  - name: security-alerts
    rules:
      # 認証失敗の多発検知
      - alert: BruteForceAttempt
        expr: |
          sum(rate({app="auth-service"} |= "authentication failed" [5m])) by (source_ip)
          > 5
        for: 2m
        labels:
          severity: critical
          team: security
        annotations:
          summary: 'ブルートフォース攻撃の疑い'
          description: 'IP {{ .Labels.source_ip }} から5分間に秒間5件以上の認証失敗が発生しました。'

RulerをLokiに設定するには、以下のように構成する。

# Loki ruler設定ブロック
ruler:
  storage:
    type: local
    local:
      directory: /loki/rules
  rule_path: /loki/rules-temp
  alertmanager_url: http://alertmanager:9093
  ring:
    kvstore:
      store: memberlist
  enable_api: true
  evaluation_interval: 1m

7. ダッシュボード構成パターン

GrafanaでLokiデータソースを活用した効果的なダッシュボード構成パターンは以下の通りである。

コアパネル構成

  1. ログボリュームヒストグラム: レベル(info、warn、error)ごとに時間帯別のログ発生量をスタック表示
  2. エラー率時系列グラフ: サービスごとのエラーログ比率をリアルタイムでモニタリング
  3. Top-Nエラーメッセージテーブル: 最も頻度の高いエラーパターンを集計して優先度を把握
  4. ログ探索パネル: 変数を活用した動的フィルタリングでドリルダウン

Grafana変数設定

  • namespace変数: label_values(namespace)クエリで動的にNamespaceを選択
  • app変数: label_values(app)クエリでサービスフィルタリング
  • 変数チェーンによる階層的フィルタ: Namespace選択後、該当Namespaceのアプリのみを表示

8. 比較表:Loki vs Elasticsearch vs CloudWatch

項目Grafana LokiElasticsearchAWS CloudWatch Logs
インデキシング方式ラベルのみ全文転置インデックスロググループベース
ストレージコスト非常に低い(オブジェクトストレージ)高い(SSD必要)中程度(従量課金)
クエリ言語LogQL(PromQL類似)Lucene / KQL / ES|QLCloudWatch Insights
全文検索制限的(ブルートフォース)非常に強力中程度
K8s統合ネイティブ追加設定が必要EKS統合
運用難易度低〜中高い(JVMチューニング)非常に低い(マネージド)
水平スケーリングコンポーネント単位で独立シャード/レプリカ管理自動
アラート統合Ruler + AlertmanagerWatcher / ElastAlertCloudWatch Alarms
マルチテナンシーネイティブ対応インデックス分離で実現アカウント/リージョン分離
日量100GB想定コスト月額50〜100 USD月額300〜600 USD月額150〜300 USD

選択基準の要約

  • Loki: Kubernetes環境でコスト効率の高いログ管理が目標であり、ラベルベースのフィルタリングで十分な場合
  • Elasticsearch: 非構造化ログに対する強力な全文検索が必須、またはセキュリティ分析(SIEM)用途
  • CloudWatch: AWSネイティブワークロードで運用負担を最小化したい場合

9. 障害事例と復旧手順

事例1:Ingester OOM(Out of Memory)

症状: Ingester Podが繰り返しOOMKilled、ログ収集が停止

原因: ラベルのカーディナリティ爆発によるインメモリストリームの過剰生成

復旧手順:

  1. 高カーディナリティラベルの特定:LogQLクエリでユニークストリーム数を確認
  2. Promtail/Alloy設定で問題のラベルを削除またはrelabel
  3. Ingesterのメモリ制限を引き上げ(暫定措置)
  4. limits_config.max_streams_per_userを適切な値に制限

事例2:クエリタイムアウト

症状: Grafanaダッシュボードでクエリのロードに30秒以上かかるかタイムアウト

原因: 過大な時間範囲のクエリまたは非効率なLogQL

復旧手順:

  1. クエリ範囲を縮小し、ストリームセレクタをより具体的にする
  2. Query Frontendのsplit_queries_by_interval設定でクエリを分割
  3. 頻繁に使用するクエリはRecording Ruleで事前計算
  4. キャッシュ設定(memcached、Redis)の確認と適用

事例3:チャンクストレージ障害

症状: IngesterログでS3/GCSアップロードエラーが繰り返し発生

復旧手順:

  1. オブジェクトストレージのIAM権限を確認
  2. ネットワーク接続状態を点検
  3. IngesterのWAL(Write-Ahead Log)の整合性を確認
  4. flush_on_shutdown: true設定で安全な終了を保証

10. 運用チェックリスト

デプロイ前チェックリスト

  • ラベルカーディナリティ設計のレビュー完了
  • リテンションポリシーの設定
  • オブジェクトストレージバケットとIAM権限の構成
  • マルチテナンシー戦略の策定(必要に応じて)
  • リソース制限(requests/limits)の設定

運用中チェックリスト

  • Ingesterメモリ使用率のモニタリング(80%未満を維持)
  • ログ収集遅延(lag)のモニタリング
  • クエリ応答時間のSLO準拠確認
  • チャンクストレージ成功率のモニタリング
  • Compactorジョブの正常動作確認

パフォーマンス最適化チェックリスト

  • Query Frontendキャッシュの適用(memcached推奨)
  • Recording Ruleで頻用メトリクスの事前計算
  • 不要なログドロップルールの適用(debugレベルなど)
  • チャンク圧縮アルゴリズムの最適化(snappy vs gzip)
  • インデックス期間の適切性レビュー

まとめ

Grafana Lokiは「すべてのログをインデキシングする必要はない」というパラダイムシフトを通じて、大規模Kubernetes環境でコスト効率の高いログ管理を可能にする。LogQLのパイプラインベースクエリ、Rulerによるアラート、そしてGrafanaエコシステムとの緊密な統合は、Lokiをクラウドネイティブオブザーバビリティの中核ツールとして確立させた。

特にPromtailからGrafana Alloyへの移行が進む中、ログだけでなくメトリクス、トレース、プロファイリングまで単一エージェントで統合収集する時代が到来している。ELKスタックの高い運用コストに負担を感じているチームであれば、Loki導入を積極的に検討すべき時期である。

運用で最も重要なのは、ラベル設計とカーディナリティ管理である。適切なラベル戦略なしでは、Lokiであってもストレージとパフォーマンスの問題に直面する可能性がある。本記事で解説したアーキテクチャの理解、LogQLの活用、アラート設定、そして障害対応パターンを基に、堅牢なログ管理体制を構築されたい。