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

目次(26 項目)
大規模な可観測性(observability)を実現するには、ログ、メトリクス、トレースに対する統一されたアプローチが不可欠です。Grafana LGTMスタック(Loki、Grafana、Tempo、Mimir)は、堅牢なクラウドネイティブソリューションを提供します。このガイドでは、OpenTelemetry(OTel)との統合、クロスシグナル相関、コスト最適化に焦点を当て、本番環境でのデプロイについて詳しく説明します。
モダンDevOps&eBPFオブザーバビリティ実践
アーキテクチャ概要
OpenTelemetry Collectorによって強化されたLGTMスタックは、まとまりのある可観測性プラットフォームを形成します。
主要コンポーネント:
- OpenTelemetry Collector: テレメトリーデータの取り込み、処理、エクスポートを行うユニバーサルエージェントです。各LGTMコンポーネントに転送する前にデータを標準化する、中枢神経系の役割を果たします。
- Mimir (Metrics): Prometheusメトリクス向けの水平スケーラブルな長期ストレージです。高可用性、マルチテナンシー、効率的なクエリを提供します。
- Loki (Logs): コスト効率とスケーラビリティを考慮して設計されたログ集約システムです。ログコンテンツ全体ではなく、メタデータ(ラベル)をインデックス化するため、クエリが効率的です。
- Tempo (Traces): 大容量で低コストの分散トレーシングバックエンドです。トレースを保存し、トレースIDに基づいて効率的な検索を可能にします。
- Grafana: ダッシュボード、アラート、すべてのテレメトリーシグナルにわたる統合された探索を提供する可視化レイヤーです。
- Object Storage (S3/GCS): Mimir、Loki、Tempoの主要な長期ストレージであり、耐久性とコスト効率のためにクラウドネイティブなオブジェクトストアを活用します。
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 ステータス:
promql
( 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日間ウィンドウ):
promql
sum by (service_name, http_route) ( increase(service_latency_slo_error_budget[7d]) ) - 残りのエラーバジェット:
promql
100 - ( sum by (service_name, http_route) ( increase(service_latency_slo_error_budget[7d]) ) )
オブジェクトストレージのコスト最適化 (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を防ぎ、極端な負荷の下でデータを適切にドロップするために、ソフトリミットとハードリミットを設定する。yamlprocessors: 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を使用して、取り込み前に高カーディナリティラベルを削除または名前変更する。yamlprocessors: 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-OrgIDHTTPヘッダー(またはOTLP属性tenant_id)を使用してテナントを分離します。各テナントは分離されたデータを受け取ります。 - Loki: Mimirと同様に、
X-Scope-OrgIDまたはloki.tenant.idOTLPログ属性を使用します。 - Tempo:
X-Scope-OrgIDまたはtenant_idOTLPリソース属性を使用します。 - 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からのログ)がわかっている場合に優れています。広大で非構造化なログに対する任意の全文検索では、従来のシステムの方がパフォーマンスが高い場合がありますが、コストは大幅に高くなります。
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

OpenTelemetry CollectorとeBPFを本番環境で活用:コード不要のトレース、メトリクス、Prometheusパイプライン
OpenTelemetry CollectorとeBPFを本番環境で活用し、コード不要のトレース、メトリクス、Prometheusパイプラインを構築するための、本番環境レベルのアーキテクチャとコード例を含む包括的なガイドです。
Read more
PrometheusとGrafanaによるアラートとバーンレートの設計
サービスレベル目標(SLO)とエラーバジェットのために、マルチウィンドウマルチバーンレートPromQLルールを使った本番環境のPrometheusとGrafanaアラートを設計します。
Read more
KubernetesにおけるeBPFによる高性能な可観測性:サイドカー税を回避する
KubernetesでのeBPF可観測性を深く掘り下げ、Envoyサイドカーの排除、カーネルプローブ、BPFリングバッファ、ゼロコードテレメトリーインストルメンテーションについて解説します。
Read more