この記事の要点
• Grafanaで複数のデータソースを統合し、リアルタイム監視ダッシュボードを構築
• Prometheus・Loki・Tempoを統合し、メトリクス・ログ・トレースを相関分析
• AlertingとProvisioningでダッシュボードをコード管理
Grafanaとは
Grafanaは、時系列データを可視化するためのオープンソース観測性プラットフォームです。Prometheus、Loki、Tempo、Elasticsearch、CloudWatchなど、多様なデータソースに対応しています。
主要機能
| 機能 | 説明 |
|---|---|
| ダッシュボード | パネルを組み合わせた可視化画面 |
| データソース | メトリクス・ログ・トレースの統合 |
| アラート | 閾値ベースの通知 |
| Provisioning | YAMLによるコード管理 |
| RBAC | チーム・組織単位のアクセス制御 |
ポイント: Grafanaは可視化レイヤーに特化し、データ収集は各種バックエンドに任せます。
Grafanaのインストール
Docker Compose
# docker-compose.yml
services:
grafana:
image: grafana/grafana:11.0.0
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-piechart-panel
volumes:
- grafana-storage:/var/lib/grafana
- ./provisioning:/etc/grafana/provisioning
networks:
- monitoring
prometheus:
image: prom/prometheus:v2.50.0
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-storage:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- monitoring
loki:
image: grafana/loki:2.9.0
container_name: loki
ports:
- "3100:3100"
volumes:
- ./loki-config.yml:/etc/loki/local-config.yaml
- loki-storage:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- monitoring
volumes:
grafana-storage:
prometheus-storage:
loki-storage:
networks:
monitoring:
driver: bridge
# 起動
docker compose up -d
# http://localhost:3000 にアクセス
# ユーザー名: admin
# パスワード: admin
Kubernetes (Helm)
# Helmリポジトリ追加
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# インストール
helm install grafana grafana/grafana \
--namespace monitoring \
--create-namespace \
--set persistence.enabled=true \
--set persistence.size=10Gi \
--set adminPassword=changeme
# パスワード取得
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode
# ポートフォワード
kubectl port-forward -n monitoring svc/grafana 3000:80
実践メモ: 本番環境ではpersistence.enabled=trueでダッシュボードやデータソースを永続化します。
データソースの設定
Prometheus
# provisioning/datasources/prometheus.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
jsonData:
httpMethod: POST
exemplarTraceIdDestinations:
- name: trace_id
datasourceUid: tempo
Loki (ログ)
# provisioning/datasources/loki.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: true
jsonData:
derivedFields:
- datasourceUid: tempo
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"
Tempo (トレース)
# provisioning/datasources/tempo.yaml
apiVersion: 1
datasources:
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
editable: true
jsonData:
tracesToLogsV2:
datasourceUid: loki
spanStartTimeShift: '-1h'
spanEndTimeShift: '1h'
filterByTraceID: true
filterBySpanID: false
tracesToMetrics:
datasourceUid: prometheus
serviceMap:
datasourceUid: prometheus
nodeGraph:
enabled: true
注意: derivedFieldsを設定すると、ログからトレースIDを抽出してTempoにジャンプできます。正規表現がログ形式と一致するか確認してください。
ダッシュボード作成
基本構造
{
"dashboard": {
"title": "Application Metrics",
"tags": ["app", "production"],
"timezone": "browser",
"schemaVersion": 38,
"version": 1,
"refresh": "10s",
"time": {
"from": "now-1h",
"to": "now"
},
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "Request Rate",
"gridPos": {
"x": 0,
"y": 0,
"w": 12,
"h": 8
},
"targets": [
{
"expr": "rate(http_requests_total[5m])",
"legendFormat": "{{method}} {{path}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"color": {
"mode": "palette-classic"
}
}
}
}
]
}
}
PromQLクエリ例
# リクエストレート(RPS)
rate(http_requests_total[5m])
# P95レイテンシ
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# エラー率
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# CPU使用率
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# メモリ使用率
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# ディスク使用率
(node_filesystem_size_bytes - node_filesystem_avail_bytes)
/
node_filesystem_size_bytes * 100
# Podレプリカ数
kube_deployment_status_replicas{deployment="my-app"}
# コンテナ再起動回数
increase(kube_pod_container_status_restarts_total[1h])
Variables(変数)
{
"templating": {
"list": [
{
"name": "namespace",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(kube_pod_info, namespace)",
"refresh": 1,
"includeAll": true
},
{
"name": "pod",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(kube_pod_info{namespace=\"$namespace\"}, pod)",
"refresh": 2,
"includeAll": true
},
{
"name": "interval",
"type": "interval",
"query": "1m,5m,10m,30m,1h",
"auto": true,
"auto_count": 30,
"auto_min": "10s"
}
]
}
}
ポイント: Variablesを使うと、ダッシュボード上部のドロップダウンで名前空間やPodを切り替えられます。
パネルタイプ
| タイプ | 用途 |
|---|---|
| Time series | 時系列グラフ |
| Stat | 単一値表示 |
| Gauge | ゲージ表示 |
| Bar chart | 棒グラフ |
| Table | テーブル |
| Heatmap | ヒートマップ |
| Logs | ログストリーム(Loki) |
| Node Graph | サービスマップ(Tempo) |
ダッシュボード例
RED Method(Rate, Errors, Duration)
# provisioning/dashboards/red-dashboard.json
{
"dashboard": {
"title": "RED Metrics - My App",
"panels": [
{
"id": 1,
"title": "Request Rate",
"type": "timeseries",
"targets": [{
"expr": "sum(rate(http_requests_total{app=\"my-app\"}[5m])) by (method)"
}]
},
{
"id": 2,
"title": "Error Rate",
"type": "timeseries",
"targets": [{
"expr": "sum(rate(http_requests_total{app=\"my-app\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{app=\"my-app\"}[5m]))"
}],
"fieldConfig": {
"defaults": {
"unit": "percentunit"
}
}
},
{
"id": 3,
"title": "Request Duration (P95)",
"type": "timeseries",
"targets": [{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app=\"my-app\"}[5m])) by (le))"
}],
"fieldConfig": {
"defaults": {
"unit": "s"
}
}
}
]
}
}
USE Method(Utilization, Saturation, Errors)
# CPU Utilization
avg(rate(container_cpu_usage_seconds_total{pod=~"my-app.*"}[5m])) by (pod)
# Memory Utilization
avg(container_memory_working_set_bytes{pod=~"my-app.*"}) by (pod)
# Network Saturation
rate(container_network_transmit_bytes_total{pod=~"my-app.*"}[5m])
# Disk I/O Errors
rate(node_disk_io_now{device="sda"}[5m])
実践メモ: RED Methodはリクエスト駆動型サービス、USE Methodはリソース(CPU・メモリ・ディスク)の監視に適しています。
LogQLクエリ(Loki)
# 特定アプリのログ
{app="my-app"}
# エラーログのみ
{app="my-app"} |= "error"
# JSON解析
{app="my-app"} | json | level="error"
# レート計算
rate({app="my-app"} |= "error" [5m])
# トレースIDでフィルタ
{app="my-app"} | json | trace_id="abc123"
# ステータスコード500のカウント
sum(count_over_time({app="my-app"} | json | status="500" [1h]))
アラート設定
Alerting Rule
# provisioning/alerting/alert-rules.yaml
apiVersion: 1
groups:
- name: my-app-alerts
folder: Application
interval: 1m
rules:
- uid: high-error-rate
title: High Error Rate
condition: C
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
- refId: C
datasourceUid: __expr__
model:
type: threshold
expression: A
conditions:
- evaluator:
params: [0.05]
type: gt
operator:
type: and
noDataState: NoData
execErrState: Alerting
for: 5m
annotations:
summary: Error rate is above 5%
labels:
severity: critical
team: platform
Contact Points
# provisioning/alerting/contact-points.yaml
apiVersion: 1
contactPoints:
- orgId: 1
name: slack-platform
receivers:
- uid: slack-1
type: slack
settings:
url: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
text: |
{{ range .Alerts }}
*{{ .Labels.alertname }}*
{{ .Annotations.summary }}
{{ end }}
Notification Policies
# provisioning/alerting/notification-policies.yaml
apiVersion: 1
policies:
- orgId: 1
receiver: slack-platform
group_by: ['alertname', 'severity']
group_wait: 10s
group_interval: 5m
repeat_interval: 12h
routes:
- receiver: slack-platform
matchers:
- severity = critical
continue: true
- receiver: pagerduty
matchers:
- severity = critical
- team = platform
注意: アラートのforパラメータは、条件が継続する期間を指定します。瞬間的なスパイクで通知しないよう適切に設定してください。
Provisioningによるコード管理
ディレクトリ構造
provisioning/
├── datasources/
│ ├── prometheus.yaml
│ ├── loki.yaml
│ └── tempo.yaml
├── dashboards/
│ ├── dashboards.yaml # ダッシュボードプロバイダ設定
│ └── app-metrics.json # 実際のダッシュボード定義
├── alerting/
│ ├── alert-rules.yaml
│ ├── contact-points.yaml
│ └── notification-policies.yaml
└── notifiers/
└── slack.yaml
ダッシュボードプロバイダ
# provisioning/dashboards/dashboards.yaml
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: 'General'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
foldersFromFilesStructure: true
エクスポートとインポート
# ダッシュボードのエクスポート(API経由)
curl -H "Authorization: Bearer ${GRAFANA_API_KEY}" \
http://localhost:3000/api/dashboards/uid/abc123 \
| jq '.dashboard' > dashboard.json
# ダッシュボードのインポート
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GRAFANA_API_KEY}" \
-d @dashboard.json \
http://localhost:3000/api/dashboards/db
ポイント: Provisioningを使うと、ダッシュボードをGitで管理し、Infrastructure as Codeとして扱えます。
Grafanaの高度な機能
Trace to Metrics(トレース→メトリクス)
# Tempoデータソース設定
jsonData:
tracesToMetrics:
datasourceUid: prometheus
queries:
- name: 'Request rate'
query: 'sum(rate(tempo_spanmetrics_calls_total{$$__tags}[5m]))'
- name: 'Error rate'
query: 'sum(rate(tempo_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR",$$__tags}[5m])) / sum(rate(tempo_spanmetrics_calls_total{$$__tags}[5m]))'
Logs to Traces(ログ→トレース)
# Lokiデータソース設定
jsonData:
derivedFields:
- datasourceUid: tempo
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"
Exemplars(サンプル)
# Prometheusクエリ with exemplars
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
パフォーマンス最適化
# grafana.ini
[dashboards]
# バージョン管理の保持数
versions_to_keep = 20
[dataproxy]
# データソースタイムアウト
timeout = 30
[query]
# クエリキャッシュ
cache_ttl = 5m
[explore]
# Exploreモードのログ行数制限
max_lines = 1000
実践メモ: 大量のパネルを含むダッシュボードは、query cacheとrepeat intervalを調整してバックエンドの負荷を軽減します。