Fluentd Fluentd

Logs aus Dateien, HTTP und Containern sammeln

Konfiguriere die drei häufigsten Eingabequellen: eine Datei auf der Platte, einen HTTP-Endpunkt und jeden Container auf einem Docker-Host.

12 min Fortgeschritten

Die meisten echten Deployments brauchen gleichzeitig mehr als eine Art von Eingabequelle: eine bestehende Anwendung, die in eine Logdatei schreibt, einen Service, der Logs stattdessen per HTTP pushen kann, und jeden Container, der auf einem Host läuft. Diese Anleitung konfiguriert alle drei und baut auf der Source/Filter/Match-Struktur der vorherigen Anleitung auf.

Eine Logdatei verfolgen (tail)

Das Input-Plugin tail folgt einer Datei so, wie es tail -f tut, und nimmt neue Zeilen auf, sobald sie geschrieben werden:

<source>
  @type tail
  path /var/log/apache2/access.log
  pos_file /var/log/fluent/apache-access.log.pos
  tag apache.access
  <parse>
    @type apache2
  </parse>
</source>

pos_file solltest du ernst nehmen, auch wenn man es leicht auslässt: Dort hält Fluentd fest, wie weit in die Datei es bereits gelesen hat. Ohne es führt ein Neustart von Fluentd dazu, die gesamte Datei von vorn neu zu lesen und alles bereits Gesendete zu duplizieren. Mit ihm macht ein Neustart genau da weiter, wo er aufgehört hat. Die Dokumentation des Plugins empfiehlt es aus genau diesem Grund ausdrücklich.

Logs über HTTP annehmen

Nicht alles kann Fluentd neben sich installiert haben. Ein verwalteter Cloud-Service, eine Serverless-Funktion, alles außerhalb einer Infrastruktur, die du auf OS-Ebene kontrollierst, kann keine tail-Quelle neben einer Datei betreiben, in die es schreibt. Für diese lässt das http-Input-Plugin etwas Logs an Fluentd senden, statt dass Fluentd daraus liest:

<source>
  @type http
  port 9880
  bind 0.0.0.0
  body_size_limit 32m
  keepalive_timeout 10s
</source>

Alles, was einen HTTP-POST-Request senden kann – eine eigene Anwendung, eine dorthin weiterleitende Logging-Bibliothek, ein curl-Aufruf in einem Skript –, kann jetzt Logdaten direkt liefern. body_size_limit und keepalive_timeout solltest du bewusst setzen statt bei dem zu belassen, was deine Version als Standard mitbringt, denn eine HTTP-Quelle ist dem Traffic ausgesetzt, der sie erreicht, und unbegrenzte Request-Größen oder offen gehaltene Verbindungen willst du selbst kontrollieren und nicht dem Standardverhalten überlassen.

Die Logs jedes Containers auf einem Host sammeln

Docker (und Container-Runtimes generell) schreibt stdout/stderr jedes Containers in JSON-formatierte Logdateien auf dem Host, ein Verzeichnis pro Container. Eine tail-Quelle mit Wildcard auf alle zu richten genügt, um die Ausgabe jedes Containers zu sammeln, ohne jeden einzeln zu konfigurieren:

<source>
  @type tail
  path /fluentd/log/containers/*/*-json.log
  pos_file /var/log/fluent/docker.log.pos
  tag docker.*
  read_from_head true
  <parse>
    @type json
  </parse>
</source>

Damit das funktioniert, muss das Docker-Log-Verzeichnis des Hosts (standardmäßig /var/lib/docker/containers) tatsächlich dorthin gemountet werden, wo Fluentd läuft, und zwar auf den Pfad, auf den das path-Muster der Quelle zeigt. Genau so funktioniert Fluentd auch als Kubernetes-DaemonSet: ein Fluentd-Pod pro Knoten, der jeweils das eigene Container-Log-Verzeichnis seines Knotens mountet, sodass im ganzen Cluster die Container jedes Knotens auf dieselbe Weise ohne Konfiguration pro Container gesammelt werden.

read_from_head true weist Fluentd an, bei Dateien, die es noch nicht gesehen hat, am Anfang zu beginnen. Das ist wichtig für Container, die schon liefen (und schon loggten), bevor diese Quelle konfiguriert wurde. Ohne es nimmt Fluentd nur neue Zeilen ab dem Moment auf, an dem es eine Datei zu beobachten beginnt.

Mit Kontext anreichern: welcher Container, welcher Host

Rohe Container-Logzeilen sagen von sich aus nicht, von welchem Pod, Knoten oder Container sie stammen. Dieser Kontext muss angehängt werden, meist mit einem filter des Plugins record_transformer:

<filter docker.**>
  @type record_transformer
  <record>
    hostname "#{Socket.gethostname}"
  </record>
</filter>

Das hängt den Namen des aktuellen Hosts an jeden unter docker. getaggten Eintrag. Damit kannst du später erkennen, von welchem von mehreren Replikas hinter einem Load Balancer oder in einem Cluster eine bestimmte Logzeile tatsächlich stammt. Dasselbe Muster erweitert sich auf Pod-Name, Namespace oder beliebige andere Metadaten, die du besser bei der Sammlung anhängst, statt später zu versuchen, sie zu rekonstruieren.

Jede Quelle einzeln prüfen

Bevor du echte Ausgabeziele anschließt, richte jede Quelle auf ein einfaches @type stdout-Match und bestätige, dass sie die erwartete Struktur erzeugt:

<match apache.access>
  @type stdout
</match>

Erzeuge eine Anfrage (oder schreibe eine Zeile in die Datei, sende einen POST an den HTTP-Endpunkt oder starte einen Container) und beobachte, wie die geparste, getaggte Ausgabe erscheint. Jede Quelle isoliert zu bestätigen, bevor du Filter oder echte Ziele darüberlegst, macht es viel leichter zu erkennen, welche Stufe ein Problem eingeführt hat, falls später in der Pipeline etwas nicht richtig aussieht.

Beliebte Anleitungen