Cambiar tema

Monitorización de Ollama en producción: logs, Prometheus y alertas

Easton editorial illustration: agent rollout and rollback rail

Tres diecisiete de la madrugada. Alerta roja en Slack: Ollama API timeout - service unavailable. Sistema de soporte con cientos de llamadas diarias. Solo logs básicos, sin monitorización. ¿VRAM llena? ¿Proceso muerto? ¿Red? A ciegas hasta las seis.

70%
Proyectos de IA que no llegan a producción
Source: Informe Hyperion Consulting 2026

La falta de monitorización es una causa principal.

Aquí va el plan completo: logs → Prometheus + Grafana → AlertManager. Configs listas para copiar. ~30 minutos para un stack de producción.

Retos de monitorización en producción

Ollama no es un web service normal: consume muchos recursos. Cada modelo: 4-16 GB RAM (Markaicode). Cold start 10-30 s —si reinicia, el usuario espera media minuta.

Trampas habituales:

Fugas de memoria y VRAM agotada. Tras días, a veces no libera VRAM. Vi 24 GB con solo 2 GB libres; rechazos hasta quejas.

Cola de peticiones. Inferencia 5-20 s; decenas concurrentes → timeout. Sin métricas, solo intuición.

Latencia de carga de modelos. Cambio entre modelos = caja negra.

Objetivos claros: disponibilidad, rendimiento, recursos (VRAM), errores.

Opciones: Prometheus + Grafana (equipos pequeños); Langfuse (Prompt/respuesta); SigNoz (OpenTelemetry enterprise). Foco en Prometheus por ser la base más universal.

Logs y servicio systemd

Configuración systemd

# /etc/systemd/system/ollama.service

[Unit]
Description=Ollama Service
After=network.target

[Service]
Type=simple
User=ollama
Group=ollama
WorkingDirectory=/usr/share/ollama
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_MODELS=/data/ollama/models"
Environment="OLLAMA_DEBUG=1"
Environment="OLLAMA_LOG_FORMAT=json"
LimitNOFILE=65535
LimitNPROC=4096
MemoryMax=32G
Restart=always
RestartSec=10
ExecStart=/usr/local/bin/ollama serve
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
  • Restart=always + RestartSec=10: evita bucle de reinicios sin pausa
  • MemoryMax=32G: protege otros servicios en la máquina
  • OLLAMA_DEBUG=1 y JSON: depuración y parsing
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo systemctl enable ollama

Docker logging

services:
  ollama:
    image: ollama/ollama:latest
    restart: always
    ports:
      - "11434:11434"
    volumes:
      - ./ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0:11434
      - OLLAMA_DEBUG=1
    deploy:
      resources:
        limits:
          memory: 32G
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"

Máx. ~500 MB de logs.

Niveles de log

Variables de entorno de Ollama:

VariableDescripciónProducción
OLLAMA_DEBUG1 = log detalladoRecomendado activar
OLLAMA_LOG_LEVELINFO / DEBUG / WARNINFO o DEBUG
OLLAMA_LOG_FORMATtext / jsonJSON

Suele valer la pena DEBUG: el disco aguanta y ahorra horas al depurar.

journalctl

# Tiempo real
sudo journalctl -u ollama -f

# Últimas 100 líneas
sudo journalctl -u ollama -n 100

# Hoy
sudo journalctl -u ollama --since today

# Errores
sudo journalctl -u ollama | grep -i "error"

# Exportar a archivo
sudo journalctl -u ollama --since "2026-04-12 00:00:00" > ollama-debug.log

Con JSON: sudo journalctl -u ollama -o json | jq 'select(.level=="error")' — solo errores, sin filtrar INFO a mano.

Prometheus + Grafana

ollama-exporter

frcooper/ollama-exporter:

services:
  ollama-exporter:
    image: frazco/ollama-exporter:latest
    restart: always
    ports:
      - "9101:9101"
    environment:
      - OLLAMA_HOST=ollama:11434
    depends_on:
      - ollama
# prometheus.yml
global:
  scrape_interval: 30s
  evaluation_interval: 30s

scrape_configs:
  - job_name: 'ollama-exporter'
    static_configs:
      - targets: ['ollama-exporter:9101']
        labels:
          instance: 'ollama-prod'
  - job_name: 'nvidia-gpu'
    static_configs:
      - targets: ['localhost:9835']

Añade Prometheus al docker-compose:

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: always
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'

volumes:
  prometheus_data:

Métricas clave

MétricaUso
ollama_requests_totalTasa de peticiones
ollama_requests_failedErrores
ollama_model_load_duration_secondsCold start
ollama_request_duration_secondsP95/P99
ollama_tokens_per_secondThroughput

También conviene métricas de sistema (node-exporter): node_cpu_seconds_total, node_memory_MemAvailable_bytes, node_network_receive_bytes_total.

GPU (nvidia exporter): nvidia_gpu_utilization, nvidia_gpu_memory_used_bytes, temperatura. Multi-GPU: etiqueta gpu_id.

docker run -d --name nvidia-exporter --restart always -p 9835:9835 --gpus all nvidia/gpu-prometheus-exporter:latest

Dashboard Grafana

Guarda este JSON e impórtalo en Grafana (Import Dashboard):

{
  "dashboard": {
    "title": "Ollama Production Monitor",
    "panels": [
      {
        "title": "Request Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(ollama_requests_total[5m])",
            "legendFormat": "Requests/sec"
          }
        ],
        "gridPos": {"x": 0, "y": 0, "w": 12, "h": 6}
      },
      {
        "title": "Error Rate",
        "type": "gauge",
        "targets": [
          {
            "expr": "rate(ollama_requests_failed[5m]) / rate(ollama_requests_total[5m]) * 100",
            "legendFormat": "Error %"
          }
        ],
        "gridPos": {"x": 12, "y": 0, "w": 6, "h": 6}
      },
      {
        "title": "GPU Memory Usage",
        "type": "graph",
        "targets": [
          {
            "expr": "nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes * 100",
            "legendFormat": "GPU {{gpu_id}}"
          }
        ],
        "gridPos": {"x": 0, "y": 6, "w": 12, "h": 6}
      },
      {
        "title": "Response Latency P95",
        "type": "stat",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(ollama_request_duration_seconds_bucket[5m]))",
            "legendFormat": "P95 Latency"
          }
        ],
        "gridPos": {"x": 12, "y": 6, "w": 6, "h": 6}
      }
    ]
  },
  "overwrite": true
}

Arriba a la izquierda: tasa de peticiones; arriba a la derecha: gauge de errores (rojo >5%); abajo: VRAM por GPU y P95. Añade un panel tokens/s para comparar modelos.

Datasource: Configuration → Data Sources → Prometheus, URL http://prometheus:9090 (en docker-compose los contenedores se hablan por nombre).

Alertas y AlertManager

Tres niveles (aprendido a golpes):

NivelCondiciónRespuesta
CriticalCaída, VRAM >95%, error >20%Inmediato (Slack + móvil)
WarningLatencia >60 s, VRAM >80%, error >5%1 h (Slack)
InfoCambio de modelo, deploySolo registro

En prometheus.yml añade rule_files: ['ollama_alerts.yml'] y crea el archivo:

# ollama_alerts.yml
groups:
  - name: ollama_critical
    rules:
      - alert: OllamaServiceDown
        expr: up{job="ollama-exporter"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Ollama Service Down"
          description: "Exporter inalcanzable, el servicio puede haber caído"

      - alert: GPUMemoryCritical
        expr: nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes > 0.95
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "VRAM casi agotada"
          description: "GPU {{ gpu_id }} supera 95%, actualmente {{ $value | humanizePercentage }}"

      - alert: HighErrorRate
        expr: rate(ollama_requests_failed[5m]) / rate(ollama_requests_total[5m]) > 0.20
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "Tasa de error demasiado alta"
          description: "Error >20% en los últimos 5 minutos, revisa logs"

  - name: ollama_warning
    rules:
      - alert: SlowResponseTime
        expr: histogram_quantile(0.95, rate(ollama_request_duration_seconds_bucket[5m])) > 60
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P95 demasiado lento"
          description: "El 95% de peticiones supera 60 segundos"

      - alert: GPUMemoryWarning
        expr: nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes > 0.80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Uso alto de VRAM"
          description: "GPU {{ gpu_id }} supera 80%"

      - alert: ErrorRateWarning
        expr: rate(ollama_requests_failed[5m]) / rate(ollama_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Tasa de error en alza"
          description: "Error >5% en los últimos 5 minutos"

for: Xm evita falsos positivos por picos. Umbral VRAM 95%: en la práctica, pasado ese punto falla casi al instante.

AlertManager (alertmanager.yml):

global:
  resolve_timeout: 5m

route:
  group_by: ['severity', 'alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 3h
  routes:
    - match:
        severity: critical
      receiver: 'critical-alerts'
    - match:
        severity: warning
      receiver: 'warning-alerts'

receivers:
  - name: 'critical-alerts'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
        channel: '#ollama-critical'
        send_resolved: true
  - name: 'warning-alerts'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
        channel: '#ollama-monitor'
        send_resolved: true

Silences para mantenimiento: UI en :9093 o API POST a /api/v1/silences.

Herramientas LLM avanzadas

Langfuse

Trazas de Prompt, versiones, coste token, feedback de calidad. Ejemplo Python con Ollama:

from langfuse import Langfuse
import requests

langfuse = Langfuse(
    public_key="pk-xxx",
    secret_key="sk-xxx",
    host="https://cloud.langfuse.com"
)

trace = langfuse.trace(
    name="ollama-chat",
    input={"prompt": user_prompt},
    metadata={"model": "llama3.1"}
)

response = requests.post(
    "http://localhost:11434/api/generate",
    json={"model": "llama3.1", "prompt": user_prompt}
)

trace.update(
    output=response.json()["response"],
    metadata={"tokens": response.json().get("eval_count", 0)}
)

Self-hosted con Docker; LangChain tiene callback handler oficial.

SigNoz

OpenTelemetry unificado (logs + métricas + traces). Útil multi-servicio; requiere ~4 GB RAM.

EscenarioRecomendación
Equipo <5Prometheus + Grafana
Trazas Prompt+ Langfuse
Empresa multi-servicioSigNoz
CloudServicio gestionado

Yo uso Prometheus + Grafana + Langfuse: infra vs capa LLM separadas.

Cierre

No esperes al incidente de las 3 AM.

Con este stack llevo más de un año; alertas Warning de VRAM resueltas antes de Critical.

Pasos:

  1. Prometheus + Grafana básico
  2. Observa 3-5 días el rango normal
  3. Ajusta umbrales
  4. Añade Langfuse si necesitas Prompt

La monitorización se paga una vez y rinde siempre.


Repositorio de configs: github.com/yourname/ollama-monitoring-config (ejemplo —sustituir en despliegue real)

Serie:

FAQ

¿Qué métricas core en producción con Ollama?
Disponibilidad (proceso vivo), rendimiento (latencia P95/P99), uso de VRAM GPU, tasa de error de peticiones.
¿Prometheus + Grafana vs Langfuse?
Prometheus + Grafana: infra (CPU, GPU, memoria, QPS). Langfuse: capa LLM (Prompt, coste token, calidad). Complementarios.
¿Umbrales de alerta razonables?
Critical: VRAM &gt;95%, error &gt;20%, servicio caído —actuar ya. Warning: VRAM &gt;80%, error &gt;5% —revisar en 1 h. Pocos Critical, que importen.
¿Logs Docker que crecen sin límite?
En docker-compose logging: max-size: "100m", max-file: "5" —máx. ~500 MB total.
¿Monitorizar cada GPU en multi-GPU?
nvidia_gpu_prometheus_exporter añade etiqueta gpu_id. En Grafana PromQL usa {{gpu_id}} en legendFormat.
¿Diagnóstico rápido a las 3 AM?
1) Curva VRAM (¿llena?). 2) Tendencia de errores (pico o degradación). 3) journalctl por mensaje de error. Orden para localizar en ~10 min.

6 min de lectura · Publicado el: 12 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog