- Authors
- Name
- はじめに
- 1. Lokiアーキテクチャ概要
- 2. ストレージ構造とインデキシング戦略
- 3. LogQLクエリ構文の深掘り
- 4. PromtailとGrafana Alloy収集パイプライン
- 5. Kubernetes環境でのログ収集
- 6. アラートルール設定(Loki Ruler)
- 7. ダッシュボード構成パターン
- 8. 比較表:Loki vs Elasticsearch vs CloudWatch
- 9. 障害事例と復旧手順
- 10. 運用チェックリスト
- まとめ

はじめに
マイクロサービスアーキテクチャと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データソースを活用した効果的なダッシュボード構成パターンは以下の通りである。
コアパネル構成
- ログボリュームヒストグラム: レベル(info、warn、error)ごとに時間帯別のログ発生量をスタック表示
- エラー率時系列グラフ: サービスごとのエラーログ比率をリアルタイムでモニタリング
- Top-Nエラーメッセージテーブル: 最も頻度の高いエラーパターンを集計して優先度を把握
- ログ探索パネル: 変数を活用した動的フィルタリングでドリルダウン
Grafana変数設定
namespace変数:label_values(namespace)クエリで動的にNamespaceを選択app変数:label_values(app)クエリでサービスフィルタリング- 変数チェーンによる階層的フィルタ: Namespace選択後、該当Namespaceのアプリのみを表示
8. 比較表:Loki vs Elasticsearch vs CloudWatch
| 項目 | Grafana Loki | Elasticsearch | AWS CloudWatch Logs |
|---|---|---|---|
| インデキシング方式 | ラベルのみ | 全文転置インデックス | ロググループベース |
| ストレージコスト | 非常に低い(オブジェクトストレージ) | 高い(SSD必要) | 中程度(従量課金) |
| クエリ言語 | LogQL(PromQL類似) | Lucene / KQL / ES|QL | CloudWatch Insights |
| 全文検索 | 制限的(ブルートフォース) | 非常に強力 | 中程度 |
| K8s統合 | ネイティブ | 追加設定が必要 | EKS統合 |
| 運用難易度 | 低〜中 | 高い(JVMチューニング) | 非常に低い(マネージド) |
| 水平スケーリング | コンポーネント単位で独立 | シャード/レプリカ管理 | 自動 |
| アラート統合 | Ruler + Alertmanager | Watcher / ElastAlert | CloudWatch 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、ログ収集が停止
原因: ラベルのカーディナリティ爆発によるインメモリストリームの過剰生成
復旧手順:
- 高カーディナリティラベルの特定:LogQLクエリでユニークストリーム数を確認
- Promtail/Alloy設定で問題のラベルを削除またはrelabel
- Ingesterのメモリ制限を引き上げ(暫定措置)
limits_config.max_streams_per_userを適切な値に制限
事例2:クエリタイムアウト
症状: Grafanaダッシュボードでクエリのロードに30秒以上かかるかタイムアウト
原因: 過大な時間範囲のクエリまたは非効率なLogQL
復旧手順:
- クエリ範囲を縮小し、ストリームセレクタをより具体的にする
- Query Frontendの
split_queries_by_interval設定でクエリを分割 - 頻繁に使用するクエリはRecording Ruleで事前計算
- キャッシュ設定(memcached、Redis)の確認と適用
事例3:チャンクストレージ障害
症状: IngesterログでS3/GCSアップロードエラーが繰り返し発生
復旧手順:
- オブジェクトストレージのIAM権限を確認
- ネットワーク接続状態を点検
- IngesterのWAL(Write-Ahead Log)の整合性を確認
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の活用、アラート設定、そして障害対応パターンを基に、堅牢なログ管理体制を構築されたい。