Technique

Comment créer des pipelines Logstash maintenables et réutilisables

Logstash est un pipeline open source destiné au traitement des données. Sa mission ? Ingérer des événements depuis une ou plusieurs entrées, les transformer, puis envoyer chacun d'eux vers une ou plusieurs sorties. Certaines implémentations Logstash peuvent avoir plusieurs lignes de code et peuvent traiter les événements depuis différentes sources d'entrée. Afin de faciliter la gestion de telles implémentations, je vais vous montrer comment créer des pipelines à partir de composants modulaires, ce qui rend le code plus facilement réutilisable.

Motivation

Logstash doit souvent appliquer un sous-ensemble logique commun à des événements provenant de différentes sources d'entrée. Pour ce faire, on adopte généralement l'une de ces deux approches : 

  1. Traiter les événements provenant de différentes sources d'entrée dans un seul pipeline, afin de faciliter l'application d'une logique commune à tous les événements, quelle que soit leur source. Dans de telles implémentations, outre la logique commune, on trouve aussi habituellement un nombre considérable de logiques conditionnelles. Par conséquent, cette approche peut se traduire par des implémentations Logstash complexes et difficiles à comprendre.
  2. Exécuter un pipeline unique pour le traitement des événements provenant de chaque source d'entrée unique. Cette approche nécessite de dupliquer et de copier dans chaque pipeline une fonctionnalité commune, ce qui complique la gestion des parties de code communes. 

La méthode présentée dans cet article de blog répond aux inconvénients des deux approches ci-dessus : elle permet de stocker des composants de pipeline modulaires dans différents fichiers, puis d'associer ces composants pour créer des pipelines. Cette méthode peut vous permettre de simplifier le pipeline et vous éviter la duplication de code.

Création d'un pipeline modulaire

Un fichier de configuration Logstash est composé d'entrées, de filtres et de sorties, qui sont exécutés par un pipeline Logstash.  Dans les configurations avancées, on trouve assez souvent une instance Logstash exécutant plusieurs pipelines. Par défaut, lorsque vous lancez Logstash sans argument, il lit un fichier nommé pipelines.yml et instancie les pipelines spécifiés. 

Vous pouvez stocker les entrées, les filtres et les sorties Logstash dans différents fichiers, que vous pouvez choisir d'inclure dans un pipeline en spécifiant une expression glob. Les fichiers qui correspondent à une expression glob sont alors associés dans l'ordre alphabétique. L'ordre d'exécution des filtres étant souvent important, il peut être utile d'inclure des identifiants numériques dans les noms des fichiers, afin qu'ils soient associés dans l'ordre voulu.

Ci-dessous, nous allons définir deux pipelines uniques, qui sont en fait l'association de plusieurs composants Logstash modulaires. Nous allons stocker nos composants Logstash components dans les fichiers suivants :

  • Déclarations d'entrée : 01_in.cfg, 02_in.cfg
  • Déclarations de filtre : 01_filter.cfg, 02_filter.cfg, 03_filter.cfg
  • Déclarations de sortie : 01_out.cfg

Au moyen d'expressions glob, nous allons ensuite définir les pipelines dans pipelines.yml, pour qu'ils comprennent les composants voulus, comme suit : 

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

Dans la configuration de pipelines ci-dessus, le fichier 02_filter.cfg est présent dans les deux pipelines. On voit donc que le code commun aux deux pipelines peut être défini et géré dans un seul fichier, tout en s'exécutant sur plusieurs pipelines.

Tester les pipelines

Dans cette partie, nous allons prendre un exemple concret pour illustrer les fichiers qui seront associés dans les pipelines uniques que nous avons définis ci-dessus dans pipelines.yml. Nous lancerons ensuite Logstash avec ces fichiers et verrons le résultat ainsi généré. 

Fichiers de configuration

Fichier d'entrée : 01_in.cfg

L'entrée que définit ce fichier est un générateur. L'entrée générateur sert à tester Logstash et dans cet exemple, elle va générer un seul événement. 

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

Fichier d'entrée : 02_in.cfg

Ce fichier définit une entrée Logstash qui écoute sur stdin .

input { 
  stdin {} 
}

Fichier de filtre : 01_filter.cfg

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

Fichier de filtre : 02_filter.cfg

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

Fichier de filtre : 03_filter.cfg

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

Fichier de sortie : 01_out.cfg

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

Exécuter le pipeline

Lorsqu'on démarre Logstash sans définir aucune option, on exécute le fichier pipelines.yml défini précédemment. Exécuter Logstash comme suit : 

./bin/logstash

Comme le pipeline my-pipeline_1 exécute un générateur pour simuler un événement d'entrée, nous devons voir le résultat suivant aussitôt l'initialisation de Logstash terminée. Nous voyons que ce pipeline exécute les contenus de 01_filter.cfg et 02_filter.cfg comme prévu.

{ 
       "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" 
    ] 
}

Étant donné que l'autre pipeline nommé my-pipeline_2 attend une entrée sur stdin, nous voyons qu'il n'a encore traité aucun événement. Saisissez quelque chose dans le terminal sur lequel s'exécute Logstash et appuyez sur la touche Retour pour créer un événement pour ce pipeline. Une fois cela fait, le résultat qui s'affiche doit ressembler à ceci : 

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

Nous voyons ci-dessus que la logique provenant de 02_filter.cfg et03_filter.cfg est appliquée comme prévu.  

Ordre d'exécution

Il faut savoir que Logstash ne tient pas compte de l'ordre des fichiers de l'expression glob. Il n'utilise l'expression glob que pour identifier les fichiers à inclure, puis les classe dans l'ordre alphabétique. Autrement dit, même si nous modifions la définition de my-pipeline_2 pour que 03_filter.cfg apparaisse avant 02_filter.cfg dans l'expression glob, les événements passeront d'abord par le filtre défini dans 02_filter.cfg avant de passer par celui de 03_filter.cfg.

Pour conclure

L'utilisation d'expressions glob permet de créer des pipelines Logstash à partir de composants modulaires stockés en tant que fichiers distincts. Cela peut faciliter la gestion, la réutilisation et la lisibilité du code. 

Par ailleurs, je vous invite à consulter la page pipeline-to-pipeline communication (communication de pipeline à pipeline), qui peut aussi vous aider à améliorer la modularité de votre implémentation Logstash.