Como criar pipelines do Logstash de forma a facilitar a manutenção e a reutilização

O Logstash é um pipeline open source de processamento de dados que ingere eventos de uma ou mais entradas, os transforma e depois envia cada evento para uma ou mais saídas. Algumas implementações do Logstash podem ter muitas linhas de código e podem processar eventos de várias fontes de entrada. Para facilitar a manutenção dessas implementações, vou mostrar como aumentar a reutilização do código criando pipelines a partir de componentes modulares.

Motivação

Geralmente, é necessário que o Logstash aplique um subconjunto comum de lógica a eventos de várias fontes de entrada. Isso geralmente é conseguido de uma das seguintes maneiras: 

  1. Processar eventos de diversas fontes de entrada diferentes em um único pipeline para que a lógica comum possa ser aplicada facilmente a todos os eventos de todas as fontes. Nessas implementações, além da lógica comum, geralmente há uma quantidade significativa de lógica condicional. Portanto, essa abordagem pode resultar em implementações do Logstash complicadas e difíceis de entender.
  2. Executar um pipeline exclusivo para processar eventos de cada fonte de entrada exclusiva. Para essa abordagem, é necessário duplicar e copiar funcionalidades comuns em cada pipeline, o que dificulta a manutenção das partes comuns do código. 

A técnica apresentada neste blog elimina as deficiências das abordagens acima armazenando componentes modulares do pipeline em arquivos diferentes e depois construindo pipelines combinando esses componentes. Essa técnica pode reduzir a complexidade do pipeline e eliminar a duplicação do código.

Construção modular do pipeline

Um arquivo de configuração do Logstash consiste em entradas, filtros e saídas que são executados por um pipeline do Logstash.  Em configurações mais avançadas, é comum ter uma instância do Logstash executando vários pipelines. Por padrão, quando você inicia o Logstash sem argumentos, ele lê um arquivo chamado pipelines.yml e instancia os pipelines especificados. 

As entradas, filtros e saídas do logstash podem ser armazenados em vários arquivos que podem ser selecionados para inclusão em um pipeline por meio da especificação de uma expressão glob. Os arquivos que corresponderem a uma expressão glob serão combinados em ordem alfabética. Como a ordem de execução dos filtros geralmente é importante, pode ser útil incluir identificadores numéricos nos nomes dos arquivos para garantir que os arquivos sejam combinados na ordem desejada.

Definiremos abaixo dois pipelines exclusivos que são uma combinação de diversos componentes modulares do Logstash. Nós armazenamos nossos componentes do Logstash nos seguintes arquivos:

  • Declarações de entrada: 01_in.cfg, 02_in.cfg
  • Declarações de filtro: 01_filter.cfg, 02_filter.cfg, 03_filter.cfg
  • Declarações de saída: 01_out.cfg

Usando expressões glob, definimos os pipelines no arquivo pipelines.yml para conterem os componentes desejados da seguinte maneira: 

- 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"

Na configuração dos pipelines acima, o arquivo 02_filter.cfg está presente nos dois pipelines, o que demonstra como o código comum a ambos os pipelines pode ser definido e mantido em um único arquivo e também ser executado por vários pipelines.

Teste dos pipelines

Nesta seção, fornecemos um exemplo concreto dos arquivos que serão combinados nos pipelines exclusivos definidos no arquivo pipelines.yml acima. Então, executamos o Logstash com esses arquivos e apresentamos a saída gerada. 

Arquivos de configuração

Arquivo de entrada: 01_in.cfg

Esse arquivo define uma entrada que é um gerador. A entrada do gerador foi projetada para testar o Logstash e, nesse caso, gerará um único evento. 

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

Arquivo de entrada: 02_in.cfg

Esse arquivo define uma entrada do Logstash que escuta no stdin.

input { 
  stdin {} 
}

Arquivo de filtro: 01_filter.cfg

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

Arquivo de filtro: 02_filter.cfg

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

Arquivo de filtro: 03_filter.cfg

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

Arquivo de saída: 01_out.cfg

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

Executar o pipeline

A inicialização do Logstash sem nenhuma opção executará o arquivo pipelines.yml que definimos anteriormente. Execute o Logstash da seguinte maneira: 

./bin/logstash

Como o pipeline my-pipeline_1 está executando um gerador para simular um evento de entrada, deveremos ver a saída a seguir assim que a inicialização do Logstash terminar. Isso mostra que o conteúdo de 01_filter.cfg e 02_filter.cfg é executado por esse pipeline conforme o esperado.

{ 
       "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 o outro pipeline chamado my-pipeline_2 está aguardando entrada no stdin, ainda não vimos nenhum evento processado por esse pipeline. Digite algo no terminal no qual o Logstash está sendo executado e pressione Return para criar um evento para esse pipeline. Depois de fazer isso, você deverá ver algo assim: 

{ 
    "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 acima que a lógica de 02_filter.cfg e 03_filter.cfg é aplicada conforme o esperado.  

Ordem de execução

Esteja ciente de que o Logstash não presta atenção à ordem dos arquivos na expressão glob. Ele usa apenas a expressão glob para determinar quais arquivos incluir e os ordena alfabeticamente. Ou seja, mesmo se alterássemos a definição de my-pipeline_2 para que 03_filter.cfg aparecesse na expressão glob antes de 02_filter.cfg, cada evento passaria pelo filtro em 02_filter.cfg antes do filtro definido em 03_filter.cfg.

Conclusão

O uso de expressões glob permite que os pipelines do Logstash sejam formados com componentes modulares, que são armazenados como arquivos individuais. Isso pode melhorar a manutenção, a reutilização e a legibilidade do código. 

Apenas a título de observação, além da técnica documentada neste blog, a comunicação de um pipeline a outro também deve ser considerada para avaliar se ela pode melhorar a modularidade da implementação do Logstash.