- Authors
- Name
- 概要
- アーキテクチャ概要
- 環境準備
- Docker Compose設定
- Loki設定
- Promtail設定
- 実行と確認
- GrafanaでLokiを接続
- ダッシュボード構成
- アラート設定
- プロダクション運用Tips
- 全体データフローまとめ
- まとめ
- クイズ

概要
本番環境においてログは障害対応とデバッグの要です。ELK(Elasticsearch + Logstash + Kibana)スタックが長らく標準でしたが、Elasticsearchの高いリソース使用量と複雑な運用負担が課題でした。Grafana Lokiはこれらの問題を解決するために登場した軽量ログ収集システムで、ログ本文をインデックスせずラベルベースのインデックスのみを行うことで、ストレージコストと運用の複雑さを大幅に削減します。
この記事では、Promtail → Loki → Grafana パイプラインをDocker Composeで構築し、LogQLクエリとアラートルールまで設定する全プロセスを解説します。
アーキテクチャ概要
ログパイプライン全体のフローは以下の通りです:
graph LR
A[Application Logs] -->|tail| B[Promtail]
B -->|HTTP Push| C[Loki]
C -->|Store| D[Object Storage / Filesystem]
C -->|Query| E[Grafana]
E -->|Alert| F[Slack / Email]
各コンポーネントの役割:
| コンポーネント | 役割 |
|---|---|
| Promtail | ログファイルをtailしてLokiに送信するエージェント |
| Loki | ログストレージ。ラベルインデックス+チャンク保存 |
| Grafana | ログの可視化、ダッシュボード、アラート設定 |
環境準備
プロジェクトディレクトリ構成
mkdir -p loki-stack/{config,data}
cd loki-stack
# ディレクトリ構成
# loki-stack/
# ├── docker-compose.yml
# ├── config/
# │ ├── loki-config.yml
# │ └── promtail-config.yml
# └── data/
Docker Compose設定
docker-compose.yml
version: '3.8'
services:
loki:
image: grafana/loki:3.3.2
container_name: loki
ports:
- '3100:3100'
volumes:
- ./config/loki-config.yml:/etc/loki/local-config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
restart: unless-stopped
networks:
- loki-net
promtail:
image: grafana/promtail:3.3.2
container_name: promtail
volumes:
- ./config/promtail-config.yml:/etc/promtail/config.yml
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki
restart: unless-stopped
networks:
- loki-net
grafana:
image: grafana/grafana:11.4.0
container_name: grafana
ports:
- '3000:3000'
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_AUTH_ANONYMOUS_ENABLED=true
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- loki
restart: unless-stopped
networks:
- loki-net
volumes:
loki-data:
grafana-data:
networks:
loki-net:
driver: bridge
Loki設定
config/loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h # 30日間保持
max_query_length: 721h
max_query_parallelism: 4
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
主要な設定ポイント:
schema: v13— 最新のTSDBスキーマでクエリ性能を向上retention_period: 720h— 30日後に自動削除auth_enabled: false— シングルテナントモード(開発・小規模運用向け)
Promtail設定
config/promtail-config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
batchwait: 1s
batchsize: 1048576 # 1MB
scrape_configs:
# システムログ収集
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: syslog
host: myserver
__path__: /var/log/syslog
# Dockerコンテナログ収集
- job_name: docker
static_configs:
- targets:
- localhost
labels:
job: docker
__path__: /var/lib/docker/containers/**/*.log
pipeline_stages:
- docker: {}
- json:
expressions:
stream: stream
time: time
log: log
- labels:
stream:
- output:
source: log
# Nginxアクセスログ
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
type: access
__path__: /var/log/nginx/access.log
pipeline_stages:
- regex:
expression: '^(?P<remote_addr>[\w.]+) - (?P<remote_user>\S+) \[(?P<time_local>[^\]]+)\] "(?P<method>\w+) (?P<request_uri>\S+) \S+" (?P<status>\d+) (?P<body_bytes_sent>\d+)'
- labels:
method:
status:
- metrics:
http_requests_total:
type: Counter
description: 'Total HTTP requests'
match_all: true
action: inc
Pipeline Stagesの詳細フロー
Promtailのパイプライン処理フローを可視化すると:
graph TD
A[Raw Log Line] --> B[docker stage]
B --> C[json stage - フィールド抽出]
C --> D[labels stage - ラベル付与]
D --> E[output stage - 最終ログ]
E --> F[Loki Push]
G[Nginx Log Line] --> H[regex stage - パターンマッチング]
H --> I[labels stage - method, status]
I --> J[metrics stage - カウンター増加]
J --> F
実行と確認
# スタック起動
docker compose up -d
# ステータス確認
docker compose ps
# Lokiステータス確認
curl -s http://localhost:3100/ready
# ready
# Promtailターゲット確認
curl -s http://localhost:9080/targets | jq '.[] | .labels'
# Lokiに保存されたラベル確認
curl -s http://localhost:3100/loki/api/v1/labels | jq
GrafanaでLokiを接続
1. データソース追加
Grafana(http://localhost:3000)にアクセス後:
- Connections → Data Sources → Add data source
- Lokiを選択
- URL:
http://loki:3100 - Save & Testをクリック
2. LogQL基本クエリ
Exploreメニューで様々なLogQLクエリを実行してみましょう:
# 全syslog参照
{job="syslog"}
# ERRORキーワードフィルタリング
{job="syslog"} |= "error"
# Nginx 5xxエラーのみ参照
{job="nginx", type="access"} | json | status >= 500
# 正規表現フィルター
{job="docker"} |~ "(?i)exception|panic|fatal"
# 直近1時間のエラーカウント(1分間隔)
count_over_time({job="syslog"} |= "error" [1m])
# 上位10件のエラーパターン
{job="syslog"} |= "error"
| pattern `<_> error: <message>`
| topk(10, count_over_time({job="syslog"} |= "error" [1h]))
LogQL演算子まとめ
| 演算子 | 説明 | 例 |
|---|---|---|
|= | 文字列を含む | {job="app"} |= "error" |
!= | 文字列を含まない | {job="app"} != "debug" |
|~ | 正規表現マッチ | {job="app"} |~ "err|warn" |
!~ | 正規表現非マッチ | {job="app"} !~ "health" |
| json | JSONパース | {job="app"} | json |
| logfmt | logfmtパース | {job="app"} | logfmt |
ダッシュボード構成
JSONモデルでダッシュボードプロビジョニング
config/dashboards/logs-overview.jsonファイルを作成して自動プロビジョニングできます:
{
"dashboard": {
"title": "Logs Overview",
"panels": [
{
"title": "Error Rate (1m)",
"type": "timeseries",
"targets": [
{
"expr": "sum(count_over_time({job=~\".+\"} |= \"error\" [1m]))",
"legendFormat": "errors/min"
}
],
"gridPos": { "x": 0, "y": 0, "w": 12, "h": 8 }
},
{
"title": "Log Volume by Job",
"type": "barchart",
"targets": [
{
"expr": "sum by (job) (count_over_time({job=~\".+\"} [5m]))",
"legendFormat": "{{job}}"
}
],
"gridPos": { "x": 12, "y": 0, "w": 12, "h": 8 }
},
{
"title": "Recent Errors",
"type": "logs",
"targets": [
{
"expr": "{job=~\".+\"} |= \"error\""
}
],
"gridPos": { "x": 0, "y": 8, "w": 24, "h": 10 }
}
]
}
}
アラート設定
GrafanaのUnified Alertingを活用してエラー急増時に通知を送りましょう:
Contact Point設定(Slack)
# Grafana provisioning: config/alerting/contact-points.yml
apiVersion: 1
contactPoints:
- orgId: 1
name: slack-alerts
receivers:
- uid: slack-1
type: slack
settings:
url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
title: '🚨 {{ .CommonLabels.alertname }}'
text: |
**Status:** {{ .Status }}
**Summary:** {{ .CommonAnnotations.summary }}
アラートルール作成
Grafana UIで:
- Alerting → Alert Rules → New Alert Rule
- クエリ:
count_over_time({job="syslog"} |= "error" [5m]) > 50 - 評価間隔: 1分
- 待機時間: 5分(一時的なスパイクを無視)
- Contact Point:
slack-alerts
プロダクション運用Tips
1. マルチテナント設定
# loki-config.yml
auth_enabled: true
# PromtailからテナントIDを送信
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: team-backend
2. S3互換オブジェクトストレージの使用
# loki-config.yml(プロダクション)
common:
storage:
s3:
endpoint: minio:9000
bucketnames: loki-chunks
access_key_id: ${MINIO_ACCESS_KEY}
secret_access_key: ${MINIO_SECRET_KEY}
insecure: true
s3forcepathstyle: true
3. Kubernetes環境でのHelm デプロイ
# Loki Stack Helmチャート
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install loki grafana/loki-stack \
--namespace observability \
--create-namespace \
--set grafana.enabled=true \
--set promtail.enabled=true \
--set loki.persistence.enabled=true \
--set loki.persistence.size=50Gi
4. ログボリューム制御
# promtail-config.yml - 不要なログをドロップ
pipeline_stages:
- match:
selector: '{job="nginx"}'
stages:
- regex:
expression: '"(?P<method>\w+) (?P<uri>\S+)'
- drop:
expression: '^/health$'
source: uri
- drop:
expression: '^/metrics$'
source: uri
全体データフローまとめ
sequenceDiagram
participant App as Application
participant PT as Promtail
participant LK as Loki
participant S3 as Storage
participant GF as Grafana
participant User as Operator
App->>App: Write logs to /var/log/app.log
PT->>App: Tail log file
PT->>PT: Pipeline processing (parse, label, filter)
PT->>LK: HTTP POST /loki/api/v1/push
LK->>LK: Index labels + compress chunks
LK->>S3: Store chunks & index
User->>GF: Open Dashboard
GF->>LK: LogQL query
LK->>S3: Read chunks
LK->>GF: Return results
GF->>User: Render logs & charts
GF->>GF: Evaluate alert rules
GF-->>User: 🚨 Slack notification (if threshold exceeded)
まとめ
Grafana + Loki + PromtailスタックはELKと比べてはるかに少ないリソースで効果的なログパイプラインを構築できます。主な利点をまとめると:
- 低リソース使用量: ログ本文をインデックスしないためストレージとメモリを節約
- Grafanaネイティブ統合: メトリクス(Prometheus)とログ(Loki)を1つのダッシュボードで参照
- LogQL: PromQLに似た構文で学習コストが緩やか
- 水平スケーリング: マイクロサービスアーキテクチャで読み取り/書き込みパスを独立してスケーリング
本番環境ではS3互換ストレージ、マルチテナント、適切なretentionポリシーを必ず検討しましょう。
クイズ
Q1: LokiがELKと比べてストレージコストが低い核心的な理由は?
Lokiはログ本文をインデックスせず、ラベル(label)のみをインデックスするためです。Elasticsearchは全文(full-text)インデックスを行い、はるかに多くのストレージとメモリを消費します。
Q2: Promtailのpositionsファイルの役割は?
各ログファイルで最後に読み取った位置(オフセット)を記録します。Promtail再起動時に重複送信や欠落なく、前回の位置から読み取りを再開できます。
Q3: LogQLの
|=と|~の違いは?|=は正確な文字列の包含を検査し、|~は正規表現(regex)パターンマッチングを実行します。例:|= "error"は"error"文字列の包含、|~ "err|warn"は"err"または"warn"にマッチします。
Q4: Lokiのschema v13で使用されるインデックスストアは?
TSDB(Time Series Database)ストアを使用します。以前のバージョンのBoltDBよりクエリ性能と圧縮効率が大幅に向上しています。
Q5: Pipeline stagesの
特定の条件にマッチするログ行をLokiに送信せず破棄する役割です。ヘルスチェックやメトリクスエンドポイントなど不要なログをフィルタリングしてストレージコストを削減します。dropステージの用途は?
Q6: マルチテナントモードでPromtailがテナントを区別する方法は?
Promtailのclients設定でtenant_idを指定すると、LokiにHTTPヘッダーX-Scope-OrgIDとして送信され、テナントごとにログが分離されます。
Q7:
指定された時間範囲内のログ行の数をカウントするLogQLメトリクスクエリです。例:count_over_time関数の役割は?count_over_time( {(job = 'app')} |= "error" [5m])は直近5分間のエラーログ数を返します。
Q8: Lokiのretention_periodとcompactorの関係は?
retention_periodはログの保持期間を定義し、compactorが実際に期限切れのチャンクを見つけて削除する役割を担います。compactorのretention_enabled: trueが必ず設定されている必要があります。