Engineering

Erstellen gut pflegbarer und wiederverwendbarer Logstash-Pipelines

Logstash ist eine Open-Source-Pipeline zur Datenverarbeitung, die Ereignisdaten aus einer oder mehreren Quellen (Inputs) ingestiert und transformiert und das Ergebnis dann an einen oder mehrere Zielorte (Outputs) sendet. Es gibt Logstash-Implementierungen mit einer großen Menge von Codezeilen, die Ereignisdaten aus mehreren Quellen verarbeiten. Ich werde Ihnen zeigen, wie Sie mithilfe von Pipelines für modulare Komponenten die Wiederverwendbarkeit von Code verbessern können, damit sich solche Implementierungen leichter pflegen lassen.

Motivation

Bei Logstash kommt es häufig vor, dass auf Ereignisdaten aus mehreren Quellen eine bestimmte Menge gemeinsamer Logik angewendet werden muss. Dies lässt sich in der Regel auf eine der beiden folgenden Arten erledigen: 

  1. Es gibt eine gemeinsame Pipeline, in der die Ereignisdaten aus allen Quellen zusammengefasst werden. Das macht es einfach, die gemeinsame Logik auf alle Ereignisdaten aus allen Quellen anzuwenden. Derartige Implementierungen weisen zusätzlich zur gemeinsamen Logik in der Regel eine beträchtliche Menge konditionaler Logik auf. Diese Herangehensweise kann daher dazu führen, dass die resultierenden Logstash-Implementierungen kompliziert und schwer durchschaubar sind.
  2. Es gibt für jede Quelle eine eigene Pipeline zur Verarbeitung der Ereignisdaten aus genau dieser Quelle. Bei dieser Methode müssen die für alle Daten benötigten Funktionen dupliziert und in jede einzelne Pipeline kopiert werden, was den Aufwand für die Pflege der gemeinsamen Teile des Codes erhöht. 

Das hier beschriebene Verfahren hat zum Ziel, den Mängeln der beiden oben aufgeführten Verfahren entgegenzuwirken, indem wir modulare Komponenten in unterschiedlichen Dateien speichern und dann diese Komponenten zu Pipelines kombinieren. So lässt sich die Komplexität der Pipelines reduzieren und es muss kein Code mehr dupliziert werden.

Aufbau einer modularen Pipeline

Eine Logstash-Konfigurationsdatei besteht aus Inputs, Filtern und Outputs, die von einer Logstash-Pipeline ausgeführt werden.  In komplexeren Setups übernimmt eine Logstash-Instanz die Ausführung mehrerer Pipelines. Wenn man Logstash ohne Argumente startet, liest diese Instanz standardmäßig eine Datei namens pipelines.yml und instanziiert die angegebenen Pipelines. 

Logstash-Inputs, ‑Filter und ‑Outputs können in mehreren Dateien gespeichert und über einen Glob-Ausdruck für die Aufnahme in eine Pipeline ausgewählt werden. Die Dateien, die mit dem Glob-Ausdruck übereinstimmen, werden dann in alphabetischer Reihenfolge miteinander kombiniert. Da es bei der Ausführung von Filtern häufig auf die Reihenfolge ankommt, kann es sinnvoll sein, in den Dateinamen Nummerierungen zu verwenden, damit die Dateien in der gewünschten Reihenfolge kombiniert werden.

Im Folgenden definieren wir zwei getrennte Pipelines, in denen mehrere modulare Logstash-Komponenten miteinander kombiniert sind. Wir speichern unsere Logstash-Komponenten in den folgenden Dateien:

  • Input-Deklarationen: 01_in.cfg, 02_in.cfg
  • Filter-Deklarationen: 01_filter.cfg, 02_filter.cfg, 03_filter.cfg
  • Output-Deklarationen: 01_out.cfg

Anschließend nutzen wir Glob-Ausdrücke, um in pipelines.yml Pipelines zu definieren, die aus den gewünschten Komponenten bestehen: 

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

In der oben beschriebenen Pipeline-Konfiguration gibt es in beiden Pipelines die Datei 02_filter.cfg. Sie zeigt, wie der in beiden Pipelines vorhandene Code in einer gemeinsamen Datei definiert und verwaltet und von mehreren Pipelines ausgeführt werden kann.

Testen der Pipelines

In diesem Abschnitt stellen wir ein konkretes Beispiel für die Dateien in den Pipelines vor, die in der oben erwähnten Datei pipelines.yml definiert sind. Anschließend führen wir Logstash mit diesen Dateien aus und zeigen, was Logstash für einen Output generiert. 

Konfigurationsdateien

Input-Datei: 01_in.cfg

Diese Datei definiert einen Input, der als Generator fungiert. Der Generator-Input dient zum Testen von Logstash und generiert in unserem Fall ein einziges Ereignis. 

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

Input-Datei: 02_in.cfg

Diese Datei definiert einen Logstash-Input, der den Datenstrom „stdin“ abhört.

input { 
  stdin {} 
}

Filter-Datei: 01_filter.cfg

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

Filter-Datei: 02_filter.cfg

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

Filter-Datei: 03_filter.cfg

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

Output-Datei: 01_out.cfg

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

Ausführen der Pipeline

Wenn Logstash ohne Optionen gestartet wird, wird die zuvor von uns definierte Datei pipelines.yml ausgeführt. Zum Starten von Logstash geben wir Folgendes ein: 

./bin/logstash

Die Pipeline my-pipeline_1 führt zum Simulieren eines Input-Ereignisses einen Generator aus. Daher wird nach der Initialisierung von Logstash Folgendes ausgegeben, was zeigt, dass die Inhalte von 01_filter.cfg und 02_filter.cfg so ausgeführt werden, wie dies von dieser Pipeline erwartet wird:

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

Da die andere Pipeline namens my-pipeline_2 auf stdin auf Input wartet, haben wir bisher noch keine Ereignisse gesehen, die von dieser Pipeline verarbeitet wurden. Wir können jetzt etwas in das Terminal für Logstash eingeben und durch Drücken der Eingabetaste ein Ereignis für diese Pipeline erstellen. Daraufhin dürfte in etwa Folgendes angezeigt werden: 

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

Das zeigt uns, dass die Logik aus 02_filter.cfg und 03_filter.cfg erwartungsgemäß angewendet wird.  

Reihenfolge der Ausführung

Zu beachten ist, dass Logstash die Reihenfolge der Dateien im Glob-Ausdruck komplett egal ist. Der Glob-Ausdruck wird nur verwendet, um zu bestimmen, welche Dateien einbezogen werden sollen. Die Dateien werden dann alphabetisch geordnet. Das bedeutet, dass selbst dann, wenn wir die Definition von my-pipeline_2 so ändern würden, dass 03_filter.cfg im Glob-Ausdruck vor 02_filter.cfg erscheint, jedes einzelne Ereignis vor dem Durchlaufen des in 03_filter.cfg definierten Filters den Filter in 02_filter.cfg passieren muss.

Fazit

Mit Glob-Ausdrücken lassen sich Logstash-Pipelines aus modularen Komponenten zusammensetzen, die als einzelne Dateien gespeichert werden. Das kann die Pflege, Wiederverwendbarkeit und Lesbarkeit von Code erleichtern. 

Nebenbei bemerkt: Bei den Überlegungen dazu, wie sich die Modularität von Logstash-Implementierungen verbessern lässt, lohnt es sich, zusätzlich zum hier beschriebenen Verfahren auch einen Blick auf die Pipeline-zu-Pipeline-Kommunikation zu werfen.