Ingeniería

Cómo crear pipelines de Logstash que se puedan mantener y volver a usar

Logstash es un pipeline de procesamiento de datos open source que ingesta eventos de una o más entradas, los transforma y después envía cada evento a una o más salidas. Algunas implementaciones de Logstash pueden tener muchas líneas de código y procesar eventos de varias fuentes de entrada. Para un mejor mantenimiento de tales implementaciones, mostraremos cómo aumentar la capacidad para volver a usar el código creando pipelines a partir de componentes modulares.

Motivación

Suele ser necesario para Logstash aplicar un subconjunto común de lógica a los eventos de varias fuentes de entrada. Esto se logra comúnmente en una de las dos formas siguientes: 

  1. Procesar eventos de varias fuentes de entrada diferentes en un solo pipeline de modo que una lógica común pueda aplicarse con facilidad a todos los eventos de todas las fuentes. En estas implementaciones, además de la lógica común suele haber una cantidad importante de lógica condicional. Este enfoque por lo tanto puede resultar en implementaciones de Logstash complicadas y difíciles de entender.
  2. Ejecutar un pipeline único para procesar eventos de cada fuente única de entrada. Este enfoque requiere la duplicación y copia de la funcionalidad común en cada pipeline, lo que hace que las porciones comunes del código sean difíciles de mantener. 

La técnica que se presenta en este blog aborda las deficiencias de los enfoques anteriores mediante el almacenamiento de componentes modulares del pipeline en distintos archivos y la posterior construcción de pipelines combinando estos componentes. Esta técnica puede reducir la complejidad del pipeline y eliminar la duplicación de código.

Construcción de pipeline modular

Un archivo de configuración de Logstash consiste en entradas, filtros y salidas que ejecuta un pipeline de Logstash.  En configuraciones más avanzadas es habitual tener una instancia de Logstash que ejecuta varios pipelines. De manera predeterminada, cuando inicias Logstash sin argumentos, este leerá un archivo llamado pipelines.yml e instanciará los pipelines especificados. 

Las entradas, filtros y salidas de Logstash pueden almacenarse en varios archivos que se pueden seleccionar para incluirse en un pipeline especificando una expresión glob. Los archivos que coinciden con una expresión glob se combinarán en orden alfabético. Como el orden de ejecución de filtros suele ser importante, puede ser útil incluir identificadores numéricos en los nombres de archivo para garantizar que los archivos se combinen en el orden deseado.

A continuación definiremos dos pipelines únicos que son una combinación de varios componentes de Logstash modulares. Almacenamos nuestros componentes de Logstash en los archivos siguientes:

  • Declaraciones de entrada: 01_in.cfg, 02_in.cfg
  • Declaraciones de filtro: 01_filter.cfg, 02_filter.cfg, 03_filter.cfg
  • Declaraciones de salida: 01_out.cfg

Con expresiones glob, después definimos pipelines en pipelines.yml que se formarán con los componentes deseados como sigue: 

- pipeline.id: my-pipeline_1
  path.config: "<path>/{01_in,01_filter,02_filter,01_out}.cfg"
- pipeline.id: my-pipeline_2
  path.config: "<path>/{02_in,02_filter,03_filter,01_out}.cfg"

En la configuración de pipelines anterior, el archivo 02_filter.cfg está presente en ambos pipelines, lo que demuestra cómo el código que es común a ambos pipelines puede definirse y mantenerse en un solo archivo y también ejecutarse por varios pipelines.

Prueba de los pipelines

En esta sección proporcionamos un ejemplo concreto de los archivos que se combinarán en los pipelines únicos definidos en pipelines.yml arriba. Después ejecutamos Logstash con estos archivos y presentamos la salida generada. 

Archivos de configuración

Archivo de entrada: 01_in.cfg

Este archivo define una entrada que es un generador. La entrada generadora está diseñada para probar Logstash y en este caso generará un solo evento. 

input { 
  generator { 
    lines => ["Generated line"] 
    count => 1 
  } 
}

Archivo de entrada: 02_in.cfg

En este archivo se define una entrada de Logstash que escucha en stdin.

input { 
  stdin {} 
}

Archivo de filtro: 01_filter.cfg

filter { 
  mutate { 
    add_field => { "filter_name" => "Filter 01" } 
  } 
}

Archivo de filtro: 02_filter.cfg

filter { 
  mutate { 
    add_field => { "filter_name" => "Filter 02" } 
  } 
}

Archivo de filtro: 03_filter.cfg

filter { 
  mutate { 
    add_field => { "filter_name" => "Filter 03" } 
  } 
}

Archivo de salida: 01_out.cfg

output { 
  stdout { codec =>  "rubydebug" } 
}

Ejecución del pipeline

Iniciar Logstash sin ninguna opción ejecutará el archivo pipelines.yml que definimos anteriormente. Ejecuta Logstash de la siguiente manera: 

./bin/logstash

Como el pipeline my-pipeline_1 está ejecutando un generador para simular un evento de entrada, deberíamos ver la salida siguiente tan pronto como Logstash haya terminado de inicializarse. Esto muestra que este pipeline ejecuta los contenidos de 01_filter.cfg y 02_filter.cfg como se esperaba.

{ 
       "sequence" => 0, 
           "host" => "alexandersmbp2.lan", 
        "message" => "Generated line", 
     "@timestamp" => 2020-02-05T22:10:09.495Z, 
       "@version" => "1", 
    "filter_name" => [ 
        [0] "Filter 01", 
        [1] "Filter 02" 
    ] 
}

Como el otro pipeline llamado my-pipeline_2 está esperando la entrada sobre stdin, no hemos visto ningún evento procesado por ese pipeline. Escribe algo en la terminal en la que se está ejecutando Logstash y presiona Return (Regresar) para crear un evento para este pipeline. Una vez que lo hayas hecho, deberías ver algo como lo siguiente: 

{ 
    "filter_name" => [ 
        [0] "Filter 02", 
        [1] "Filter 03" 
    ], 
           "host" => "alexandersmbp2.lan", 
        "message" => "I’m testing my-pipeline_2", 
     "@timestamp" => 2020-02-05T22:20:43.250Z, 
       "@version" => "1" 
}

Podemos ver en lo anterior que la lógica de 02_filter.cfg y 03_filter.cfg se aplica como se esperaba.  

Orden de ejecución

Ten en cuenta que Logstash no presta atención al orden de los archivos en la expresión glob. Solo usa la expresión glob para determinar los archivos que debe incluir y después los ordena alfabéticamente. Es decir, incluso si cambiáramos la definición de my-pipeline_2 de modo que 03_filter.cfg aparezca en la expresión glob antes que 02_filter.cfg, cada evento pasaría por el filtro en 02_filter.cfg antes que por el filtro definido en 03_filter.cfg.

Conclusión

Usar expresiones glob permite que los pipelines de Logstash se conformen de componentes modulares, que se almacenan como archivos individuales. Esto puede mejorar la capacidad de mantener, volver a usar y leer el código. 

Además de la técnica documentada en este blog, también debería tenerse en cuenta la comunicación de pipeline a pipeline para ver si puede mejorar la modularidad de implementación de Logstash.