Skip to main content
Miguel Ordóñez

Montar ELK rápido para debugging de problemas de performance

Hace poco me tocó resolver un problema urgente de performance en un servidor web Oracle Apache que estaba dando problemas. El cliente no tenía ELK montado y necesitábamos analizar millones de líneas de logs ya, para tomar decisiones sobre qué optimizar.

Este artículo documenta cómo monté un stack ELK temporal en pocas horas para analizar logs y encontrar el origen del problema. No es una solución para producción, sino una herramienta de debugging rápida y efectiva.

El problema: rendimiento crítico sin herramientas

El escenario:

Necesitaba ver: qué endpoints eran más lentos, de dónde venía el tráfico, qué IPs generaban más carga, horarios pico, errores 5xx, etc.

¿Por qué ELK y no otra cosa?

Elastic ofrece muchas herramientas de ingesta: Beats, Fleet, Elastic Agent, integraciones pre-configuradas... Todas son excelentes para entornos de producción permanentes.

Pero para este caso puntual elegí Logstash + Docker porque:

  1. Rapidez: Podía levantar el stack completo en minutos con Docker
  2. Ingesta masiva simple: Con Logstash y nc (netcat) podía descargar los logs del servidor e ingestarlos rápidamente sin configurar agentes
  3. Flexibilidad: El formato de log era customizado, necesitaba crear un pipeline a medida
  4. Temporal: No era para dejarlo en producción, solo para debugging puntual

Arquitectura de la solución

┌─────────────────┐
│  Servidor Web   │
│ (Oracle Apache) │
└────────┬────────┘
         │ rsync/scp (descargar logs)
         ↓
┌─────────────────┐
│   Mi Laptop     │
│   access.log    │
└────────┬────────┘
         │ cat logs | nc localhost 50000 (ingesta rápida)
         ↓
┌─────────────────┐
│   Logstash      │ ← Parser custom con Grok
│   (puerto 50000)│
└────────┬────────┘
         │ procesa y envía
         ↓
┌─────────────────┐
│ Elasticsearch   │ ← Almacena e indexa
└────────┬────────┘
         │ consulta
         ↓
┌─────────────────┐
│    Kibana       │ ← Visualiza y analiza
└─────────────────┘

Paso 1: Levantar ELK con Docker

Usamos docker-elk que tiene todo preconfigurado:

git clone https://github.com/deviantony/docker-elk.git
cd docker-elk
docker compose up setup
docker compose up -d

En 5 minutos tienes:

Paso 2: Crear el Index Template

Define cómo se almacenan los datos. Esto asegura que los campos como IPs, fechas y números se indexen correctamente:

curl -X PUT -u elastic:changeme "http://localhost:9200/_index_template/logs-debug-template?pretty" -H 'Content-Type: application/json' -d '{
  "index_patterns": ["logs-debug-*"],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 0
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "client_ip": { "type": "ip", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } },
        "server_ip": { "type": "ip", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } },
        "year": { "type": "integer" },
        "month": { "type": "integer" },
        "day": { "type": "integer" },
        "time": { "type": "keyword" },
        "hour_of_day": { "type": "integer" },
        "method": { "type": "keyword" },
        "request_path": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 512 } } },
        "request_query": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 512 } } },
        "request_full": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 512 } } },
        "status": { "type": "integer" },
        "request_size": { "type": "integer" },
        "user_agent": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 512 }
          }
        },
        "geoip": {
          "properties": {
            "country_name": { "type": "keyword" },
            "region_name": { "type": "keyword" },
            "city_name": { "type": "keyword" },
            "coordinates": { "type": "geo_point" }
          }
        }
      }
    }
  },
  "priority": 200
}'

Nota: Usamos number_of_replicas: 0 porque es un entorno temporal de debugging. En producción usarías réplicas para alta disponibilidad.

Paso 3: Pipeline customizado para parsear logs

Mi caso tenía un formato específico de Oracle Apache Server. Creé un pipeline con Grok para extraer los campos:

curl -u elastic:changeme -X PUT "localhost:9200/_ingest/pipeline/logs-debug-pipeline?pretty" -H 'Content-Type: application/json' -d '
{
  "description": "Parser para logs customizados",
  "processors": [
    {
      "grok": {
        "field": "message",
        "patterns": [
          "(?:\\\"?(%{IP:client_ip})?\\\"?|-)\\s+%{IP:server_ip}\\s+%{YEAR:year}-%{MONTHNUM:month}-%{MONTHDAY:day}\\s+%{TIME:time}\\s+%{WORD:method}\\s+%{URIPATH:request_path}(?:\\?(%{DATA:request_query}))?\\s+%{NUMBER:status}\\s+(?:%{NUMBER:request_size}|-)\\s+(?:\\\"%{GREEDYDATA:user_agent}\\\"|-)"
        ]
      }
    },
    {
      "set": {
        "field": "timestamp",
        "value": "-- "
      }
    },
    {
      "date": {
        "field": "timestamp",
        "formats": ["yyyy-MM-dd HH:mm:ss"],
        "target_field": "@timestamp"
      }
    },
    {
      "script": {
        "lang": "painless",
        "source": "def date = Instant.parse(ctx[\"@timestamp\"]).atZone(ZoneId.of(\"GMT+1\")); ctx.hour_of_day = date.getHour();"
      }
    },
    {
      "set": {
        "field": "request_full",
        "value": ""
      }
    },
    {
      "geoip": {
        "field": "client_ip",
        "target_field": "geoip",
        "properties": ["country_name", "region_name", "city_name", "location"],
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "geoip.location",
        "target_field": "geoip.coordinates",
        "ignore_missing": true
      }
    }
  ]
}
'

Importante: Este Grok pattern está customizado para mi formato específico. Si tus logs siguen el formato estándar de Apache, usa directamente la integración Apache HTTP Server que ya incluye parser + dashboards.

Paso 4: Configurar permisos en Elasticsearch

El usuario por defecto logstash_internal necesita permisos para crear índices:

curl -X POST -u elastic:changeme "localhost:9200/_security/role/logstash_debug_writer" \
  -H 'Content-Type: application/json' -d '{
  "cluster": ["manage_index_templates", "monitor"],
  "indices": [
    {
      "names": ["logs-debug-*"],
      "privileges": ["create_index", "write", "create", "manage"]
    }
  ]
}'

curl -X PUT -u elastic:changeme "localhost:9200/_security/user/logstash_internal" \
  -H 'Content-Type: application/json' -d '{
  "roles": ["logstash_writer", "logstash_debug_writer"]
}'

Paso 5: Configurar Logstash

Edita logstash/pipeline/logstash.conf:

input {
  tcp {
    port => 50000
    codec => plain
  }
}

filter {
	mutate {
		add_field => { "pipeline" => "logs-ubwebsinst-pipeline" }
	}
}

output {
  elasticsearch {
    hosts => "elasticsearch:9200"
    user => "logstash_internal"
    password => "${LOGSTASH_INTERNAL_PASSWORD}"
    pipeline => "logs-debug-pipeline"
    index => "logs-debug-%{+YYYY.MM.dd}"
  }
}

La clave aquí es el input tcp en puerto 50000. Esto permite ingestar datos con un simple cat logs | nc localhost 50000.

Reinicia Logstash:

docker compose restart logstash

Paso 6: Descargar e ingestar logs

Descarga los logs del servidor:

# Desde el servidor remoto a tu laptop
rsync -avz user@server:/path/to/logs/access* ./logs/

Ingesta masiva con netcat:

# Ingestar todos los logs de golpe
find ./logs -name "access*" -type f -exec cat {} + | nc localhost 50000

Esto envía millones de líneas directamente a Logstash que las procesa y envía a Elasticsearch. En mi caso ingesté 5GB de logs en unos 20 minutos.

Paso 7: Analizar en Kibana

  1. Abre http://localhost:5601
  2. Ve a Management > Stack Management > Data Views
  3. Crea un Data View con logs-debug-*
  4. Ve a Discover y empieza a explorar

Análisis útiles que usé

Con Kibana puedes crear visualizaciones y filtrar por:

Mi caso real: Qué encontré

Tras analizar los logs con Kibana, descubrí:

  1. 3 endpoints específicos generaban el 60% de los errores 5xx
  2. Picos de tráfico entre las 10-12h que saturaban el servidor
  3. Una IP específica hacía scraping agresivo sin respetar rate limits
  4. Peticiones lentas a una API externa de traducciones que a veces tardaba +30 segundos

Acciones tomadas:

Resultado: Errores 5xx bajaron de 15% a menos de 2%, tiempos de respuesta mejoraron un 40%.

Ventajas de este enfoque

Rápido de montar: 30 minutos desde cero hasta analizar logs ✅ Flexible: Puedes adaptar el pipeline a cualquier formato de log ✅ Ingesta masiva simple: cat logs | nc sin instalar agentes ✅ Potente: Kibana te da análisis visual que con grep nunca conseguirías ✅ Temporal: Cuando terminas, docker compose down -v y listo

Limitaciones (no es para producción)

❌ No es tiempo real (descargas logs y los analizas después) ❌ Sin réplicas ni alta disponibilidad ❌ Requiere descargar logs manualmente ❌ No es escalable para logs continuos

Cuándo usar este setup

✅ Úsalo cuando:

❌ NO lo uses cuando:

Para producción, usa las herramientas oficiales de Elastic: Elastic Agent, Fleet, Beats o las integraciones pre-configuradas.

Limpieza

Cuando termines el análisis:

# Para los contenedores y borra los datos
docker compose down -v

# Borra los logs descargados
rm -rf ./logs

Alternativas según tu caso

Conclusión

Este no es un tutorial para montar ELK en producción. Es una solución rápida y práctica para cuando necesitas analizar logs urgentemente y tomar decisiones basadas en datos.

Me salvó en una situación donde tenía que encontrar el origen de problemas de performance sin herramientas, sin tiempo y con logs en formato custom.

Si estás en una situación similar: descarga docker-elk, adapta el pipeline a tu formato de log, ingesta con netcat y en una hora estarás explorando tus datos en Kibana.

Para entornos de producción permanentes, definitivamente invierte tiempo en montar las herramientas oficiales de ingesta de Elastic correctamente.

Referencias


Escrito por Miguel Ordóñez