Promtail y Loki para Agregación de Logs
Loki es un sistema de agregación de logs escalable y económico desarrollado por Grafana Labs que, a diferencia de Elasticsearch, solo indexa los metadatos (etiquetas/labels) y almacena los logs comprimidos. Promtail es el agente que recopila los logs de los ficheros del sistema y los envía a Loki. Juntos forman el stack de logging del ecosistema Grafana, integrándose perfectamente con Prometheus y Grafana para una observabilidad completa.
Requisitos Previos
- Ubuntu 22.04/20.04, Debian 11, o CentOS/Rocky Linux 8+
- Grafana instalado para visualización (opcional pero muy recomendado)
- Al menos 2 GB de RAM para Loki en instalaciones pequeñas
- Acceso de escritura al directorio de logs del sistema
Instalación de Loki
# Descargar Loki y Promtail desde GitHub
LOKI_VERSION="2.9.4"
wget "https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/loki-linux-amd64.zip"
wget "https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/promtail-linux-amd64.zip"
unzip loki-linux-amd64.zip
unzip promtail-linux-amd64.zip
mv loki-linux-amd64 /usr/local/bin/loki
mv promtail-linux-amd64 /usr/local/bin/promtail
chmod +x /usr/local/bin/loki /usr/local/bin/promtail
# Verificar la instalación
loki --version
promtail --version
# Alternativa: instalar con Docker
docker pull grafana/loki:latest
docker pull grafana/promtail:latest
# Crear el usuario del sistema para Loki
useradd --system --home /etc/loki --shell /bin/false loki
mkdir -p /etc/loki /var/lib/loki /var/log/loki
chown -R loki:loki /etc/loki /var/lib/loki /var/log/loki
# Configuración básica de Loki para instalación local
cat > /etc/loki/loki.yaml << 'EOF'
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
instance_addr: 127.0.0.1
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
limits_config:
# Retención global de logs (ingestion_rate_mb = 16 por defecto)
retention_period: 744h # 31 días
max_streams_per_user: 10000
ingestion_rate_mb: 32
ingestion_burst_size_mb: 64
# Habilitar eliminación de logs por política de retención
compactor:
working_directory: /var/lib/loki/compactor
retention_enabled: true
delete_request_cancel_period: 24h
EOF
# Crear el servicio systemd para Loki
cat > /etc/systemd/system/loki.service << 'EOF'
[Unit]
Description=Loki log aggregation system
After=network.target
[Service]
Type=simple
User=loki
Group=loki
ExecStart=/usr/local/bin/loki --config.file=/etc/loki/loki.yaml
StandardOutput=journal
StandardError=journal
SyslogIdentifier=loki
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable loki && systemctl start loki
# Verificar que Loki está respondiendo
curl -s http://localhost:3100/ready
curl -s http://localhost:3100/metrics | head -20
Instalación y Configuración de Promtail
# Crear el usuario para Promtail
useradd --system --home /etc/promtail --shell /bin/false promtail
# Agregar el usuario promtail al grupo adm para leer logs del sistema
usermod -aG adm promtail
mkdir -p /etc/promtail /var/lib/promtail /var/log/promtail
chown -R promtail:promtail /etc/promtail /var/lib/promtail /var/log/promtail
# Configuración de Promtail
cat > /etc/promtail/promtail.yaml << 'EOF'
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
# Archivo donde Promtail guarda la posición de lectura de cada log
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
# Recopilar logs del kernel y del sistema
- job_name: syslog
static_configs:
- targets:
- localhost
labels:
job: syslog
host: __HOSTNAME__
__path__: /var/log/syslog
pipeline_stages:
- regex:
expression: '^(?P<timestamp>\S+ \S+) (?P<hostname>\S+) (?P<app>\S+): (?P<message>.*)$'
- labels:
app:
hostname:
# Recopilar logs de Nginx
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
host: __HOSTNAME__
__path__: /var/log/nginx/*.log
pipeline_stages:
- regex:
expression: '^(?P<remote_addr>\S+) - (?P<remote_user>\S+) \[(?P<time_local>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) \S+" (?P<status>\d+) (?P<body_bytes_sent>\d+)'
- labels:
method:
status:
- metrics:
http_nginx_requests_total:
type: Counter
description: Total de peticiones HTTP de Nginx
source: status
config:
action: inc
# Recopilar logs de aplicación personalizada
- job_name: mi_aplicacion
static_configs:
- targets:
- localhost
labels:
job: mi_aplicacion
entorno: produccion
__path__: /var/log/mi_aplicacion/*.log
pipeline_stages:
# Parsear logs JSON
- json:
expressions:
level: level
message: message
request_id: request_id
user_id: user_id
- labels:
level:
request_id:
- timestamp:
source: timestamp
format: RFC3339
EOF
# Crear el servicio systemd para Promtail
cat > /etc/systemd/system/promtail.service << 'EOF'
[Unit]
Description=Promtail log agent
After=network.target
[Service]
Type=simple
User=promtail
Group=promtail
ExecStart=/usr/local/bin/promtail --config.file=/etc/promtail/promtail.yaml
StandardOutput=journal
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable promtail && systemctl start promtail
# Verificar que Promtail está enviando logs
curl -s http://localhost:9080/metrics | grep promtail_sent
Extracción de Labels con Pipelines
# Configuración avanzada de pipeline stages para logs de aplicación
cat >> /etc/promtail/promtail.yaml << 'EOF'
# Ejemplo avanzado: log de PostgreSQL
- job_name: postgresql
static_configs:
- targets:
- localhost
labels:
job: postgresql
__path__: /var/log/postgresql/postgresql-*.log
pipeline_stages:
# Manejar logs multilínea de PostgreSQL
- multiline:
firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'
max_wait_time: 3s
# Extraer campos del log de PostgreSQL
- regex:
expression: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d+) \S+ \[(?P<pid>\d+)\] (?P<user>\S+)@(?P<database>\S+) (?P<level>\w+): (?P<message>.*)'
# Solo indexar el nivel y la base de datos como labels
- labels:
level:
database:
# Filtrar logs de DEBUG para no saturar Loki
- drop:
expression: 'level="DEBUG"'
# Agregar timestamp extraído del log
- timestamp:
source: timestamp
format: '2006-01-02 15:04:05.000'
EOF
systemctl restart promtail
Backends de Almacenamiento de Loki
# Configuración de Loki con almacenamiento en S3 (para producción)
cat > /etc/loki/loki-s3.yaml << 'EOF'
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /var/lib/loki
replication_factor: 1
ring:
kvstore:
store: consul
consul:
host: consul:8500
ingester:
chunk_idle_period: 1h
chunk_target_size: 1048576
max_chunk_age: 1h
wal:
enabled: true
dir: /var/lib/loki/wal
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: s3
schema: v11
index:
prefix: index_
period: 24h
storage_config:
aws:
s3: s3://us-east-1/mi-bucket-loki-logs
s3forcepathstyle: false
region: us-east-1
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG
boltdb_shipper:
active_index_directory: /var/lib/loki/boltdb-shipper-active
cache_location: /var/lib/loki/boltdb-shipper-cache
cache_ttl: 24h
shared_store: s3
compactor:
working_directory: /var/lib/loki/compactor
shared_store: s3
retention_enabled: true
limits_config:
retention_period: 720h # 30 días
ingestion_rate_mb: 32
EOF
Consultas con LogQL
# LogQL es el lenguaje de consultas de Loki, similar a PromQL
# Consultas básicas desde la línea de comandos con logcli
# Instalar logcli
wget "https://github.com/grafana/loki/releases/download/v2.9.4/logcli-linux-amd64.zip"
unzip logcli-linux-amd64.zip && mv logcli-linux-amd64 /usr/local/bin/logcli
export LOKI_ADDR=http://localhost:3100
# Ver los streams (combinaciones de labels) disponibles
logcli labels
logcli series '{job="nginx"}'
# Consultar logs de nginx del último 1 hora
logcli query '{job="nginx"}' --limit=100
# Filtrar por método HTTP y código de estado
logcli query '{job="nginx", status="500"}' --since=1h
# Buscar texto específico en los logs
logcli query '{job="nginx"} |= "error" |= "timeout"'
# Buscar usando regex
logcli query '{job="mi_aplicacion"} |~ "ERROR|CRITICAL" !~ "test|staging"'
# Métricas derivadas de logs (LogQL metrics queries)
# Tasa de errores en Nginx en los últimos 5 minutos:
logcli query 'rate({job="nginx", status=~"5.."}[5m])'
# Número total de peticiones HTTP por estado:
logcli query 'sum by(status) (count_over_time({job="nginx"}[1h]))'
# Latencia del percentil 99 (si el log incluye un campo de duración):
logcli query 'quantile_over_time(0.99, {job="nginx"} | json | unwrap duration [5m])'
Visualización en Grafana
# Agregar Loki como datasource en Grafana mediante la API
curl -s -X POST \
-H "Content-Type: application/json" \
-u admin:contraseña \
"http://grafana.midominio.com:3000/api/datasources" \
-d '{
"name": "Loki",
"type": "loki",
"url": "http://loki:3100",
"access": "proxy",
"isDefault": false,
"jsonData": {
"maxLines": 1000,
"derivedFields": [
{
"matcherRegex": "request_id=(\\w+)",
"name": "TraceID",
"url": "http://tempo:3200/trace/$${__value.raw}",
"datasourceUid": "tempo"
}
]
}
}'
# Crear un panel de dashboard en Grafana para visualizar logs
# Usar el plugin "Logs" de Grafana con la query:
# {job="nginx", status=~"4..|5.."}
Integración con Kubernetes
# Instalar Loki Stack en Kubernetes con Helm
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# Instalar el stack completo: Loki + Promtail + Grafana
helm install loki-stack grafana/loki-stack \
--namespace monitoring \
--create-namespace \
--set grafana.enabled=true \
--set prometheus.enabled=false \
--set loki.persistence.enabled=true \
--set loki.persistence.size=50Gi
# Verificar la instalación
kubectl get pods -n monitoring | grep loki
# Promtail en Kubernetes recoge automáticamente los logs de todos los pods
# Con labels del pod automáticamente como labels de Loki:
# namespace, pod, container, node, app
# Ver los logs de un deployment específico en Kubernetes con logcli
export LOKI_ADDR=http://$(kubectl get svc -n monitoring loki -o jsonpath='{.spec.clusterIP}'):3100
logcli query '{namespace="produccion", app="api-backend"} |= "error"' --since=1h
Solución de Problemas
Loki rechaza logs con "too many outstanding requests":
# Aumentar los límites de ingesta en loki.yaml
# limits_config:
# ingestion_rate_mb: 64
# ingestion_burst_size_mb: 128
# max_streams_per_user: 50000
systemctl restart loki
Promtail no envía logs:
# Verificar el estado de Promtail
curl -s http://localhost:9080/ready
curl -s http://localhost:9080/metrics | grep promtail_sent_entries_total
# Ver los logs de Promtail
journalctl -u promtail -f
# Verificar que el archivo de posiciones está actualizado
cat /var/lib/promtail/positions.yaml
Consultas lentas en Loki:
# Verificar el uso de labels (no usar labels de alta cardinalidad)
# MALO: usar user_id, ip_address como labels
# BUENO: usar job, env, level, service como labels
# Ver las estadísticas de los chunks
curl -s http://localhost:3100/metrics | grep loki_store_chunk
Conclusión
El stack Promtail + Loki + Grafana ofrece una alternativa más económica y fácil de operar que el stack ELK para la mayoría de casos de uso de agregación de logs. La filosofía de Loki de indexar solo los metadatos y comprimir los logs reduce significativamente los costes de almacenamiento y computación, mientras que LogQL proporciona la flexibilidad necesaria para hacer consultas analíticas sobre los logs. Para equipos que ya usan Prometheus y Grafana, añadir Loki es el camino natural hacia una observabilidad completa de sus sistemas.


