•19 min read

OpenTelemetry LGTMスタック:Loki、Grafana、Tempo、Mimir本番環境ガイド

OpenTelemetry LGTMスタック:Loki、Grafana、Tempo、Mimir本番環境ガイド

大規模な可観測性(observability)を実現するには、ログ、メトリクス、トレースに対する統一されたアプローチが不可欠です。Grafana LGTMスタック(Loki、Grafana、Tempo、Mimir)は、堅牢なクラウドネイティブソリューションを提供します。このガイドでは、OpenTelemetry(OTel)との統合、クロスシグナル相関、コスト最適化に焦点を当て、本番環境でのデプロイについて詳しく説明します。

Audio Briefing
0:00 / 0:00

アーキテクチャ概要

OpenTelemetry Collectorによって強化されたLGTMスタックは、まとまりのある可観測性プラットフォームを形成します。

主要コンポーネント:

  • OpenTelemetry Collector: テレメトリーデータの取り込み、処理、エクスポートを行うユニバーサルエージェントです。各LGTMコンポーネントに転送する前にデータを標準化する、中枢神経系の役割を果たします。
  • Mimir (Metrics): Prometheusメトリクス向けの水平スケーラブルな長期ストレージです。高可用性、マルチテナンシー、効率的なクエリを提供します。
  • Loki (Logs): コスト効率とスケーラビリティを考慮して設計されたログ集約システムです。ログコンテンツ全体ではなく、メタデータ(ラベル)をインデックス化するため、クエリが効率的です。
  • Tempo (Traces): 大容量で低コストの分散トレーシングバックエンドです。トレースを保存し、トレースIDに基づいて効率的な検索を可能にします。
  • Grafana: ダッシュボード、アラート、すべてのテレメトリーシグナルにわたる統合された探索を提供する可視化レイヤーです。
  • Object Storage (S3/GCS): Mimir、Loki、Tempoの主要な長期ストレージであり、耐久性とコスト効率のためにクラウドネイティブなオブジェクトストアを活用します。
Advertisement

OpenTelemetry Collector パイプライン

OTel Collectorは、テレメトリーデータを標準化するために不可欠です。OTLPを受信し、処理してMimir、Loki、Tempoにエクスポートするように設定します。

Collector の設定 (otel-collector-config.yaml)

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
    send_batch_size: 1024
    timeout: 10s
  resource/add_service_name: # Ensure service.name is present for correlation
    attributes:
      - key: service.name
        value: unknown_service
        action: upsert
  attributes/add_k8s_labels: # Example: Add Kubernetes labels as resource attributes
    actions:
      - key: k8s.pod.name
        from_attribute: k8s.pod.name
        action: upsert
      - key: k8s.namespace.name
        from_attribute: k8s.namespace.name
        action: upsert
  transform/logs: # Transform OTel logs to Loki-compatible format
    log_statements:
      - context: log
        statements:
          - set(attributes["loki.tenant.id"], "default") # Multi-tenancy example
          - set(attributes["loki.resource.labels"], Concat([resource.attributes["service.name"], resource.attributes["k8s.namespace.name"]], ","))
          # Ensure trace_id and span_id are available as attributes for Loki
          - set(attributes["trace_id"], SpanIDToHex(trace_id))
          - set(attributes["span_id"], SpanIDToHex(span_id))
  transform/metrics: # Example: Add tenant ID to metrics
    metric_statements:
      - context: metric
        statements:
          - set(attributes["tenant_id"], "default")
  resource/add_tenant_id: # Add tenant ID to resource attributes for traces
    attributes:
      - key: tenant_id
        value: default
        action: upsert

exporters:
  otlp/mimir:
    endpoint: mimir-gateway.observability.svc.cluster.local:9090 # Mimir OTLP endpoint
    tls:
      insecure: true # Use proper TLS in production
  otlp/loki:
    endpoint: loki-gateway.observability.svc.cluster.local:3100 # Loki OTLP endpoint
    tls:
      insecure: true
  otlp/tempo:
    endpoint: tempo-gateway.observability.svc.cluster.local:4317 # Tempo OTLP endpoint
    tls:
      insecure: true
  logging: # For debugging
    verbosity: detailed

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [resource/add_service_name, attributes/add_k8s_labels, transform/metrics, batch]
      exporters: [otlp/mimir, logging]
    logs:
      receivers: [otlp]
      processors: [resource/add_service_name, attributes/add_k8s_labels, transform/logs, batch]
      exporters: [otlp/loki, logging]
    traces:
      receivers: [otlp]
      processors: [resource/add_service_name, resource/add_tenant_id, batch]
      exporters: [otlp/tempo, logging]

説明:

  • receivers.otlp: gRPCおよびHTTP経由でOTLPデータを受け入れるようにコレクターを設定します。
  • processors.batch: 効率的なエクスポートのためにテレメトリーデータをバッチ処理します。
  • processors.resource/add_service_name: 相関に不可欠なservice.nameが常に存在するようにします。
  • processors.attributes/add_k8s_labels: Kubernetesメタデータでテレメトリーを強化する方法を示しており、フィルタリングやグループ化に役立ちます。
  • processors.transform/logs: これはLokiにとって重要です。loki.tenant.idとloki.resource.labels(Lokiがインデックス付きラベルとして使用)を追加し、trace_idとspan_idをログ属性として抽出し、トレースとログの相関を可能にします。
  • exporters.otlp/mimir, otlp/loki, otlp/tempo: 各LGTMコンポーネントへのOTLP直接エクスポートです。これらのエンドポイントがクラスター内で解決可能であることを確認してください。
  • service.pipelines: 各シグナルタイプ(メトリクス、ログ、トレース)について、データがレシーバー、プロセッサー、エクスポーターをどのように流れるかを定義します。

クロスシグナル相関: トレースからログへ & ログからトレースへ

統合された可観測性は、シグナル間を移動する能力にかかっています。Grafanaはデータリンク設定でこれを容易にします。

1. トレースからログへのドリルダウン

Tempoでトレースを表示しているときに、特定のスパンに関連付けられたログにジャンプしたい場合があります。これには、ログにtrace_idとspan_idが属性として存在する必要があります。OTel Collectorのtransform/logsプロセッサーがこれを処理します。

Grafana データリンク設定 (Tempo データソース):

{
  "name": "Logs for Span",
  "url": "/explore?orgId=1&left=[\"now-1h\",\"now\",\"Loki\",{\"expr\":\"{service_name=\\\"$${service.name}\\\", trace_id=\\\"$${traceID}\\\", span_id=\\\"$${spanID}\\\"}\",\"refId\":\"A\",\"queryType\":\"range\"},{\"ui\":true}]",
  "targetBlank": true
}

説明:

  • $${service.name}: トレーススパンから推測されます。
  • $${traceID}: 現在のスパンのトレースID。
  • $${spanID}: 現在のスパンのスパンID。
  • exprは、LokiのLogQLを使用してservice_name、trace_id、span_idでログをフィルタリングします。これらのラベルは、Lokiログに存在する必要があります(OTel Collectorによって設定されたとおり)。

2. ログからトレースへのドリルダウン

Lokiのログ行から、Tempoの対応するトレースにジャンプしたい場合があります。これには、ログにtrace_idが属性として存在する必要があります。

Grafana データリンク設定 (Loki データソース):

{
  "name": "Trace for Log",
  "url": "/explore?orgId=1&left=[\"now-1h\",\"now\",\"Tempo\",{\"query\":\"$${__line.trace_id}\",\"queryType\":\"search\",\"refId\":\"A\"},{\"ui\":true}]",
  "targetBlank": true
}

説明:

  • $${__line.trace_id}: この特別なGrafana変数は、現在のログ行からtrace_id属性を抽出します。これは、OTel Collectorがtrace_idをログの属性として追加していることを前提としています。

高シグナル SLI/SLO ダッシュボード

Mimirを活用することで、堅牢なSLI/SLOダッシュボードを構築できます。この例では、リクエストレイテンシーSLIに焦点を当てます。

1. SLI/SLO の定義

  • SLI: p99レイテンシーが500ms未満で処理されたリクエストの割合。
  • SLO: 7日間のローリングウィンドウで、リクエストの99.9%がSLIを満たす必要があります。

2. SLI 用の Mimir レコーディングルール

Mimirでレコーディングルールを定義し、SLIデータを事前集計します。これにより、生メトリクスへのクエリ負荷が軽減され、ダッシュボードのパフォーマンスが向上します。

# rules.yaml for Mimir
groups:
  - name: service-sli
    rules:
      - record: service_latency_sli_total
        expr: |
          sum by (service_name, http_route) (
            rate(http_server_request_duration_bucket{le="0.5"}[5m])
          )
      - record: service_latency_slo_error_budget
        expr: |
          1 - (
            sum by (service_name, http_route) (
              rate(http_server_request_duration_bucket{le="0.5"}[5m])
            )
            /
            sum by (service_name, http_route) (
              rate(http_server_request_duration_count[5m])
            )
          )

説明:

  • http_server_request_duration_bucket: アプリケーションがOpenTelemetry HTTPサーバーリクエスト期間ヒストグラムをエクスポートしていることを前提としています。
  • service_latency_sli_total: レイテンシーターゲット内のリクエスト数をカウントします。
  • service_latency_slo_error_budget: 消費されたエラーバジェットを計算します。

3. Grafana ダッシュボードパネル

  • 現在の SLI ステータス:
    (
      sum by (service_name, http_route) (service_latency_sli_total)
      /
      sum by (service_name, http_route) (rate(http_server_request_duration_count[5m]))
    ) * 100
    
  • エラーバジェット燃焼率 (7日間ウィンドウ):
    sum by (service_name, http_route) (
      increase(service_latency_slo_error_budget[7d])
    )
    
  • 残りのエラーバジェット:
    100 - (
      sum by (service_name, http_route) (
        increase(service_latency_slo_error_budget[7d])
      )
    )
    
Advertisement

オブジェクトストレージのコスト最適化 (S3/GCS)

Mimir、Loki、Tempoはオブジェクトストレージに大きく依存しています。これを最適化することは、コスト管理にとって非常に重要です。

1. データ保持ポリシー

データの重要度とコンプライアンスに基づいて保持期間を設定します。

  • Mimir: 高解像度メトリクスには短期(例:30日)、集計メトリクスには長期。Mimirのインジェスターとコンパクターがこれを管理します。
  • Loki: ログはフォレンジックに必要とされることが多いため、通常は長期(例:90〜180日)。Lokiのtable_managerとcompactorが保持を処理します。
  • Tempo: 特定のコンプライアンスで長期が必要な場合を除き、大容量のため通常は短期(例:7〜30日)。Tempoのcompactorが保持を管理します。

Loki 保持設定の例 (loki.yaml):

table_manager:
  retention_period: 90d # 90 days for index and chunks
compactor:
  retention_enabled: true
  retention_period: 90d

2. ストレージ階層とライフサイクルポリシー

クラウドプロバイダーのライフサイクルポリシーを活用して、古いデータを安価なストレージ階層(例:S3 Standard-IA、Glacier、GCS Nearline、Coldline)に移行します。

S3 ライフサイクルポリシーの例 (Terraform aws_s3_bucket_lifecycle_configuration):

resource "aws_s3_bucket_lifecycle_configuration" "loki_bucket_lifecycle" {
  bucket = aws_s3_bucket.loki_chunks.id

  rule {
    id     = "loki-retention"
    status = "Enabled"

    transition {
      days          = 30
      storage_class = "STANDARD_IA" # Infrequent Access
    }

    transition {
      days          = 60
      storage_class = "GLACIER" # Archival
    }

    expiration {
      days = 90 # Final deletion
    }
  }
}

3. データ圧縮

すべてのLGTMコンポーネントは、オブジェクトストレージに保存されるデータに圧縮(Snappy、LZ4、Zstd)を使用します。設定が効率的なコーデックを活用していることを確認してください。これは通常デフォルトですが、確認する価値はあります。

4. シャーディングとインデックス戦略

  • Loki: 書き込みパフォーマンスとクエリ効率のバランスを取るために、max_chunk_ageとchunk_target_sizeを最適化します。max_chunk_ageが小さいほど、フラッシュが頻繁になり、小さなオブジェクトが増える可能性がありますが、インデックスの更新は速くなります。
  • Tempo: 非常に高い取り込みレートがある場合は、インジェスター全体に負荷を分散するためにトレースIDシャーディング戦略を検討してください。

本番環境での落とし穴とトラブルシューティング

1. OTel Collector バックプレッシャー / OOMKilled

症状: OTel CollectorポッドがOOMKilledされるか、高いCPU/メモリ使用量を示し、テレメトリーデータがドロップされる。 原因: 取り込みレートが処理/エクスポート容量を超えているか、batchプロセッサーの設定が利用可能なメモリに対して積極的すぎる。 修正:

  • リソースの増加: OTel Collectorポッドにより多くのCPU/メモリを割り当てる。
  • batchプロセッサーの調整: send_batch_sizeとtimeoutを増やしてより大きなバッチを許可するが、メモリを監視する。
  • memory_limiterプロセッサーの追加: OOMを防ぎ、極端な負荷の下でデータを適切にドロップするために、ソフトリミットとハードリミットを設定する。
    processors:
      memory_limiter:
        limit_mib: 256
        spike_limit_mib: 64
        check_interval: 1s
    
  • スケールアウト: ロードバランサーの背後に複数のOTel Collectorインスタンスをデプロイする。

2. Loki クエリパフォーマンスの低下

症状: LogQLクエリが遅い、特に正規表現や広い時間範囲を含むもの。 原因:

  • ユニークなラベルの組み合わせが多すぎる(高カーディナリティ)。
  • 多くのログストリームをスキャンするクエリ。
  • 非効率的なLogQL式。
  • Lokiクエリフロントエンド/クエリアーのリソース不足。 修正:
  • ラベルのレビュー: ユニークなラベルの数を最小限に抑える。高カーディナリティ属性(例:リクエストID、完全なURLパス)をLokiラベルとして追加しない。代わりにログコンテンツ属性として使用する。
  • LogQLの最適化: 非常に選択的なラベルでクエリを開始する。line_formatを使用して、インデックス化する代わりにログコンテンツからデータを抽出する。
  • Lokiリソースの増加: query-frontendとquerierコンポーネントをスケールする。
  • インデックスの最適化: max_chunk_ageとchunk_target_sizeのバランスが取れていることを確認する。

3. Tempo トレース取り込みの失敗

症状: Tempoでトレースが欠落しているか不完全である。 原因:

  • OTel Collectorがトレースを正しく転送していない。
  • Tempoインジェスターが過負荷または異常である。
  • サービス計測が正しくない(例:トレースコンテキスト伝播の欠落)。 修正:
  • OTel Collectorログの確認: Tempoへのエクスポートエラーを探す。
  • Tempoインジェスターの監視: 健全性とリソース使用率を確認する。必要に応じてスケールする。
  • 計測の検証: すべてのサービスがトレースコンテキスト(例:W3C Trace Contextヘッダー)を正しく伝播していることを確認する。curlとtraceparentヘッダーを使用してテストする。
  • Tempoインジェスターのmax_block_bytesを増やす: トレースが非常に大きい場合、これがボトルネックになる可能性がある。

4. Mimir 高カーディナリティ問題

症状: Mimirインジェスター/コンパクターが苦戦している、ストレージコストが高い、メトリクスクエリが遅い。 原因: メトリクスにユニークなラベルの組み合わせが多すぎる。 修正:

  • メトリクスリラベル: OTel CollectorのmetricstransformプロセッサーまたはPrometheusのrelabel_configsを使用して、取り込み前に高カーディナリティラベルを削除または名前変更する。
    processors:
      metricstransform:
        transforms:
          - include: "http_request_duration_seconds_bucket"
            action: "delete_label"
            label: "request_id" # Example: delete high-cardinality label
    
  • 計測のレビュー: 開発者に高カーディナリティラベルを避けるよう教育する。
  • Mimirの制限: Mimirのmax_series_per_userとmax_label_names_per_seriesを設定して、乱用を防ぐ。

よくある質問

Q1: LGTMスタックでマルチテナンシーをどのように処理しますか?

A1: Mimir、Loki、Tempoはマルチテナンシー向けに設計されています。

  • Mimir: X-Scope-OrgID HTTPヘッダー(またはOTLP属性tenant_id)を使用してテナントを分離します。各テナントは分離されたデータを受け取ります。
  • Loki: Mimirと同様に、X-Scope-OrgIDまたはloki.tenant.id OTLPログ属性を使用します。
  • Tempo: X-Scope-OrgIDまたはtenant_id OTLPリソース属性を使用します。
  • Grafana: 各テナントに個別のデータソースを設定するか、ダッシュボードでX-Scope-OrgIDのテンプレート変数を使用します。

Q2: KubernetesでLGTMスタックをデプロイする推奨方法は?

A2: Helmチャートが標準です。Grafana LabsはLoki、Mimir、Tempoの公式チャートを提供しています。OpenTelemetry Collectorには、opentelemetry-collector Helmチャートを使用します。ステートフルコンポーネント(例:Mimirインジェスター、Lokiインジェスター)には永続ストレージを設定し、堅牢なオブジェクトストレージバックエンドを使用してください。

Q3: LGTMスタック自体をどのように監視しますか?

A3: LGTMコンポーネントはPrometheusメトリクスを公開しています。専用のPrometheusインスタンス(またはMimir自体)をデプロイして、これらのメトリクスをスクレイピングします。Grafanaダッシュボードを作成して、それらの健全性、リソース使用量、取り込みレート、クエリレイテンシー、エラーレートを監視します。主要なメトリクスには、loki_ingester_received_entries_total、mimir_ingester_received_samples_total、tempo_ingester_traces_received_total、およびさまざまな_grpc_server_handled_totalメトリクスが含まれます。

Q4: 既存のPrometheusメトリクスをMimirで使用できますか?

A4: はい。MimirはPrometheusの長期ストレージです。既存のPrometheusサーバーをMimirにリモート書き込みするように設定するか、OpenTelemetry CollectorのPrometheusレシーバーとOTLPエクスポーターをMimirに使用できます。後者は、統一されたOTelアプローチのために一般的に推奨されます。

Q5: Lokiのラベルベースのインデックスと従来の全文ログインデックスのトレードオフは何ですか?

A5:

機能Loki (ラベルベース)従来型 (全文)
コストストレージが少なく、インデックス作成の計算量が少ないストレージが多く、インデックス作成の計算量が多い
クエリ速度ラベルフィルタリングされたクエリでは高速、テキストでは遅い全文検索では高速、複雑なフィルターでは遅い
スケーラビリティ水平方向に高いスケーラビリティスケーラブルだが、インデックス管理が複雑になる場合がある
柔軟性効率的な検索には事前定義されたラベルが必要任意のログコンテンツで検索可能
ユースケース運用デバッグ、既知のパターンセキュリティ分析、アドホックな探索

Lokiは、探しているもの(例:service=Xのnamespace=Yからのログ)がわかっている場合に優れています。広大で非構造化なログに対する任意の全文検索では、従来のシステムの方がパフォーマンスが高い場合がありますが、コストは大幅に高くなります。

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement