/ Docker

Kubernetes (Multiline) Logs sammeln mit Fluent Bit

Kubernetes ist eine großartige Orchestrierungsplattform, die das Hosting von Microservices vereinfacht. Zu einem erfolgreichen Betrieb von Anwendungen in einem Kubernetes-Cluster gehört unter anderem die Möglichkeit alle anfallenden Logs zu sichern und einfach einsehen zu können, um bei eventuellen Problemen die Fehlerursache schnell finden zu können. Eine zentrale Log-Verwaltung ist dabei ein wichtieges Hilfsmittel um Logs zu persistieren, sortieren und gegenüber stellen zu können. Mit einer meist integrierten, potenten Suchmaschine können logs noch einfacher durchsucht werden, als es mit einfacher Dateisuche möglich sein würde.

In diesem Beitrag wird ein Beispiel präsentiert, in dem alle in einem Kubernetes-Cluster anfallenden logs gesammelt, im Format angeglichen, eventuell mit weiteren Metadaten angereichert und an einen Log-Management-Server weitergereicht werden. Im Cluster laufen dabei auch Anwendungen, die Multiline-Logs generieren.

Dienste in einem Kubernetes Cluster

Auf den Nodes eines Kubernetes Clusters laufen hauptsächlich folgende Dienste:

  • System-Komponenten des Clusters
    • Laufend als Docker-Container
      • Scheduler
      • Kube-Proxy
    • Laufend als normaler Dienst auf der Node
      • Kubelet
      • Cuntainer-Runtime (z.B. Docker)
  • Containerisierte Anwendungen im Cluster
  • Dienste des Betriebssystems

Logdateien

Die liste im vorigen Abschnitt betrachtend kann man zum Schluss kommen, dass man eigentlich nur zwei Arten von Logs sammeln muss: Container- und System-Logs. Container-Logs werden im Falle der Runtime "Docker" unter `/var/log/containers/*` abgelegt. Für die System-Logs kann man zum Beispiel journald abfragen, wenn das Betriebssystem Prozesse mit Systemd startet.

Graylog

Graylog ist ein Open-Source-Logverwaltungssystem. Es kann die Zentrale Komponente eines Log-Stacks bilden. Graylog nimmt Logs entgegen, speichert sie in Elasticsearch-Datenbanken und stellt eine Weboberfläche für die Suchfunktion bereit. Das ist aber noch nicht alles. Graylog ist skalierbar und kann mit steigendem Logaufkommen um weitere Knoten ergänzt werden. Zudem kann er ankommende Logs mit Hilfe sogenannter Extraktoren interpretieren und aus den Log-Nachrichten weitere durchsuchbare Keys extrahieren.

Logsammler

Die bekanntesten Werkzeuge zum Sammeln von Logs sind Filebeat und Fluent Bit. Fluent Bit hat aber einen geringeren Ressourcenverbrauch als Filebeat und kann von Haus aus Logs im Graylog Extended Log Format (GELF) an Graylog übermitteln. Das Tool kann zudem Logs aus mehreren Inputs and mehrere Outputs senden. Die Daten können dabei flexibel geroutet werden und mit Hilfe von eigenen oder vorkonfigurierten Parsern und Filtern kann der Inhalt strukturiert und manipuliert werden.

Fluent Bit kann im Kubernetes Cluster am Einfachsten per Helm installiert werden:

helm repo add fluent https://fluent.github.io/helm-charts
helm repo update
kubectl create namespace logging
helm install -n logging fluent-bit fluent/fluent-bit

Für die Installation benötigt man natürlich die passende Konfiguration, die im Folgenden erarbeitet wird.

Die Konfiguration

Quellen

Wie im zweiten Abschnitt bereits festgestellt, gibt es in einem Kubernetes-Cluster zwei Arten Logs, die es sinnvoll wäre zu sammeln: Container-Logs und Systemd-Logs. Es ist jedoch an der Stelle empfehlenswert noch eine weitere Log-Quelle zu definieren: Container unserer gehosteten Anwendung.

Es ist nämlich noch bis vor zwei Monaten gar nicht möglich gewesen in Container anfallenden Multiline-Logs im Fluent Bit zu erkennen und die einzelnen Zeilen zu einer Log-Nachricht zusammenzuführen. Der Grund war vor allem, dass Docker Log-Zeilen eines Containers als separate JSON-Objekte in Logdateien speichert. Im Juli dieses Jahres wurde eine neue (noch nicht dockumentierte) Option für Fluent-Bit-Inputs hinzugefügt: "Docker_Mode".

Die drei Quellen der zu sammelnder Logs könne wie folgt konfiguriert werden:

[INPUT]
    Name tail
    Path /var/log/containers/*_my_java_apps_*.log
    Tag kube.*
    Mem_Buf_Limit 20MB
    Skip_Long_Lines Off
    # "Docker_Mode" Not yet documented!!! https://github.com/fluent/fluent-bit/pull/2043
    Docker_Mode On
    Docker_Mode_Parser java_multi_line

[INPUT]
    Name tail
    Path /var/log/containers/*.log
    Tag kube.*
    Parser docker
    Exclude_Path /var/log/containers/*_my_java_apps_*.log
    Mem_Buf_Limit 10MB
    Skip_Long_Lines Off

[INPUT]
    Name systemd
    Tag systemd.*
    Read_From_Tail On
    Strip_Underscores On
    Key log
    ```

Parser

Mit einem Parser kann man in Fluent Bit einzelne Felder aus einem "Record" extrahieren. Im Beispiel der Java-Logs wird der Parser benötigt um die erste Zeile in Multiline-Logs zu erkennen.

[PARSER]
    Name java_multi_line
    Format regex
    Regex (?<log>^{"log":"\d{4}-\d{2}-\d{2}\ \d{2}:\d{2}:\d{2}\.\d{3}\ .*)

Filter

Mit Filtern kann man den Inhalt der Logs manipulieren und erweitern. Zweckmässig ist die Erweiterung der Container-Logs um Kubernetes-Metadaten. Zudem muss das vom Systemd-Input vergebene Log-Nachricht-Key "MESSAGE" umgeändert werden in "log", da sonst das GEL-Format nicht generiert werden kann.

[FILTER]
    Name kubernetes
    Match kube.*
    Merge_Log On
    Keep_Log Off
    K8S-Logging.Parser On
    K8S-Logging.Exclude On

[FILTER]
    Name modify
    Match systemd.*
    # Key "log" is needed as Gelf_Short_Message_Key
    Rename MESSAGE log

Ziel

Nun fehlt noch die Daten ins GELF zu übersetzen und an Graylog zu senden. Nitchs leichter als das. In Fluent Bit muss dafür einfach ein "Output" definiert werden:

[OUTPUT]
    Name gelf
    Match *
    Host mgmt-graylog-k8s-1.mgmt.lcscloud
    Port 12201
    Mode tcp
    Gelf_Short_Message_Key log

Die Konfigurationsdatei für Helm

Die Fluent Bit Optionen müssen noch noch in eine Helm-Chart-Konfigurationsdatei übertragen werden. Diese Datei muss dann bei der Installation mit angegeben werden.

---
config:
  inputs: |
    [INPUT]
        Name tail
        Path /var/log/containers/*_my_java_apps_*.log
        Tag kube.*
        Mem_Buf_Limit 20MB
        Skip_Long_Lines Off
        # "Docker_Mode" Not yet documented!!! https://github.com/fluent/fluent-bit/pull/2043
        Docker_Mode On
        Docker_Mode_Parser java_multi_line

    [INPUT]
        Name tail
        Path /var/log/containers/*.log
        Tag kube.*
        Parser docker
        Exclude_Path /var/log/containers/*_my_java_apps_*.log
        Mem_Buf_Limit 10MB
        Skip_Long_Lines Off

    [INPUT]
        Name systemd
        Tag systemd.*
        Read_From_Tail On
        Strip_Underscores On
        Key log

  filters: |
    [FILTER]
        Name kubernetes
        Match kube.*
        Merge_Log On
        Keep_Log Off
        K8S-Logging.Parser On
        K8S-Logging.Exclude On

    [FILTER]
        Name modify
        Match systemd.*
        # Key "log" is needed as Gelf_Short_Message_Key
        Rename MESSAGE log

  outputs: |
    [OUTPUT]
        Name gelf
        Match *
        Host graylog.for.my.cloud
        Port 12201
        Mode tcp
        Gelf_Short_Message_Key log

  customParsers: |
    [PARSER]
        Name java_multi_line
        Format regex
        Regex (?<log>^{"log":"\d{4}-\d{2}-\d{2}\ \d{2}:\d{2}:\d{2}\.\d{3}\ .*)
helm install -n logging fluent-bit fluent/fluent-bit -f fluent-bit-values.yml