TL;DR — Resumen Rápido
Configura Grafana Loki para centralizar logs con Promtail. Cubre LogQL, dashboards en Grafana, alertas con ruler y configuración multi-tenant en producción.
Grafana Loki es un sistema de agregación de logs escalable horizontalmente construido sobre los mismos principios que Prometheus: indexa solo etiquetas, no el texto completo del log, lo que lo hace dramáticamente más económico que el stack ELK. Esta guía cubre la arquitectura de Loki, instalación con Docker Compose, configuración de Promtail, el lenguaje de consultas LogQL, construcción de dashboards en Grafana, alertas, multi-tenancy y dimensionamiento en producción.
Requisitos Previos
- Docker y Docker Compose (o un clúster de Kubernetes para instalación con Helm).
- Grafana 10+ (o Grafana Cloud) para visualización.
- Familiaridad básica con configuración YAML y herramientas de línea de comandos.
Arquitectura de Loki
Loki distribuye su trabajo entre cuatro componentes principales:
- Distributor — Recibe flujos de logs de Promtail/agentes, valida etiquetas y los distribuye a los ingesters mediante hashing consistente.
- Ingester — Mantiene logs recientes en memoria (tamaño de chunk configurable), descarga chunks comprimidos a almacenamiento de objetos y mantiene un WAL para recuperación ante fallos.
- Querier — Ejecuta consultas LogQL sobre chunks tanto en memoria (recientes) como en almacenamiento (históricos).
- Compactor — Fusiona y deduplica las tablas de índice de boltdb-shipper y aplica las políticas de retención.
En modo monolítico (por defecto en instalaciones de nodo único), los cuatro componentes corren en un solo binario. Cuando el volumen de logs supera los ~50 GB/día, conviene cambiar a modo microservicios donde cada componente escala de forma independiente.
La clave del diseño: Loki nunca construye un índice invertido del contenido de los logs. Una consulta con |= "OutOfMemoryError" realiza un grep distribuido sobre los chunks que coinciden con las etiquetas seleccionadas.
¿Por Qué Loki en Lugar de ELK?
| Característica | Loki | Elasticsearch |
|---|---|---|
| Modelo de indexado | Solo etiquetas | Índice invertido full-text |
| Costo de almacenamiento | ~10x menos | Alto (índice + datos) |
| Lenguaje de consulta | LogQL (similar a PromQL) | Lucene / KQL |
| Nativo en Grafana | Sí — primera clase | Via plugin |
| Escala horizontal | Sí (sharding por etiquetas) | Sí (shards) |
| Cambios de esquema | No necesarios | Requiere re-indexado |
| Alertas | Loki ruler + Alertmanager | ElastAlert / SIEM |
| Nativo en Kubernetes | Sí (Helm chart) | Sí (operador ECK) |
Paso 1: Instalación con Docker Compose
Crea un directorio de proyecto y el siguiente docker-compose.yml:
version: "3.8"
services:
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:3.0.0
volumes:
- ./promtail-config.yaml:/etc/promtail/config.yaml
- /var/log:/var/log:ro
- /run/log/journal:/run/log/journal:ro
command: -config.file=/etc/promtail/config.yaml
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
volumes:
loki-data:
Paso 2: Configurar Loki
Crea loki-config.yaml:
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 5m
chunk_retain_period: 30s
wal:
enabled: true
dir: /loki/wal
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/boltdb-cache
shared_store: filesystem
filesystem:
directory: /loki/chunks
compactor:
working_directory: /loki/compactor
shared_store: filesystem
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
limits_config:
retention_period: 30d
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_query_length: 721h
Paso 3: Configurar Promtail
Crea promtail-config.yaml:
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:
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
host: __HOSTNAME__
relabel_configs:
- source_labels: [__journal__systemd_unit]
target_label: unit
- job_name: nginx
static_configs:
- targets: [localhost]
labels:
job: nginx
env: prod
__path__: /var/log/nginx/access.log
- job_name: docker
static_configs:
- targets: [localhost]
labels:
job: docker
__path__: /var/lib/docker/containers/*/*-json.log
pipeline_stages:
- json:
expressions:
log: log
stream: stream
- output:
source: log
Inicia el stack:
docker compose up -d
docker compose logs -f loki
Paso 4: Lenguaje de Consultas LogQL
LogQL tiene dos formas: consultas de log y consultas de métricas.
Selectores de stream (obligatorios, siempre primero):
{job="nginx"}
{job="nginx", env="prod"}
{job=~"nginx|apache"}
Expresiones de filtro (pipe tras el selector):
{job="nginx"} |= "error"
{job="nginx"} != "health_check"
{job="nginx"} |~ "5[0-9]{2}"
Expresiones de parser — extrae campos de logs estructurados:
{job="app"} | json
{job="app"} | logfmt
{job="app"} | json | level="error"
Consultas de métricas — convierte logs en series temporales:
rate({job="nginx"} |= "error" [5m])
count_over_time({job="app"} | json | level="error" [1h])
sum by (status) (rate({job="nginx"} | logfmt | status=~"5.." [5m]))
topk(5, sum by (uri) (rate({job="nginx"}[10m])))
Paso 5: Fuente de Datos y Dashboards en Grafana
- En Grafana ve a Connections → Data sources → Add data source → Loki.
- Configura la URL a
http://loki:3100(o tu dirección de Loki). - Haz clic en Save & test — deberías ver “Data source connected.”
Consejos para el panel Explore:
- Usa Live tail para hacer streaming de logs en tiempo real — ideal para depurar despliegues.
- Haz clic en cualquier línea de log para ver el contexto del log — líneas anteriores y posteriores al match.
- Cambia a la pestaña Metrics para visualizar consultas
rate()como paneles de series temporales.
Paso 6: Alertas con Loki Ruler
Agrega un bloque ruler a loki-config.yaml:
ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://alertmanager:9093
ring:
kvstore:
store: inmemory
enable_api: true
Crea /loki/rules/fake/rules.yaml:
groups:
- name: alertas-app
interval: 1m
rules:
- alert: TasaErrorAlta
expr: |
sum(rate({job="app"} | json | level="error" [5m])) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Tasa de errores alta en logs de app"
Loki vs. Alternativas
| Herramienta | Modelo de almacenamiento | Auto-alojado | Costo | Nativo Grafana | Mejor para |
|---|---|---|---|---|---|
| Loki | Índice de etiquetas + chunks comprimidos | Sí | Bajo | Sí | Equipos con Grafana/Prometheus |
| Elasticsearch | Índice invertido full-text | Sí | Alto | Via plugin | Búsqueda full-text, SIEM |
| Fluentd | Solo agente (sin almacenamiento) | Sí | Gratis | No | Enrutamiento y transformación |
| Datadog Logs | SaaS, propietario | No | Alto | No | Empresa, observabilidad completa |
| CloudWatch Logs | SaaS (AWS) | No | Medio | Via plugin | Cargas de trabajo nativas de AWS |
Multi-Tenancy
Habilita multi-tenancy con auth_enabled: true en Loki. Cada solicitud debe incluir X-Scope-OrgID: <tenant-id>. Configura Promtail para enviar el encabezado:
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: equipo-backend
Recomendaciones de Dimensionamiento en Producción
| Volumen de logs | Arquitectura | Almacenamiento | RAM |
|---|---|---|---|
| < 5 GB/día | Monolítico, nodo único | Filesystem local | 2 GB |
| 5–50 GB/día | Monolítico + S3/GCS | Almacenamiento de objetos | 4–8 GB |
| 50–500 GB/día | Microservicios, 3 ingesters | Objetos + TSDB | 16–32 GB |
| > 500 GB/día | Microservicios + Thanos ruler | Objetos multi-AZ | 64 GB+ |
Siempre habilita el WAL (ingester.wal.enabled: true) en producción para evitar pérdida de datos durante reinicios del ingester.
Resolución de Problemas
| Problema | Solución |
|---|---|
connection refused al enviar | Verifica que el contenedor Loki esté corriendo y el puerto 3100 sea accesible desde Promtail |
| Timeout en consultas | Aumenta querier.query_timeout y divide las consultas en rangos de tiempo más cortos |
| Logs de Docker faltantes | Revisa positions.yaml de Promtail para la ruta del log del contenedor |
| Alta memoria en ingester | Reduce chunk_idle_period para descargar chunks al almacenamiento más pronto |
| Retención no funciona | Asegúrate de que compactor.retention_enabled: true y limits_config.retention_period estén configurados |
Resumen
- El indexado solo de etiquetas hace a Loki 10 veces más económico que Elasticsearch para el mismo volumen.
- Promtail envía logs desde archivos, journal de systemd y Docker con enriquecimiento de etiquetas.
- LogQL soporta filtrado de streams, parseo JSON/logfmt, cálculos de tasa y agregación.
- El Loki ruler evalúa alertas LogQL en un calendario y las enruta a Alertmanager.
- Para producción, reemplaza el almacenamiento en filesystem con S3/GCS y habilita WAL y caché de chunks.