Grafana
1. Architecture & Data Sources
Grafana is a visualization layer that queries data sources and renders panels. It does not store metrics — it proxies queries to backends.
flowchart LR
subgraph Sources
PR["Prometheus<br/>metrics"]
LK["Loki<br/>logs"]
TP["Tempo<br/>traces"]
PG["PostgreSQL<br/>relational"]
end
subgraph Grafana
DS["Data Source<br/>plugins"] --> QE[Query Engine]
QE --> PN[Panel Renderer]
end
Sources --> DS
PN --> DB[Dashboard]
| Data Source | Query Language | Best For |
|---|---|---|
| Prometheus | PromQL | Metrics, counters, gauges |
| Loki | LogQL | Log streams, structured logs |
| Tempo | TraceQL | Distributed traces, spans |
| PostgreSQL | SQL | Business data, audit logs |
2. Panel Types
| Panel | Use When |
|---|---|
| Time series | Metrics over time (latency, RPS, CPU) |
| Stat | Single current value with threshold color |
| Gauge | Value within a min/max range (SLO burn) |
| Table | Multi-dimensional comparison, top-N |
| Heatmap | Latency distribution over time |
| Logs | Log stream output from Loki |
Picking the right panel:
- Trending over time → Time series
- "Is it OK right now?" → Stat or Gauge
- "Which pods are slowest?" → Table
- "Where are latency outliers?" → Heatmap
- "What did the app log?" → Logs
3. Variables
Variables make dashboards reusable across environments, clusters, and services.
Dashboard URL: /d/abc?var-cluster=prod&var-namespace=payments
Query variable — populated from a data source:
# Variable: cluster
# Query (Prometheus label_values):
label_values(kube_node_info, cluster)
Custom variable — static list:
name: env
values: dev,staging,prod
Interval variable — for $__interval in rate() calls:
name: interval
values: 1m,5m,10m,30m
auto: true
Use in panels:
rate(http_requests_total{cluster="$cluster", namespace="$namespace"}[$interval])
4. USE Method Dashboard
Per resource (CPU, memory, disk, network):
| Row | Metric | PromQL sketch |
|---|---|---|
| Utilization | % busy | rate(node_cpu_seconds_total{mode!="idle"}[5m]) |
| Saturation | Run-queue / pressure | node_pressure_cpu_waiting_seconds_total |
| Errors | Hardware / kernel errors | node_disk_io_time_seconds_total |
Layout (4 rows × 3 panels):
[CPU Util] [CPU Saturation] [CPU Errors]
[Mem Util] [Mem Saturation] [OOM Kills ]
[Disk Util] [Disk Saturation] [Disk Errors]
[Net Util] [Net Saturation] [Net Errors ]
Each panel uses $node variable to filter by host.
5. RED Method Dashboard
Per service/endpoint:
| Panel | Metric | PromQL sketch |
|---|---|---|
| Rate | Requests/sec | sum(rate(http_requests_total[$interval])) by (service) |
| Errors | Error rate % | sum(rate(http_requests_total{status=~"5.."}[$interval])) / sum(rate(http_requests_total[$interval])) |
| Duration | P50/P95/P99 latency | histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[$interval])) by (le, service)) |
Layout:
[RPS - all services (time series)]
[Error Rate % (time series)] [Top errors by service (table)]
[P50 latency] [P95 latency] [P99 latency]
6. SLO Dashboard
SLO: 99.9% availability over 30-day rolling window
Error budget: 0.1% = ~43 minutes/month
| Panel | Formula |
|---|---|
| Availability % | 1 - (errors / total) over 30d |
| Error budget remaining | budget_total - errors_consumed |
| Burn rate (1h) | error_rate_1h / (1 - SLO_target) |
| Burn rate (6h) | same, 6h window |
| Budget exhaustion forecast | linear projection |
Burn rate thresholds (Google SRE):
| Window | Burn rate | Action |
|---|---|---|
| 1h | > 14x | Page immediately |
| 6h | > 6x | Page |
| 3d | > 1x | Ticket |
# 1-hour burn rate
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) / (1 - 0.999)
7. Provisioning Dashboards as Code
flowchart LR
GH["Git repo<br/>dashboard JSON"] -->|CI push| CM[K8s ConfigMap]
CM -->|volume mount| SC["Grafana sidecar<br/>container"]
SC -->|watches /dashboards| GF["Grafana<br/>auto-reloads"]
ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboards
labels:
grafana_dashboard: "1" # sidecar watches this label
data:
red-dashboard.json: |
{ "title": "RED Dashboard", ... }
Grafana Helm values:
grafana:
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
searchNamespace: ALL
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:9090
isDefault: true
- name: Loki
type: loki
url: http://loki:3100
Grafana sidecar watches all ConfigMaps with grafana_dashboard: "1" and hot-reloads dashboards without restart.
8. Alerting
Grafana-native alerting (Grafana 9+) replaces the old panel-level alerts.
Components:
| Component | Role |
|---|---|
| Alert rule | PromQL/LogQL condition with for: duration |
| Contact point | Destination (Slack, PagerDuty, email, webhook) |
| Notification policy | Routes alerts to contact points by labels |
| Silence | Mutes matching alerts for a time range |
Contact point (Slack):
# provisioning/alerting/contact-points.yaml
apiVersion: 1
contactPoints:
- orgId: 1
name: slack-oncall
receivers:
- uid: slack-oncall-uid
type: slack
settings:
url: 'https://hooks.slack.com/services/XXX/YYY/ZZZ'
recipient: '#alerts'
title: '{{ .CommonLabels.alertname }}'
Notification policy:
# provisioning/alerting/notification-policies.yaml
apiVersion: 1
policies:
- orgId: 1
receiver: slack-oncall
group_by: [alertname, cluster]
routes:
- receiver: pagerduty-critical
matchers:
- severity = critical
Alert rule (provisioning):
apiVersion: 1
groups:
- orgId: 1
name: RED Alerts
folder: SRE
interval: 1m
rules:
- title: High Error Rate
condition: C
for: 5m
labels:
severity: warning
annotations:
summary: "Error rate above 1% for {{ $labels.service }}"
data:
- refId: A
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
- refId: C
datasourceUid: __expr__
model:
type: threshold
conditions:
- evaluator: { params: [0.01], type: gt }
query: { params: [A] }