Setup e Instalação
Docker Compose com Prometheus + Grafana
# docker-compose.yml — stack completa de observabilidade
version: '3.9'
volumes:
grafana-data: {}
prometheus-data: {}
loki-data: {}
networks:
observability:
driver: bridge
services:
grafana:
image: grafana/grafana:10.2.3
container_name: grafana
restart: unless-stopped
networks:
- observability
ports:
- "3000:3000"
environment:
# Usuário/senha do admin
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
# Desabilitar cadastro de novos usuários
GF_USERS_ALLOW_SIGN_UP: "false"
# Configuração de email para alertas
GF_SMTP_ENABLED: "true"
GF_SMTP_HOST: smtp.gmail.com:587
GF_SMTP_USER: alertas@empresa.com
GF_SMTP_PASSWORD: senha-aqui
GF_SMTP_FROM_ADDRESS: alertas@empresa.com
# Instalar plugins extras na inicialização
GF_INSTALL_PLUGINS: grafana-piechart-panel,grafana-worldmap-panel
# Desabilitar telemetria
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
volumes:
- grafana-data:/var/lib/grafana
# Provisioning automático de datasources e dashboards
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
interval: 30s
timeout: 5s
retries: 5
prometheus:
image: prom/prometheus:v2.48.0
container_name: prometheus
restart: unless-stopped
networks:
- observability
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
loki:
image: grafana/loki:2.9.3
container_name: loki
restart: unless-stopped
networks:
- observability
ports:
- "3100:3100"
volumes:
- ./loki/loki.yml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:2.9.3
container_name: promtail
restart: unless-stopped
networks:
- observability
volumes:
- ./promtail/promtail.yml:/etc/promtail/config.yml:ro
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/config.yml
node-exporter:
image: prom/node-exporter:v1.7.0
container_name: node-exporter
restart: unless-stopped
networks:
- observability
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'Data Sources
Configuração via UI ou Provisioning
Prometheus:
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
jsonData:
httpMethod: POST
timeInterval: 15s # deve bater com o scrape_interval
queryTimeout: 60s
exemplarTraceIdDestinations:
- name: TraceID
datasourceUid: tempo # link para traces no Tempo
editable: falseLoki:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
jsonData:
maxLines: 1000
derivedFields:
# Extrai TraceID dos logs e cria link para Jaeger/Tempo
- matcherRegex: '"trace_id":"(\w+)"'
name: TraceID
url: '$${__value.raw}'
datasourceUid: tempo
editable: falsePostgreSQL:
- name: PostgreSQL-Producao
type: postgres
url: postgres-server:5432
user: grafana_ro # usuário read-only dedicado
secureJsonData:
password: senha-secreta
jsonData:
database: producao
sslmode: require
maxOpenConns: 10
maxIdleConns: 5
connMaxLifetime: 14400
postgresVersion: 1500 # PostgreSQL 15
timescaledb: false
editable: falseElasticsearch:
- name: Elasticsearch
type: elasticsearch
url: http://elasticsearch:9200
jsonData:
index: 'logs-*'
timeField: '@timestamp'
esVersion: '8.0.0'
logMessageField: message
logLevelField: log.level
maxConcurrentShardRequests: 5
editable: falsePanels — Tipos e Quando Usar
Time Series
Para dados contínuos ao longo do tempo. O tipo mais comum.
{
"type": "timeseries",
"title": "Taxa de Requisições por Segundo",
"fieldConfig": {
"defaults": {
"unit": "reqps",
"min": 0,
"color": { "mode": "palette-classic" },
"custom": {
"lineWidth": 2,
"fillOpacity": 10,
"pointSize": 0
}
}
},
"options": {
"tooltip": { "mode": "multi", "sort": "desc" },
"legend": { "displayMode": "table", "placement": "bottom" }
}
}Quando usar:
- Métricas de rate/irate
- CPU, memória, latência ao longo do tempo
- Qualquer série temporal contínua
Stat
Exibe um único valor grande com contexto.
Quando usar:
- Uptime percentual
- Total de erros no período
- Valor atual de uma métrica crítica
- KPIs do dashboard
{
"type": "stat",
"title": "Error Rate",
"options": {
"colorMode": "background",
"graphMode": "area",
"textMode": "value_and_name",
"reduceOptions": {
"calcs": ["lastNotNull"]
}
},
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 0.01 },
{ "color": "red", "value": 0.05 }
]
}
}
}
}Table
Para dados tabulares e comparações entre instâncias.
Quando usar:
- Comparar múltiplos hosts (CPU, memória, disco)
- Lista de alertas ativos
- Top N endpoints por latência
- Dados com múltiplas colunas
Transformações úteis para Table:
Merge— combinar resultados de múltiplas queriesFilter by value— filtrar linhasSort by— ordenarRename by regex— renomear camposCalculate field— campos calculados
Bar Chart
Para comparar categorias (não tempo).
Quando usar:
- Distribuição de erros por endpoint
- Comparação de recursos entre serviços
- Ranking de usuários mais ativos
Heatmap
Para visualizar distribuições ao longo do tempo.
Quando usar:
- Distribuição de latências (histograms)
- Horários de pico de tráfego
- Densidade de eventos
# Query para heatmap de latência
sum(increase(http_request_duration_seconds_bucket[$__interval])) by (le)Logs Panel
Para visualizar logs do Loki.
Quando usar:
- Log Explorer
- Correlacionar logs com métricas no mesmo dashboard
Traces Panel
Para visualizar traces do Tempo/Jaeger.
Quando usar:
- Análise de latência de requests distribuídos
- Debugging de microserviços
Queries Prometheus no Grafana
Variáveis de Tempo
O Grafana injeta automaticamente variáveis especiais nas queries:
# $__interval — intervalo calculado baseado no range do dashboard
# Evita over-sampling, ideal para dashboards
rate(http_requests_total[$__interval])
# $__rate_interval — similar, mas garante mínimo de 4x o scrape interval
# Mais seguro que $__interval para rate()
rate(http_requests_total[$__rate_interval])
# $__range — range total selecionado (ex: "1h")
increase(http_requests_total[$__range])
# $__from e $__to — timestamps Unix em milissegundos
# Usado em queries SQL: WHERE timestamp BETWEEN $__from AND $__toUsando Template Variables
# Dashboard com variável $job
rate(http_requests_total{job="$job"}[$__rate_interval])
# Múltiplos valores selecionados (regex)
rate(http_requests_total{job=~"$job"}[$__rate_interval])
# Variável $instance com valores múltiplos
process_memory_bytes{instance=~"$instance"}Editor de Query (Transform e Override)
// Transformação: Rename Fields usando Regex
{
"id": "renameByRegex",
"options": {
"regex": "http_requests_total\\{.*job=\"([^\"]+)\".*\\}",
"renamePattern": "$1"
}
}
// Transformação: Calculate Field (adicionar coluna calculada)
{
"id": "calculateField",
"options": {
"mode": "reduceRow",
"reduce": { "reducer": "sum" },
"alias": "total"
}
}
// Override: Customizar uma série específica
{
"matcher": { "id": "byName", "options": "error_rate" },
"properties": [
{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } },
{ "id": "custom.lineWidth", "value": 3 }
]
}Template Variables
Tipos de Variables
Query Variable — busca valores do datasource:
# Buscar todos os jobs disponíveis no Prometheus
type: query
datasource: Prometheus
query: label_values(up, job)
refresh: On Dashboard Load
multi: true
includeAll: true
allValue: ".*" # valor para "All" em regex
# Buscar instâncias de um job específico
query: label_values(up{job="$job"}, instance)
# Buscar métricas que começam com "http"
query: metrics(http.*)Custom Variable — lista fixa:
type: custom
query: "prod,staging,dev"
# Com labels customizados:
query: "Produção : prod, Homologação : staging, Dev : dev"Interval Variable — para seleção de janela de tempo:
type: interval
values: "1m,5m,10m,30m,1h,6h,12h,1d"
auto: true
auto_count: 30 # divide o range em 30 intervalos
auto_min: 10sTextbox Variable — input livre:
type: textbox
default: "api-backend"Uso Avançado de Variables
# Variável com dependência (instâncias do job selecionado)
label_values(up{job="$job"}, instance)
# Usar variável em títulos de panels
"CPU Usage — $instance"
# Usar em links
"https://logs.empresa.com/search?job=$job"
# Repetir rows/panels por variável
# No JSON: "repeat": "instance", "repeatDirection": "h"Alerting no Grafana
Regras de Alerta (Grafana Alerting)
# API: POST /api/v1/provisioning/alert-rules
{
"title": "High Error Rate",
"ruleGroup": "api-alerts",
"folderUID": "prod-alerts",
"noDataState": "NoData", # comportamento quando sem dados: NoData, Alerting, OK
"execErrState": "Error",
"for": "5m", # precisa ficar ativo por 5 minutos
"labels": {
"severity": "critical",
"team": "backend"
},
"annotations": {
"summary": "Error rate acima de 5%",
"description": "Error rate atual: {{ $values.A.Value | printf \"%.2f\" }}%"
},
"data": [
{
"refId": "A",
"queryType": "",
"relativeTimeRange": { "from": 300, "to": 0 },
"datasourceUid": "prometheus",
"model": {
"expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100",
"intervalMs": 1000,
"maxDataPoints": 43200
}
},
{
"refId": "B",
"queryType": "expression",
"model": {
"type": "threshold",
"refId": "B",
"conditions": [
{
"evaluator": { "params": [5], "type": "gt" }
}
]
}
}
]
}Contact Points (Destinos de Notificação)
# grafana/provisioning/alerting/contact-points.yml
apiVersion: 1
contactPoints:
- orgId: 1
name: Slack-Critico
receivers:
- uid: slack-critico
type: slack
settings:
url: https://hooks.slack.com/services/T00/B00/xxx
channel: "#alertas-criticos"
username: Grafana Alerts
icon_emoji: ":rotating_light:"
title: |
{{ if eq .Status "firing" }}ALERTA{{ else }}RESOLVIDO{{ end }}: {{ .CommonLabels.alertname }}
text: |
{{ range .Alerts }}
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Severity:* {{ .Labels.severity }}
{{ end }}
- orgId: 1
name: Email-Oncall
receivers:
- uid: email-oncall
type: email
settings:
addresses: oncall@empresa.com;backup@empresa.com
subject: "[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}"
message: |
{{ range .Alerts }}
Alert: {{ .Labels.alertname }}
Summary: {{ .Annotations.summary }}
{{ end }}
- orgId: 1
name: PagerDuty
receivers:
- uid: pagerduty
type: pagerduty
settings:
integrationKey: <integration-key>
severity: '{{ .CommonLabels.severity }}'
class: '{{ .CommonLabels.alertname }}'
component: grafanaNotification Policies
# grafana/provisioning/alerting/notification-policies.yml
apiVersion: 1
policies:
- orgId: 1
receiver: default-receiver
group_by: [alertname, job]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
# Alertas críticos → PagerDuty
- receiver: PagerDuty
matchers:
- severity = critical
repeat_interval: 1h
# Alertas de banco de dados → canal específico
- receiver: Slack-DBA
matchers:
- team = dba
continue: false # não propagar para rotas superiores
# Alertas de staging → silenciados fora do horário
- receiver: Slack-Dev
matchers:
- environment = staging
mute_time_intervals:
- outside-business-hoursSilences (via API)
# Criar silence por 4 horas (manutenção planejada)
curl -X POST http://grafana:3000/api/alertmanager/grafana/api/v2/silences \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"matchers": [
{"name": "instance", "value": "server-1", "isRegex": false},
{"name": "environment", "value": "production", "isRegex": false}
],
"startsAt": "2024-01-15T14:00:00Z",
"endsAt": "2024-01-15T18:00:00Z",
"createdBy": "ops-team",
"comment": "Manutenção planejada servidor-1"
}'Grafana Loki
Configuração do Loki
# loki/loki.yml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://alertmanager:9093
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h # 7 dias
max_entries_limit_per_query: 5000
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32Promtail — Coleta de Logs
# promtail/promtail.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Logs de arquivos do sistema
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: system-logs
__path__: /var/log/*.log
# Logs de containers Docker
- job_name: docker-containers
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: status
values: ["running"]
relabel_configs:
# Label com nome do container
- source_labels: [__meta_docker_container_name]
regex: '/(.*)'
target_label: container
# Label com imagem do container
- source_labels: [__meta_docker_container_image]
target_label: image
pipeline_stages:
# Parse JSON logs
- json:
expressions:
level: level
message: message
trace_id: trace_id
# Extrair labels dos campos parseados
- labels:
level:
trace_id:
# Timestamp do log (não do promtail)
- timestamp:
source: time
format: RFC3339Nano
# Logs de aplicação Spring Boot (JSON)
- job_name: api-backend
static_configs:
- targets:
- localhost
labels:
job: api-backend
env: production
__path__: /var/log/api/*.log
pipeline_stages:
- json:
expressions:
level: level
logger: logger_name
thread: thread_name
message: message
trace_id: X-B3-TraceId
- labels:
level:
logger:
- output:
source: messageLogQL — Linguagem de Query do Loki
Log Queries (retornam linhas de log)
# Seletor básico — streams de log por labels
{job="api-backend"}
# Múltiplos labels
{job="api-backend", env="production"}
# Operadores de label
{job="api-backend"} # igualdade
{job!="api-backend"} # diferença
{job=~"api.*|gateway"} # regex
{job!~"test.*"} # negação regex
# Filtros de texto
{job="api-backend"} |= "ERROR" # contém
{job="api-backend"} != "DEBUG" # não contém
{job="api-backend"} |~ "ERROR|WARN" # regex match
{job="api-backend"} !~ "healthcheck" # regex negação
# Encadeamento de filtros
{job="api-backend"}
|= "ERROR"
!= "health"
|~ "timeout|connection refused"
# Parse JSON e filtrar por campos
{job="api-backend"}
| json
| level = "ERROR"
| status_code >= 500
# Parse logfmt (key=value format)
{job="nginx"} | logfmt | method="POST" | status=500
# Parse com regex (extrai campos via grupos captura)
{job="nginx"}
| regexp `(?P<method>\w+) (?P<path>[^ ]+) HTTP/[^ ]+" (?P<status>\d+)`
| status >= 500
# Parse com padrão (mais legível que regex para logs estruturados)
{job="nginx"}
| pattern `<ip> - <user> [<_>] "<method> <path> <_>" <status> <size>`
| status >= 500
# Filtro por linha (expressão)
{job="api-backend"} | json | line_format "{{.message}}"Metric Queries (retornam séries temporais)
# Contagem de linhas por segundo
rate({job="api-backend"}[5m])
# Total de linhas na janela
count_over_time({job="api-backend"}[5m])
# Contagem de erros por segundo
rate({job="api-backend"} |= "ERROR" [5m])
# Taxa de erros (%) por serviço
sum(rate({job="api-backend"} |= "ERROR" [5m])) by (container)
/ sum(rate({job="api-backend"} [5m])) by (container)
# Soma de campo numérico extraído
sum(
sum_over_time(
{job="api-backend"}
| json
| unwrap duration_ms [5m]
)
) by (endpoint)
# P99 de latência a partir de logs
quantile_over_time(0.99,
{job="api-backend"}
| json
| unwrap duration_ms
| __error__=""
[5m]
) by (endpoint)
# Bytes transferidos por segundo
rate({job="nginx"}
| logfmt
| unwrap bytes_sent [5m])
# Top 5 IPs com mais requisições
topk(5,
sum by (remote_addr) (
count_over_time(
{job="nginx"} | logfmt [5m]
)
)
)
# Logs por nível de severidade
sum(count_over_time({job="api-backend"} | json [5m])) by (level)Queries Avançadas
# Deduplicar linhas duplicadas (útil para logs que escrevem em múltiplos alvos)
{job="api-backend"} | decolorize | distinct
# Filtros de label após parse
{job="api-backend"}
| json
| duration > 1s # campo duration como Duration
| status_code >= 400
| drop __error__ # remover label de erro de parse
# Format de saída customizado
{job="api-backend"}
| json
| line_format "[{{.level}}] {{.message}} (trace={{.trace_id}})"
# Label filter com expressões
{job="api-backend"}
| json
| label_format request_time=duration # renomear labelTraces com Tempo e Jaeger
Configuração do Tempo
# tempo/tempo.yml
server:
http_listen_port: 3200
distributor:
receivers:
jaeger:
protocols:
thrift_http:
endpoint: 0.0.0.0:14268
grpc:
endpoint: 0.0.0.0:14250
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
zipkin:
endpoint: 0.0.0.0:9411
ingester:
trace_idle_period: 10s
max_block_bytes: 1_000_000
max_block_duration: 5m
compactor:
compaction:
block_retention: 1h
storage:
trace:
backend: local
block:
bloom_filter_false_positive: .05
index_downsample_bytes: 1000
encoding: zstd
local:
path: /tmp/tempo/blocks
wal:
path: /tmp/tempo/walDatasource Tempo no Grafana
# grafana/provisioning/datasources/tempo.yml
datasources:
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
uid: tempo
jsonData:
httpMethod: GET
serviceMap:
datasourceUid: prometheus # integração com métricas de serviço
nodeGraph:
enabled: true # habilitar visualização de grafo de serviços
search:
hide: false
lokiSearch:
datasourceUid: loki # correlação com logs
traceQuery:
timeShiftEnabled: true
spanStartTimeShift: 1h
spanEndTimeShift: -1hProvisioning — Dashboards como Código
Configuração de Provisioning
# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
- name: default
orgId: 1
type: file
disableDeletion: false # permitir deletar dashboards via UI
updateIntervalSeconds: 30 # verificar novos arquivos a cada 30s
allowUiUpdates: true # permitir salvar mudanças via UI
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: true # criar pastas baseadas em subdiretóriosDashboard JSON Básico
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus"
}
],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "10.0.0"
}
],
"id": null,
"uid": "api-overview",
"title": "API Overview",
"tags": ["api", "production"],
"timezone": "browser",
"schemaVersion": 38,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-1h",
"to": "now"
},
"templating": {
"list": [
{
"name": "job",
"type": "query",
"datasource": "${DS_PROMETHEUS}",
"query": "label_values(up, job)",
"refresh": 1,
"multi": true,
"includeAll": true
}
]
},
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "Request Rate",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"targets": [
{
"datasource": "${DS_PROMETHEUS}",
"expr": "sum by (job) (rate(http_requests_total{job=~\"$job\"}[$__rate_interval]))",
"legendFormat": "{{job}}"
}
]
}
]
}Plugins Úteis
# Instalar plugins via CLI
grafana-cli plugins install grafana-piechart-panel
grafana-cli plugins install grafana-worldmap-panel
grafana-cli plugins install grafana-clock-panel
grafana-cli plugins install grafana-polystat-panel
grafana-cli plugins install marcusolsson-json-datasource
grafana-cli plugins install volkovlabs-image-panel
# Via variável de ambiente no Docker
GF_INSTALL_PLUGINS: "grafana-piechart-panel,grafana-worldmap-panel,grafana-clock-panel"| Plugin | Uso |
|---|---|
grafana-piechart-panel | Gráficos de pizza para proporções |
grafana-worldmap-panel | Mapa mundial com dados geográficos |
grafana-clock-panel | Relógio para contexto de timezone |
marcusolsson-json-datasource | Datasource para APIs JSON REST |
volkovlabs-image-panel | Exibir imagens dinâmicas |
grafana-polystat-panel | Múltiplos stats em hexágonos |
Boas Práticas de Dashboard
Naming e Organização
Estrutura de pastas recomendada:
├── Infrastructure/
│ ├── Nodes Overview
│ ├── Node Detail
│ └── Network Overview
├── Applications/
│ ├── API Overview
│ ├── Service Detail
│ └── Database Overview
└── Business/
├── Sales Dashboard
└── User ActivityConvenções de nomenclatura:
- Títulos de dashboard:
[Serviço] [Escopo]ex: “API — Visão Geral”, “PostgreSQL — Detalhes” - Títulos de panels: curtos e descritivos, incluir unidade se não óbvio
- Tags: ambiente (prod/staging), time (backend/infra/dba), tipo (overview/detail)
Time Ranges e Refresh
// Configurações recomendadas por tipo de dashboard
{
// Dashboard operacional (on-call)
"time": { "from": "now-1h", "to": "now" },
"refresh": "30s",
// Dashboard de análise de capacidade
"time": { "from": "now-7d", "to": "now" },
"refresh": "5m",
// Dashboard de tendências
"time": { "from": "now-30d", "to": "now" },
"refresh": "1h"
}Thresholds Semânticos
// Padrão semáforo para SLOs
"thresholds": {
"mode": "percentage",
"steps": [
{ "color": "red", "value": null }, // 0-80% → vermelho
{ "color": "yellow", "value": 80 }, // 80-95% → amarelo
{ "color": "green", "value": 95 } // >95% → verde
]
}
// Para métricas inversas (menor = melhor, ex: latência)
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 200 }, // 200ms
{ "color": "red", "value": 500 } // 500ms
]
}Links Entre Dashboards
// Link de panel para dashboard de detalhes
"links": [
{
"title": "Ver detalhes",
"type": "dashboard",
"url": "/d/service-detail?var-service=${__field.name}",
"targetBlank": false
}
]
// Link de dashboard para logs correlacionados
{
"title": "Logs deste serviço",
"type": "link",
"url": "/explore?orgId=1&left={\"datasource\":\"Loki\",\"queries\":[{\"expr\":\"{job=\\\"$job\\\"}\"}]}",
"icon": "external link"
}Dashboard as Code com Grafonnet
Grafonnet é uma biblioteca Jsonnet para gerar dashboards programaticamente.
# Instalar dependências
brew install jsonnet
jb init
jb install github.com/grafana/grafonnet/gen/grafonnet-latest@main// dashboard.jsonnet
local grafana = import 'grafonnet-latest/main.libsonnet';
local dashboard = grafana.dashboard;
local panel = grafana.panel;
local query = grafana.query;
// Variáveis reutilizáveis
local prometheusDs = grafana.datasource.prometheus.new('DS_PROMETHEUS', 'Prometheus');
local jobVar = grafana.dashboard.variable.query.new('job')
+ grafana.dashboard.variable.query.withDatasource('prometheus', 'DS_PROMETHEUS')
+ grafana.dashboard.variable.query.withQuery('label_values(up, job)')
+ grafana.dashboard.variable.query.selectionOptions.withMulti()
+ grafana.dashboard.variable.query.selectionOptions.withIncludeAll();
// Panel de request rate
local requestRatePanel =
panel.timeSeries.new('Request Rate')
+ panel.timeSeries.gridPos.withH(8)
+ panel.timeSeries.gridPos.withW(12)
+ panel.timeSeries.queryOptions.withTargets([
query.prometheus.new(
'$DS_PROMETHEUS',
'sum by (job) (rate(http_requests_total{job=~"$job"}[$__rate_interval]))'
)
+ query.prometheus.withLegendFormat('{{job}}'),
])
+ panel.timeSeries.standardOptions.withUnit('reqps')
+ panel.timeSeries.options.tooltip.withMode('multi');
// Dashboard final
dashboard.new('API Overview')
+ dashboard.withUid('api-overview')
+ dashboard.withTags(['api', 'production'])
+ dashboard.time.withFrom('now-1h')
+ dashboard.withRefresh('30s')
+ dashboard.withVariables([jobVar])
+ dashboard.withPanels([requestRatePanel])# Gerar JSON do dashboard
jsonnet -J vendor dashboard.jsonnet > dashboard.json
# Importar no Grafana via API
curl -X POST http://grafana:3000/api/dashboards/db \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"dashboard\": $(cat dashboard.json), \"overwrite\": true}"Grafana Alloy
Grafana Alloy é o sucessor do Grafana Agent — coleta telemetria (métricas, logs, traces) e encaminha para Prometheus/Loki/Tempo.
// alloy/config.alloy
// Coletar métricas do sistema
prometheus.exporter.unix "node" {
// Equivalente ao node_exporter
}
prometheus.scrape "unix" {
targets = prometheus.exporter.unix.node.targets
forward_to = [prometheus.remote_write.mimir.receiver]
}
// Coleta de logs via tail
loki.source.file "logs" {
targets = [
{__path__ = "/var/log/*.log", job = "system"},
]
forward_to = [loki.write.grafana_loki.receiver]
}
// Coleta de logs Docker
loki.source.docker "containers" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.containers.targets
forward_to = [loki.write.grafana_loki.receiver]
}
discovery.docker "containers" {
host = "unix:///var/run/docker.sock"
}
// Receber traces via OTLP
otelcol.receiver.otlp "default" {
grpc { endpoint = "0.0.0.0:4317" }
http { endpoint = "0.0.0.0:4318" }
output {
traces = [otelcol.exporter.otlp.tempo.input]
metrics = [otelcol.exporter.prometheus.default.input]
logs = [otelcol.exporter.loki.default.input]
}
}
// Pipeline de métricas: processar → enviar
otelcol.processor.batch "default" {
output {
metrics = [otelcol.exporter.prometheus.default.input]
traces = [otelcol.exporter.otlp.tempo.input]
}
}
// Remote write para Prometheus/Mimir
prometheus.remote_write "mimir" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
// Enviar logs para Loki
loki.write "grafana_loki" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
// Enviar traces para Tempo
otelcol.exporter.otlp "tempo" {
client {
endpoint = "http://tempo:4317"
tls { insecure = true }
}
}# Executar Alloy via Docker
docker run -d \
--name alloy \
-v /etc/alloy:/etc/alloy \
-v /var/log:/var/log:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 12345:12345 \
grafana/alloy:latest \
run /etc/alloy/config.alloyReferência Rápida
API do Grafana
# Listar dashboards
curl -H "Authorization: Bearer $TOKEN" http://grafana:3000/api/search
# Criar/atualizar dashboard
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"dashboard": {...}, "overwrite": true}' \
http://grafana:3000/api/dashboards/db
# Criar API key
curl -X POST -H "Content-Type: application/json" \
-u admin:admin \
-d '{"name":"deploy-key", "role": "Editor"}' \
http://grafana:3000/api/auth/keys
# Health check
curl http://grafana:3000/api/health
# Reload provisioning
curl -X POST -u admin:admin \
http://grafana:3000/api/admin/provisioning/dashboards/reloadUnidades Comuns no Grafana
| Unit Key | Descrição |
|---|---|
reqps | Requisições por segundo |
percentunit | Percentual (0.0 a 1.0) |
percent | Percentual (0 a 100) |
bytes | Bytes (auto-escalado) |
Bps | Bytes por segundo |
ms | Milissegundos |
s | Segundos |
short | Número curto (k, M) |
none | Sem unidade |
dateTimeISO | Data/hora ISO 8601 |
Variáveis Especiais do Grafana
| Variável | Valor |
|---|---|
$__from | Início do range (ms) |
$__to | Fim do range (ms) |
$__interval | Intervalo auto |
$__rate_interval | Intervalo para rate() |
$__range | Duração total (ex: 1h) |
$__org | ID da organização |
$__user.login | Login do usuário |
$__dashboard | Nome do dashboard |
${variable:csv} | Variable como CSV |
${variable:regex} | Variable como regex |
${variable:pipe} | Variable com pipe (OR) |