Logging leichtgemacht

Docker: zentralisiertes Logging mit dem ELK-Stack
Kommentare

Logausgaben sind immer noch eine wichtige Quelle für Informationen über Anwendungen. Durch Docker entstehen sehr viele Container, die alle Loginformationen schreiben. Dabei geht schnell der Überblick verloren, wenn die Daten in unterschiedlichen Dateien landen. Genau dafür gibt es aber Werkzeuge, die helfen können – beispielsweise den ELK-Stack mit Elasticsearch zum Speichern der Daten, Logstash für die Verarbeitung der Logs und Kibana für die interaktive Analyse.

Das Schreiben von Logdateien ist einfach zu implementieren: Jede Programmiersprache kann Informationen in Dateien oder die Standardausgabe schreiben. Die Informationen können sehr einfach ausgewertet werden. Solange nur die Informationen einzelner Server analysiert werden sollen, sind typische Werkzeuge wie zum Beispiel grep vollkommen ausreichend. Wenn das System aber komplexer wird und über mehrere Server verteilt ist, reicht dieser Ansatz nicht mehr aus. Die Daten liegen auf den Servern verstreut, und man wird sich kaum auf jedem Server einloggen wollen, um die Informationen zusammenzusammeln. Nur mit einer gemeinsamen Sicht auf die Daten aller Server können die Abläufe wirklich nachvollzogen werden. Durch die Nutzung von Docker wird dieses Problem sogar noch größer: Die Teile der Anwendung sind auf Docker-Container verteilt. Da die Container sehr leichtgewichtig sind, gibt es meistens auch deutlich mehr davon als klassische Server. Jeder Container gibt Informationen aus, und alle diese Informationen müssen gesammelt und ausgewertet werden.

Mit dem Befehl docker logs können die Logausgaben eines laufenden Docker-Containers ausgelesen werden. So gibt Docker Zugriff auf die Standardausgabe des Prozesses, der in dem Container läuft, was für einfache Systeme ausreichend ist. Mit diesem Mechanismus verringert Docker die Komplexität gegenüber Lösungen wie syslogd, die auf Ebene des Betriebssystems für die Verarbeitung von Logs bereitstehen. Daten aus der Standardausgabe auslesen ist der einfachste denkbare Ansatz. Aber das Korrelieren von Informationen über verschiedene Docker-Container wird trotzdem schnell zu einer Herausforderung. Außerdem können bei einem Produktionssystem die Docker-Container auf verschiedenen physischen Hosts verteilt werden. Das hätte natürlich zur Folge, dass man sich auf den verschiedenen Hosts einloggen und dort jeweils die Logdateien mit docker logs abholen müsste, was schlicht und ergreifend zu aufwändig wäre.

Hinzu kommt, dass Logdateien nicht nur durchsucht werden sollen. Statistiken sind ebenso nützlich. Viele Informationen über den Zugriff auf Websites stammen letztendlich aus Logdateien. Ebenso sind grafische Auswertungen interessant. Letztendlich können Logdaten die Quelle für das komplette Reporting über die Anwendungen sein.

ELK-Stack als Lösung

Um Logdaten aus verschiedenen Systemen zu konsolidieren, gibt es verschiedene Lösungen. Schon lange am Markt ist Splunk: Eine kommerzielle Lösung, die sich mittlerweile als generelle Lösung für Big-Data-Probleme etabliert hat. Eine Open-Source-Lösung ist Graylog2: Es definiert das GELF-Format (Graylog Extended Log Format) für die Übertragung von Loginformationen im Netz. Metadaten und Konfiguration speichert Graylog2 in MongoDB. Die Logdaten werden in Elasticsearch abgelegt.

Der ELK-Stack ist eine weitere Open-Source-Alternative und geht einen ähnlichen Weg wie Graylog2: Auch in diesem Fall wird Elasticsearch für die Ablage der Daten genutzt. Logdaten sind Text, und Elasticsearch ist für die Volltextsuche sehr gut geeignet. Aber es kann noch mehr: Mit Elasticsearch können strukturierte Daten abgelegt und durchsucht werden. Dazu wird JSON (JavaScript Object Notation) genutzt. Logdaten werden dadurch zu mehr als nur einfach Zeilen in einer Textdatei. Sie können mit Metadaten angereichert werden. Genau dazu dient Logstash: Es kann Logdaten parsen und außerdem aus verschiedenen Quellen wie Dateien, aber auch Messaging-Systemen oder TCP/IP-Ports lesen und schreiben. Im Grunde genommen ist Logstash also eine Technologie, mit der Datenströme gelesen, gefiltert, transformiert und weggeschrieben werden können. Es ist auch möglich, die Events an mehrere Empfänger weiterzuleiten. So wird Logstash zur zentralen Drehschreibe für Logevents. Kibana dient zur Visualisierung von Daten aus Elasticsearch. Darüber hinaus kann mit Kibana Elasticsearch durchsucht werden.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

ELK-Stack mit Docker und Vagrant

Als Beispiel dient eine Java-Anwendung, die in einem Docker-Container installiert wird. Die Logausgaben können dann mit dem ELK-Stack ausgewertet werden. Die Installation des ELK-Stacks ist auch in Docker-Containern verteilt. Das Beispiel kann hier heruntergeladen werden. Die Installation des ELK-Stacks findet sich im Unterverzeichnis log-analysis. In diesem Verzeichnis findet sich auch eine Anleitung in der Datei README.md, wie man das System auf dem eigenen Rechner laufen lassen kann.

Um die verschiedenen Container gemeinsam zu starten und zu installieren, nutzt das Beispiel Vagrant. Dadurch ist es auch sehr einfach, das Beispiel auf dem eigenen Rechner auszuprobieren. Vagrant kann mit verschiedenen Virtualisierungslösungen zusammenarbeiten. Es startet eine virtuelle Maschine und installiert Software mit Werkzeugen wie Chef, Puppet oder eben Docker darauf. Dieses Vorgehen dient dazu, auf einem Laptop oder Desktoprechner ohne großen Aufwand auch komplexe Setups für Tests und Entwicklung aufzubauen. Vor allem wird es zum Aufbau von Entwicklungsumgebungen genutzt.

Vagrant kann von Docker auf zwei verschiedene Arten Gebrauch machen: Zum einen kann es Docker als Ersatz für eine Virtualisierungslösung nutzen. Oder die Installation der Software auf der virtuellen Maschine erfolgt durch Docker-Container. Vagrant orchestriert die verschiedenen Docker-Container und sorgt dafür, dass überhaupt eine passende Infrastruktur aufgebaut wird, in der Docker dann laufen kann. Im Beispiel nutzt Vagrant eine Virtualisierungslösung wie VirtualBox. Im ersten Schritt wird eine virtuelle Maschine mit Linux erzeugt. Auf dieser Maschine installiert Vagrant die verschiedenen Docker-Container. Vagrant stößt dabei nicht nur das Erzeugen der Docker Images an, sondern startet die Docker-Container und installiert darüber hinaus sogar Docker auf der Umgebung, falls notwendig.

Vagrant nutzt dazu eine Ruby-basierende DSL (Domain Specific Language). Listing 1 zeigt das Vagrantfile für ein einfaches ELK-System. Zunächst wird ein Basis-Image für eine VM mit Ubuntu installiert. Dann wird das Verzeichnis mit der Anwendung vom Host in die virtuelle Maschine eingeblendet. Schließlich werden bestimmte Ports der VM an Ports des Hosts gebunden, auf dem die VM läuft. Der Rest des Listings erzeugt mit build_image Docker Images, die dann mit run gestartet werden. Die Dockerfiles liegen im Verzeichnis vagrant. Dieses Verzeichnis liegt eigentlich auf dem Host, auf dem die virtuelle Maschine läuft. Es wird lediglich in das Dateisystem der virtuellen Maschine eingeblendet. So ist ein einfacher Austausch dieser Daten zwischen der virtuellen Maschine und dem Host möglich.

Listing 1 : Vagrantfile für ein einfaches ELK-System

Vagrant.configure("2") do |config|
  config.vm.box = "chef/ubuntu-14.04"
  config.vm.synced_folder "../user-registration-application/target",
   "/target", create: true
  config.vm.network "forwarded_port", guest: 8080, host: 8080
  config.vm.network "forwarded_port", guest: 8081, host: 8081
  config.vm.network "forwarded_port", guest: 9200, host: 9200

  config.vm.provision "docker" do |d|
    d.build_image "--tag=java /vagrant/java"
    d.build_image "--tag=tomcat /vagrant/tomcat"
    d.build_image "--tag=kibana /vagrant/kibana"
    d.build_image "--tag=elasticsearch /vagrant/elasticsearch"
    d.build_image "--tag=logstash /vagrant/logstash"
    d.build_image "--tag=user-registration /vagrant/user-registration"
    d.run "elasticsearch",
      args: "-p 9200:9200"
    d.run "kibana",
      args: "-p 8080:8080"
    d.run "logstash",
      args: "--link elasticsearch:elasticsearch"
    d.run "user-registration",
      args: "-p 8081:8080 -v /target:/target --volumes-from logstash"
  end

end

Um das installierte System besser zu verstehen, zeigt Abbildung 1 einen konzeptionellen Überblick: Der Container user-registration enthält die Beispielanwendung, die im Docker-Container am Port 8080 zur Verfügung steht. Dieser Port wird auf den Port 8081 in der Vagrant-VM gemappt und dann auf den Port 8081 auf dem Host-System. Die Anwendung schreibt Lognachrichten in eine Datei.

Der Container logstash enthält eine Installation der gleichnamigen Software, mit der die Logdateien geparst und bearbeitet werden. Dieser Container stellt das Docker-Data-Volume log zur Verfügung. Ein solches Volume kann von mehreren Docker-Containern genutzt werden. Es wird von der Beispielanwendung als Verzeichnis /log genutzt, sodass die Anwendung dort die Logdaten ablegen kann, die dann von Logstash geparst werden. Im Vagrantfile wird der Docker-Container mit der Anwendung beim Start passend parametrisiert. Natürlich können mehrere Docker-Container sich dieses Volume teilen, sodass verschiedene Logdateien verarbeitet werden können. Allerdings geht der Inhalt des Data Volumes verloren, wenn der Logstash-Container neu aufgebaut wird. Als Lösung kann entweder ein Container dienen, der nur das Data Volume exportiert, oder man kann ein Verzeichnis des Hosts mounten.

In dem Container elasticsearch ist Elasticsearch installiert. Der Container stellt den Port 9200 zur Verfügung, der auch auf dem Host angesprochen werden kann. Außerdem wird er vom Container logstash über einen Docker-Link genutzt. Ein Docker-Link stellt eine Netzwerkverbindung zwischen zwei Docker-Containern her. Dabei wird der TCP/IP-Port freigegeben. Außerdem wird der Container auch unter einem passenden Hostnamen in den verlinkten Containern bekannt gemacht. Auch hier findet sich die notwendige Parametrisierung im Vagrantfile.

Der Container kibana enthält einen Webserver, der HTML-Dateien und JavaScript ausliefert. Er steht auf dem Host unter dem Port 8080 zur Verfügung. So muss mit einem Browser nur den URL http://localhost:8080/ geöffnet werden, um auf die Logdaten zuzugreifen. Kibana ist in JavaScript implementiert und läuft im Browser. Der Browser lädt dann gegebenenfalls Daten aus Elasticsearch über den exportierten Port. Der Container kibana selbst greift auf die Daten aus Elasticsearch nicht zu.

Abb. 1: Aufbau der Docker-Container

Abb. 1: Aufbau der Docker-Container

Mit Docker kann also ein System für die Analyse von Logs in mehrere kleine Docker-Container aufgeteilt werden, die jeweils eine bestimmte Funktion erfüllen. Es zeigt sich, dass die Aufteilung in Docker-Container auch bei Software funktioniert, die nicht speziell für Docker entwickelt worden ist.

Beeindruckend ist auch, wie einfach der Aufbau der notwendigen Automatisierung ist. Konkret sind die folgenden Dateien notwendig:

  • Vagrantfile (25 Zeilen)
  • 6 Dockerfiles (zusammen 22 Zeilen)
  • 1 HTML-Datei (11 Zeilen)
  • 1 Logstash –Konfiguration (29 Zeilen)

Insgesamt sind es also lediglich 87 Zeilen in neun Dateien. Die Installation ist vor allem so einfach, weil die Dockerfiles letztendlich nur Shell-Skripte enthalten, die jeweils einen Container komplett neu aufbauen. Durch die Optimierungen des Docker-Dateisystems ist dieses Vorgehen aber in der Praxis vollkommen ausreichend. Wenn nämlich schon Teile der Installation ausgeführt worden sind, gibt es ein Docker Image mit den entsprechenden Daten, das wiederverwendet wird. Die Installation in diesem speziellen Fall erzeugt ein Image mit einer Java-Installation, die dann von der Logstash-Installation und der Elasticsearch-Installation wiederverwendet werden. Beide Systeme sind in Java geschrieben und benötigen daher eine JVM-Installation.

Listing 2 : Vagrantfile für ein einfaches ELK-System

input {
  file {
    path => ["/log/spring.log"]
    start_position => beginning
  }
}

filter {
  multiline {
    pattern => "((^\s*)[a-z\$\.A-Z\.]*Exception.+)|((^\s*)at .+)"
    what => "previous"
  }
  grok {
    match => [ "message", 
      "^(?[0-9]{4}\-[0-9]{2}\-[0-9]{2})
       %{TIME:time}
       (?:\s*)
       (?[A-Z]+)
       %{NUMBER:pid}
       (?\[.*\])
       (?:\-\-\-)
       (?[0-9a-z\$A-Z\[\]/\.]*)
       (?:\s*:)
       (?(.|\s)+)"]
  }
  kv {}
}

output {
  elasticsearch {
    host => elasticsearch
    index => "logs"
  }
}

Die höchste Komplexität in dem Setup hat die Konfiguration von Logstash (Listing 2): Der erste Teil definiert mit input, woher die Daten kommen: im konkreten Fall aus der Datei /log/spring.log. Diese Datei liegt auf dem Docker Data Volume, das von der Beispielanwendung beschrieben wird.

Der erste Filter sorgt dafür, dass die Ausgaben von Java Exceptions, die sich über mehrere Zeilen erstrecken, zusammengefasst werden. Dazu dient ein regulärer Ausdruck, der definiert, welche Zeilen durch diesen Filter zusammengeführt werden sollen.

Der zweite Filter zerschneidet jede Logzeile in verschiedene Felder, die beispielsweise die Zeit, das Datum oder den Loglevel enthalten. Diese Daten werden in eigene Felder im JSON-Dokument übernommen, das letztendlich in Elasticsearch gespeichert wird.

kv steht für Key/Value. Wenn in einer Logzeile eine Information wie email=eberhard.wolff@gmail.com enthalten ist, reagiert der Filter auf das Gleichheitszeichen und erzeugt in dem JSON-Dokument ein neues Feld mit dem Schlüssel (Key) email und dem Wert (Value) eberhard.wolff@gmail.com. So können sehr einfach Lognachrichten gruppiert werden, da nach solchen Werten auch gefiltert und gesucht werden kann. Beispielsweise können so auch alle Informationen zu einem Request, zu einem Auftrag oder zu einem Kunden zusammengefasst werden. Diese Gruppierungen können für Statistiken genutzt werden oder um die Statistiken zu erstellen.

Schließlich werden die Daten in Elasticsearch gespeichert. Dazu wird der Name des Hosts konfiguriert, auf dem Elasticsearch läuft. Durch den Docker-Link ist unter dem Host-Namen Elasticsearch der richtige Server erreichbar. Docker modifiziert dafür die Datei /etc/hosts im Container, die solche Namen auf IP-Adressen übersetzt. Also ist der Host nur scheinbar fest kodiert – in Wirklichkeit ist das Setup flexibel, weil Docker diese Verbindung verwaltet. Gleichzeitig ist die Installation sehr einfach, weil der Hostname nicht konfiguriert werden muss.

Abb. 2: Ansicht aus Kibana

Abb. 2: Ansicht aus Kibana

Mit Kibana können die Daten durchsucht oder auch grafische Auswertungen erzeugt werden, wie in Abbildung 2 dargestellt. Wer Lust hat, es selber auszuprobieren: Dieser Link enthält das Beispiel. Im dortigen Unterverzeichnis log-analysis findet sich die Installationsanleitung. Dann muss man nur noch http://localhost:8081/ im Browser öffnen, nachdem man mit der Anwendung unter http://localhost:8080/ herumgespielt hat, um Logausgaben zu erzeugen.

Abb. 3: Fehleranalyse mit  Kibana

Abb. 3: Fehleranalyse mit Kibana

Ebenso ist es sehr einfach, Fehler zu analysieren (Abb. 3): Mit einem Filter auf dem Log-Level-Error erscheinen nur noch die Lognachrichten, die mit einem Fehler in Verbindung stehen. Anschließend kann die Fehlernachricht analysiert werden – im Beispiel ein SQL-Fehler. Der lässt sich in der Beispielanwendung recht einfach erzeugen, indem man einen Namen oder Vornamen eingibt, der länger als 30 Zeichen ist.

Natürlich kann dieses Setup auch erweitert werden – beispielsweise können Ausgaben von docker log ebenfalls in das System geschrieben werden. Dadurch loggen auch bei einem komplexen System alle beteiligten Komponenten ihre Daten in das Setup. Das wird beispielsweise erreicht, indem Logstash aus einem gemeinsamen Verzeichnis nicht nur Dateien von Docker-Containern, sondern auch Dateien aus dem Host ausliest – oder die Daten werden über eine Middleware oder einen einfachen TCP/IP-Port weitergegeben. Logstash bietet Unterstützung für sehr viele Technologien in diesem Bereich. Ebenso können natürlich weitere Docker-Container in dasselbe Data Volume loggen, um so ebenfalls ihre Logdaten für eine Auswertung verfügbar zu machen.

Herausforderungen des Setups

Das skizzierte Setup hat allerdings einige Herausforderungen:

  • Parsing belastet die CPU, daher kann es sinnvoll sein, das Parsen vom Schreiben der Lognachrichten zu trennen, um so die Last der Systeme besser zu managen.
  • Wenn sehr viele Daten anfallen, kann das Schreiben und das Parsen ein Engpass werden. Diese Probleme kann ein erweitertes Setup lösen, wie Abbildung 4 zeigt:
  • Logstash wird in einer vereinfachten Konfiguration als Shipper genutzt. Logstash muss dann nur noch die Daten aus dem Data Volume lesen und weiterreichen. Für diese Aufgabe ist nur noch I/O notwendig und keine CPU. Sollen weitere Docker-Container angebunden werden, können weitere Logstash Shipper konfiguriert werden. In produktiven Systemen kann statt dieser Java-basierten Lösung beispielsweise auch Beaver, Woodchuck oder logstash-forwarder genutzt werden. Diese Systeme können die Logs nur weiterreichen und nicht verarbeiten, benötigen aber wesentlich weniger Ressourcen. So nutzt beispielsweise das System aus dem Artikel „Continuous Deployment mit Docker“ auf Seite 63 den logstash-forwarder.
  • Um die Daten zu buffern, wird Redis genutzt. Das ist eine sehr schnelle NoSQL-Datenbank, die Daten im Speicher aber auch auf Festplatte ablegt. Zusammen mit Logstash kann sie entweder als Datenbank verwendet werden, oder die eingebauten Möglichkeiten für das Verschicken von Nachrichten werden genutzt. Durch den Buffer kann das System auch Lastspitzen abfangen.
  • Eine zweite Installation von Logstash parst die Daten und schreibt sie in Elasticsearch. Diese Aufgabe ist eher CPU-intensiv. Sie kann durch die Trennung in einen eigenen Docker-Container geeignet behandelt werden. Ein Vorteil dieses Setups ist außerdem, dass Regeln zum Parsen der Daten nur an einer Stelle konfiguriert sind.

Dieses Setup kann also als Vorlage für leistungsfähigere ELK-Installationen dienen, die mehrere Docker-Container bedienen. Im Beispiel findet sich dieses Setup im Unterverzeichnis log-analysis-redis. Es kann ebenfalls sehr einfach mit Vagrant gestartet werden.

Abb. 4: Setup mit Redis als Buffer und zwei Logstash-Installationen

Abb. 4: Setup mit Redis als Buffer und zwei Logstash-Installationen

Fazit

Komplexere Docker-Setups benötigen mehr als nur die Möglichkeit, Logdateien auszulesen. In einem Docker-System den Logs nur mit grep zu Leibe zu rücken, ist kaum möglich. Lösungen wie der ELK-Stack sind ideal dazu geeignet, ein zentralisiertes Logging mit Auswertungen und Suchmöglichkeiten aufzusetzen. Dank Docker ist es nicht sehr kompliziert, ein solches Setup zu erstellen und die Installation zu automatisieren. Das gilt sogar, wenn das Setup um einen Buffer mit Redis ergänzt wird.

Entsprechend benötigen Docker-Umgebungen leistungsfähige Werkzeuge für die Analyse von Logs. Aber gleichzeitig macht Docker die Installation dieser Werkzeuge auch wesentlich einfacher.

Übrigens gilt das auch für Monitoring. Das Beispielprojekt enthält auch eine Installation von Graphite, mit der die Anwendung überwacht werden kann. So entsteht eine auf Docker basierende Infrastruktur, die den Produktionsaspekt von Anwendungen weitgehend abdeckt. [1] erläutert den Aufbau des Graphite-Beispiels im Detail.

Links & Literatur

[1] Eberhard Wolff: Continuous Delivery – Der pragmatische Einstieg, dpunkt, 2014, ISBN 978-3864902086


Inside Docker: Wenn Sie mehr über Docker wissen möchten, empfehlen wir Ihnen das Entwickler Magazin Spezial Vol. 2: Docker zum leichten Einstieg in die Container-Virtualisierung.

docker-coverMit Docker feiern Linux-Container momentan ein eindrucksvolles Comeback. Während der Einsatz von virtuellen Maschinen viele Vor-, aber auch zahlreiche Nachteile mit sich bringt, ist Docker eine leichtgewichtige, containerbasierte Alternative, die die System-Level-Virtualisierung auf ein neues Level hebt. Dabei ergänzt Docker das Deployment von Betriebssystemen und Webanwendungen um die Lösungen, die man beim Original schmerzlich vermisst. In diesem Jahr hat Docker eine hohe Dynamik entwickelt und wird in allen aktuellen Linux-Distributionen wie Redhat, SUSE oder Ubuntu ausgeliefert. Firmen wie Spotify, Google, BBC, eBay und seit kurzem auch Zalando setzen Docker bereits produktiv ein. Das Entwickler Magazin Spezial „Docker“ informiert kompetent über diese revolutionäre Technologie, von der viele meinen, dass sie eine neue Ära in der IT einläuten wird. Wir erklären technische Hintergründe, demonstrieren Best Practices und zeigen, wie Docker effektiv eingesetzt werden kann. Das Sonderheft vereint umfangreiches Wissen über die wichtigsten Aspekte von Docker, spannende Ideen für eigene Docker-Projekte und wertvolle Impulse für ihre strategische Planung.

 

Aufmacherbild: industrial port with containers Foto via Shutterstock / Urheberrecht: hxdyl

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -