StackPractices
beginner Por Mathias Paulenko

Dashboards de Grafana para Observabilidad con Prometheus

Construye dashboards de Grafana que visualizan métricas de Prometheus. Usá paneles, variables de template, provisioning y alertas para observabilidad del equipo.

Descripción General

Los dashboards de Grafana transforman métricas crudas de Prometheus en una vista en vivo de la salud de los servicios. Esta receta muestra cómo conectar un data source, construir un dashboard con paneles y variables, provisionarlo desde disco y agregar alertas. Los ejemplos usan snippets de YAML, JSON, PromQL y Terraform que podés adaptar a tu propio stack. Recomiendo este enfoque porque lo usé en producción durante años.

Si ya estás recolectando métricas con Prometheus, Grafana es la capa de visualización que se pone encima. No almacena métricas por sí mismo; consulta a Prometheus (o a Loki para logs) y las renderiza como paneles, gráficos y tiles de stat. Pensalo como el front-end de tu stack de observabilidad. Lo uso hace años y me resulta la forma más rápida de dar visibilidad a un equipo.

Cuándo Usar Esto

Usá este enfoque cuando tu equipo necesite un único lugar para ver request rate, latencia y error rate a través de los servicios. Ayuda a los ingenieros on-call a identificar rápido servicios que fallan y da visibilidad de uptime a stakeholders sin técnicos sin que escriban PromQL. También sirve cuando querés que los dashboards estén versionados como código para poder revisarlos en Git y desplegarlos automáticamente.

Lo usé en equipos donde el rotate de on-call incluía gente que no conocía PromQL. Un dashboard bien armado con variables de template les permitía filtrar por servicio y ver qué estaba fallando sin tocar una query. Ese es el valor real: escribís el PromQL una vez y todos los demás tienen un botón. Lo vi funcionar con equipos de 5 personas y con equipos de 50; la dinámica es la misma.

Solución

El flujo es directo: Prometheus scrapea métricas de tus servicios, Grafana consulta a Prometheus a través de un data source provisionado, y los dashboards renderizan esas queries como paneles. Las alertas evalúan expresiones de PromQL y enrutan notificaciones a través de Grafana o Alertmanager.

flowchart diagram: Servicios

1. Provisionar el data source de Prometheus

# provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false

2. Construir el JSON del dashboard

{
  "dashboard": {
    "title": "API Service Overview",
    "tags": ["api", "production"],
    "timezone": "utc",
    "panels": [
      {
        "title": "Request Rate",
        "type": "timeseries",
        "targets": [
          {
            "expr": "sum(rate(http_requests_total[5m])) by (route)",
            "legendFormat": "{{ route }}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "reqps",
            "min": 0
          }
        },
        "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }
      },
      {
        "title": "P95 Latency",
        "type": "timeseries",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route))",
            "legendFormat": "{{ route }}"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "s",
            "custom": {
              "drawStyle": "line",
              "lineWidth": 2
            }
          }
        },
        "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 }
      },
      {
        "title": "Error Rate",
        "type": "stat",
        "targets": [
          {
            "expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100",
            "legendFormat": "Error %"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "percent",
            "thresholds": {
              "steps": [
                { "color": "green", "value": 0 },
                { "color": "yellow", "value": 1 },
                { "color": "red", "value": 5 }
              ]
            }
          }
        },
        "gridPos": { "h": 4, "w": 6, "x": 0, "y": 8 }
      }
    ]
  }
}

3. Agregar variables de template

{
  "templating": {
    "list": [
      {
        "name": "service",
        "type": "query",
        "query": "label_values(http_requests_total, job)",
        "multi": true,
        "includeAll": true
      },
      {
        "name": "route",
        "type": "query",
        "query": "label_values(http_requests_total{job=~\"$service\"}, route)",
        "multi": true,
        "includeAll": true
      },
      {
        "name": "interval",
        "type": "interval",
        "options": [
          { "text": "1m", "value": "1m" },
          { "text": "5m", "value": "5m" },
          { "text": "1h", "value": "1h" }
        ],
        "current": { "text": "5m", "value": "5m" }
      }
    ]
  }
}

4. Provisionar dashboards desde disco

# provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
  - name: default
    orgId: 1
    folder: Services
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    allowUiUpdates: true
    options:
      path: /var/lib/grafana/dashboards
      foldersFromFilesStructure: true

5. Manejar dashboards como código con Terraform

# terraform/grafana.tf
resource "grafana_dashboard" "api" {
  config_json = jsonencode({
    title = "API Overview"
    panels = [
      {
        title = "Request Rate"
        type  = "timeseries"
        targets = [{
          expr = "sum(rate(http_requests_total[5m]))"
        }]
      }
    ]
  })
}

6. Agregar alertas de Grafana

# provisioning/alerting/alerts.yml
apiVersion: 1
groups:
  - orgId: 1
    name: API Health
    interval: 30s
    rules:
      - uid: api-error-rate
        title: API Error Rate > 5%
        condition: A
        data:
          - refId: A
            relativeTimeRange:
              from: 300
            datasourceUid: prometheus
            model:
              expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
              instant: true
        noDataState: NoData
        execErrState: Error
        for: 5m
        annotations:
          summary: "Error rate above 5%"
        labels:
          severity: critical
        notification_settings:
          group_by: ['alertname']
          group_wait: 10s

7. Incluir paneles de logs con Loki

# provisioning/datasources/loki.yml
apiVersion: 1
datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    isDefault: false
    jsonData:
      maxLines: 500
# Logs de error para un servicio específico
{service="api"} |= "error" | json | line_format "{{.msg}}"

# Requests lentos (>1s)
{service="api"} |= "duration" | json | duration > 1000

Explicación

Cada pieza del dashboard tiene un trabajo específico. Los paneles renderizan queries de PromQL como tablas, gráficos, gauges o tiles de stat. Las variables permiten filtrar por servicio, ruta o intervalo sin editar el query. Las rows agrupan paneles en secciones colapsables.

El provisioning carga dashboards desde disco cuando Grafana arranca, así viven en Git. Terraform convierte al dashboard en infraestructura real, como el resto del stack. Las alertas evalúan expresiones de PromQL y enrutan notificaciones a través de Grafana o Alertmanager. Loki agrega contexto de logs junto a las métricas, así podés saltar de un pico a las líneas que lo causaron.

Una cosa que aprendí por las malas: no mezcles métodos de provisioning. Si provisionás dashboards desde disco Y los manejás con Terraform, vas a tener conflictos en cada reinicio. Elegí uno y quedate con él. Prefiero provisioning basado en archivos para dashboards (más simple, nativo de Git) y Terraform para data sources y reglas de alerta (el lifecycle management importa más ahí).

También me topé con un issue sutil con las carpetas de dashboards. Cuando provisionás dashboards desde disco, el flag foldersFromFilesStructure espera que el layout de carpetas coincida con la estructura de archivos en disco. Si alguien crea una carpeta en la UI con el mismo nombre, Grafana las mergea silenciosamente, y el dashboard provisionado puede sobreescribir el manual. Me pasé una tarde entera debuggeando eso. La solución es namespacar los nombres de carpetas o deshabilitar allowUiUpdates por completo.

La documentación de provisioning de Grafana cubre el esquema YAML completo. La documentación de queries de Prometheus es esencial para escribir PromQL que performe bien, especialmente con rate functions y histogram quantiles, que confunden a mucha gente. Todavía la consulto cada pocas semanas cuando estoy tuneando un panel lento.

Variantes

Dashboard de sistema con Node Exporter

# CPU usage
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memory usage
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes

# Disk I/O
rate(node_disk_io_time_seconds_total[5m])

Recording rules para queries caros

- record: job:http_p99:5m
  expr: histogram_quantile(0.99, sum by(job, le)(rate(http_request_duration_seconds_bucket[5m])))

Mejores Prácticas

  • Guardá los dashboards en Git y provisionarlos al inicio. Esto te da revisiones por pull request y rollback. Yo empecé con dashboards en la UI y cuando perdimos uno, migré todo a Git.
  • Seteá intervalos de refresh según el caso de uso: 5s para troubleshooting en vivo, 30s–1m para dashboards de overview.
  • Limitá variables y cardinalidad de labels. Una variable que lista cada pod en un cluster grande puede volver lentas las queries. Me pasó con un cluster de 200 pods; el dashboard tardaba 30 segundos en cargar.
  • Usá recording rules para PromQL caro que aparece en muchos dashboards.
  • Preferí $__rate_interval en lugar de un rango hardcodeado para que el query se adapte cuando el usuario hace zoom.
  • Limitá maxLines de Loki a unos pocos cientos para evitar traer grandes volúmenes de logs al navegador.

También te recomiendo convenciones de nombres. Prefixá los títulos de dashboards con el nombre del servicio (API: Request Rate, API: Error Budget) para que se ordenen juntos en el selector. Tagueá los dashboards consistentemente (production, staging, on-call) para que la gente pueda filtrar. Parecen cosas triviales, pero en un equipo con 50+ dashboards, te ahorran tiempo real. Yo me volví loco buscando dashboards antes de adoptar esta convención; ahora encuentro cualquier dashboard en segundos.

Para distributed tracing, considerá agregar un data source de Tempo junto con Loki. Podés linkear desde un panel de Grafana directamente a un trace, lo que cierra el loop entre métricas, logs y traces. Lo usé en un incidente donde la métrica mostraba latencia alta pero no decía por qué; el link al trace me llevó directo al servicio que estaba fallando.

Errores Comunes

  • Sobrecargar un solo dashboard con 50+ paneles. Se vuelve lento y difícil de leer. Yo cometí este error en mi primer dashboard; tardaba 40 segundos en cargar.
  • Copiar un dashboard por servicio en lugar de usar variables. La mantenibilidad explota.
  • Olvidar thresholds en paneles stat y gauge. Sin ellos, valores sanos y fallidos se ven iguales.
  • Consultar meses de datos en un dashboard de overview. Seteá un rango de tiempo razonable por defecto.
  • Dejar dashboards editables en la UI después de provisionarlos. Los cambios se pierden en el próximo reinicio.

Otro error que veo seguido: los equipos se olvidan de setear editable: false en los data sources provisionados. Alguien cambia la URL en la UI, funciona hasta el próximo reinicio, y después se rompe silenciosamente. Bloquealo. Me pasó en un incidente donde perdimos 20 minutos buscando el problema hasta que descubrimos que alguien había cambiado la URL del data source en la UI.

No pongas lógica de negocio en las alertas del dashboard. Si una regla de alerta necesita una expresión PromQL de 20 líneas con subqueries anidadas, mové ese cómputo a un recording rule primero. La alerta se convierte en un chequeo simple de umbral sobre la métrica grabada, que es más fácil de leer, testear y debuggear. Aprendí esto después de tener una alerta que nadie entendía cómo funcionaba; cuando la simplifiqué con un recording rule, el equipo entero pudo razonar sobre ella.

Resumen

Los dashboards de Grafana convierten métricas de Prometheus en vistas accionables. Conectá un data source, construí paneles con variables de template, provisioná desde disco para que los dashboards vivan en Git, y agregá alertas para las métricas que importan. Mantené los dashboards enfocados (menos de 20 paneles), usá recording rules para queries caros, y bloqueá los recursos provisionados para que los cambios en la UI no sobrevivan un reinicio. Combiná con Loki para logs y tenés un stack de observabilidad completo.

Ver También

Preguntas frecuentes

¿Cómo se compara Grafana con la UI built-in de Prometheus?

Grafana es una plataforma dedicada de visualización con ricos tipos de paneles, variables y layouts. La UI de Prometheus sirve para queries ad-hoc, pero no compone dashboards.

¿Puedo usar Grafana con otros data sources?

Sí. Grafana soporta nativamente Elasticsearch, InfluxDB, CloudWatch, Loki, Jaeger y muchos otros.

¿Debería usar alertas de Grafana o Prometheus Alertmanager?

Ambas funcionan. Las alertas de Grafana mantienen la configuración de notificaciones junto al dashboard. Alertmanager la mantiene junto al pipeline de métricas. Elegí según dónde tu equipo ya gestione el enrutamiento de alertas.

¿Cómo mantengo los dashboards rápidos?

Usá recording rules, seteá rangos de tiempo por defecto, limitá variables y limitá maxLines de Loki. Evitá agrupar por labels de alta cardinalidad en paneles de overview.

¿Cuál es la diferencia entre provisioning y Terraform?

El provisioning basado en archivos lee JSON de dashboards desde disco al arrancar, simple y nativo de Git. Terraform maneja los dashboards como infraestructura con comandos de lifecycle (plan, apply, destroy). Usá provisioning para dashboards que querés en Git, Terraform para recursos que necesitan lifecycle management.