Engineering

Überwachung von Java-Anwendungen mit Metricbeat und Jolokia

Die Java Virtual Machine (JVM) bietet ein komplettes Framework für das operative Management und die Überwachung. In diesem Post werfen wir einen Blick auf die JMX (Java Management eXtensions) und sehen uns an, wie Sie die mit JMX gewonnenen Informationen erkunden und dann mit Jolokia und dem Elastic Stack für Ihre Zwecke nutzen können. Wenn Sie sich mit JMX und Jolokia bereits auskennen, können Sie den ersten Teil einfach überspringen und direkt mit dem Abschnitt fortfahren, in dem die zugehörigen Metricbeat-Features beschrieben werden.

JMX und JConsole

JMX definiert eine komplette Architektur und einen Satz von Designmustern für die Überwachung und Verwaltung von Java-Anwendungen. Die Technologie stützt sich dabei auf sogenannte MBeans (Managed Beans) – Klassen, die durch Abhängigkeitsinjektion instanziiert werden und Ressourcen in einer JVM repräsentieren. Mit diesen MBeans lassen sich bestimmte Aspekte der Anwendung verwalten, vor allem können sie aber zur Erfassung statistischer Informationen über die Nutzung dieser Ressourcen eingesetzt werden. Kernstück von JMX ist der MBean Server, der als Mittler zwischen den MBeans, den Anwendungen in derselben JVM und der Außenwelt fungiert. Alle Interaktionen mit den MBeans erfolgen über diesen Server. Prinzipiell kann nur Java-Code direkt auf die JMX-API zugreifen, aber es gibt Adaptoren, die diese API in Standardprotokolle übersetzen. So dient zum Beispiel Jolokia zur Übersetzung der API in HTTP.

Als nützliches Tool für die Arbeit mit JMX hat sich JConsole erwiesen, das Bestandteil der normalen Java Runtime-Distributionen ist. Beim Öffnen von JConsole wird Ihnen die Liste der auf Ihrer Maschine ausgeführten Java-Prozesse angezeigt. Sind noch keine Prozesse vorhanden, ist zumindest JConsole selbst zu sehen.

JConsole-Begrüßungsbildschirm

Nach Herstellen einer Verbindung zu einem dieser Prozesse wird ein Fenster mit mehreren Tabs geöffnet, die generische Überwachungsinformationen zu verschiedenen Aspekten der Prozesse bereitstellen, wie Arbeitsspeicher oder Threads. Zudem gibt es auch einen Tab mit einem MBeans-Browser.

JConsole-Hauptfenster

Im Browser werden, nach Namespaces geordnet, die MBeans des Prozesses aufgeführt. Einige von ihnen, zum Beispiel die unter dem Namespace „java.lang“, sind in allen JVMs vorhanden, während andere anwendungsspezifisch sind. Für jede MBean werden eigene Attributs-, Operations- und Benachrichtigungskategorien angezeigt. Für die Überwachung konzentrieren wir uns auf Attribute. Es gibt MBeans, die trotz abweichender Eigenschaften dasselbe Interface implementieren. So nutzen beispielsweise unterschiedliche Anwendungen wie Tomcat oder Kafka in der Regel unterschiedliche Garbage Collectors – je nach Einsatzgebiet. In JMX sind sie jedoch Objekte desselben Typs, die sich lediglich im Namen unterscheiden.

MBean-Browser in JConsole

Es hilft natürlich, diese Informationen präsentiert zu bekommen, aber bei der Überwachung von Infrastruktur sind Tools wie JConsole in der Regel nicht verfügbar. Hinzu kommt, dass es gelegentlich notwendig ist, auch Informationen aus unterschiedlichen Prozessen zu aggregieren, die sich auf unterschiedlichen Servern befinden. Glücklicherweise gibt es dafür eine Lösung: Metricbeat und Jolokia.

Erfassung von JMX-Metriken mit Metricbeat und Jolokia --------------------------------------------------

Metricbeat kann Informationen von unterschiedlichen Servern erfassen und diese an Elasticsearch senden, von wo aus sie dank Kibana auf verschiedenste Art und Weise visualisiert werden können. Wie wir jedoch bereits gesehen haben, wird JMX nur von Java-Anwendungen unterstützt. Hier kommt Jolokia ins Spiel.

Jolokia ist ein Agent, der auf JVMs bereitgestellt wird und über einen REST-artigen HTTP-Endpunkt deren MBeans zugänglich machen kann, sodass sie von Java-fremden Anwendungen auf demselben Host genutzt werden können. Die Bereitstellung kann als normaler JVM-Agent, als WAR für Java EE-Umgebungen oder als OSGI- oder Mule-Agent erfolgen.

Wenn Jolokia als Agent bereitgestellt werden soll, muss beim Starten der Java-Anwendung das Flag „-javaagent“ verwendet werden. Bei direkter Verwendung des Java-Befehls ist die Übergabe als Argument möglich, aber bei Anwendungen mit eigenen Startup-Scripts kann es sein, dass in deren Dokumentation auch andere Bereitstellungsvarianten vorgeschlagen werden. Wenn Sie beispielsweise Kafka mit den Kafka-eigenen Startup-Scripts verwenden, müssen Sie möglicherweise die Umgebungsvariable „KAFKA_OPTS“ verwenden:

export KAFKA_OPTS=-javaagent:/opt/jolokia-jvm-1.5.0-agent.jar=port=8778,host=localhost
./bin/kafka-server-start.sh ./config/server.properties

Soll Jolokia als WAR bereitgestellt werden, muss der Agent im Java EE-Server installiert sein. In Tomcat, zum Beispiel, wird dazu die WAR-Datei in das entsprechende „webapps“-Verzeichnis kopiert. Um die jeweils beste Möglichkeit für die Ausführung von Java-Agents in einer konkreten Anwendung herauszufinden, sollten Sie einen Blick in die entsprechende Dokumentation werfen. Außerdem empfiehlt es sich, die Jolokia-Dokumentation zu studieren, um einen Überblick über die verfügbaren Agents und deren Optionen zu erhalten. Sollte die Bereitstellung von Jolokia in derselben JVM nicht möglich sein, können Sie über den Proxymodus von Jolokia auch JMX-Abfragen von einer anderen JVM aus starten.

Sobald der Jolokia-Agent in der JVM einer Anwendung erst einmal läuft, ist es dank des JMX-Metricsets des Jolokia-Moduls, der in Metricbeat 5.4 eingeführt wurde, problemlos möglich, JMX-Metriken zu erfassen. Dieses Modul muss mit dem Host und Port des Jolokia-Agents und einem Satz von Zuordnungen zwischen den JMX-Metriken und Metricbeat-Ereignisfeldern konfiguriert werden. Sehen wir uns dazu ein Beispiel an.

Beispiel: Überwachung einer Java-Anwendung mit Metricbeat und Jolokia ------------------------------------------------------------------ Nehmen wir an, Jolokia hört localhost, Port 8778, ab, wie das beim Beispiel oben mit Kafka der Fall wäre. Mit JConsole können wir nach den MBeans und Attributen suchen, die wir überwachen möchten. Wenn wir die MBean auswählen, wird deren Name angezeigt. Diesen können wir direkt in die Konfigurationsdatei kopieren.

MBean „Threading“ in JConsole

In diesem Beispiel sollen die Zahl der Threads, die von der MBean „java.lang:type=Threading“ ausgehen, sowie die von „java.lang:type=Memory“ ausgehende Heap-Speicher-Nutzung überwacht werden. Dies kann wie folgt konfiguriert werden:

- module: jolokia
  metricsets: ["jmx"]
  hosts: ["localhost:8778"]
  period: 10s
  namespace: "jvm"
  jmx.mappings:
  - mbean: "java.lang:type=Memory"
    attributes:
    - attr: "HeapMemoryUsage"
        field: "memory.heap"
    - attr: "NonHeapMemoryUsage"
        field: "memory.nonheap"
  - mbean: "java.lang:type=Threading"
    attributes:
    - attr: "ThreadCount"
        field: "thread.count"
    - attr: "DaemonThreadCount"
        field: "thread.daemon"

Metricbeat erfasst in regelmäßigen Abständen die Informationen und sendet die Ereignisse mit den vier Werten weiter. Wie Sie vielleicht in JConsole bemerkt haben, sind die Speichernutzungsattribute keine einfachen Werte, wie zum Beispiel die Thread-Zahlen, sondern Objekte, die jeweils vier Felder enthalten. Metricbeat kümmert sich darum, dass die Daten im Ereignis neu strukturiert werden, sodass es letztendlich unter dem Namespace „jolokia.jvm“ zehn Felder enthält:

Ereignis mit den erfassten Jolokia-Metriken

Komplexere Konfigurationen

Es kann vorkommen, dass Sie Ihre Felder in unterschiedlichen Ereignissen haben möchten. Seit Metricbeat 6.3 können JMX-Zuordnungen über die Ereigniseinstellung auch definieren, wie Felder zu gruppieren sind. Zwei Ereignisse mit demselben Wert werden zusammen gruppiert. So würde zum Beispiel die folgende Konfiguration zwei unterschiedliche Ereignisse generieren – eines für Speicherfelder und ein anderes für Thread-Felder:

  jmx.mappings:
  - mbean: "java.lang:type=Memory"
    attributes:
    - attr: "HeapMemoryUsage"
        field: "memory.heap"
        event: "memory"
    - attr: "NonHeapMemoryUsage"
        field: "memory.nonheap"
        event: "memory"
  - mbean: "java.lang:type=Threading"
    attributes:
    - attr: "ThreadCount"
      field: "thread.count"
      event: "threads"
    - attr: "DaemonThreadCount"
      field: "thread.daemon"
      event: "threads"

Zusätzlich werden seit Version 6.3 Platzhalterzeichen unterstützt, sodass ein und dieselbe Zuordnung für mehrere MBeans verwendet werden kann. Das ist sinnvoll, wenn eine Anwendung mehrere Instanzen desselben Typs enthält oder wenn der konkrete Name vorab nicht bekannt ist. So hat zum Beispiel Tomcat mehrere Thread-Pools und um auch die Zahl der Threads und Verbindungen pro Pool angezeigt zu bekommen, können wir unsere vorherigen Zuordnungen mit einer zusätzlichen Konfiguration erweitern:

  - mbean: "Catalina:name=*,type=ThreadPool"
    attributes:
    - attr: "currentThreadCount"
      field: "thread.count"
    - attr: "maxThreads"
      field: "thread.max"
    - attr: "connectionCount"
      field: "connection.count"

Bei dieser Konfiguration wird für jede MBean mit einer Übereinstimmung ein neues Ereignis mit dem Namen der MBean als neuem Feld gesendet:

Ereignis mit den erfassten Jolokia-Metriken bei Verwendung von Platzhaltern

Zur Vervollständigung der Jolokia-Unterstützung werden wir in Metricbeat 6.4 dank eines Beitrags aus der Community einen Proxymodus hinzufügen. Außerdem wird es eine Implementierung von Jolokia Discovery geben, die die Überwachung von Java-Anwendungen in Umgebungen mit einer größeren Dynamik ermöglicht.

Jolokia Discovery ist eine auf UDP-Multicast basierende Technologie, mit der Jolokia-Agents nicht nur Informationen zu dem Dienst, mit dem sie verbunden sind, sondern auch ihre Endpunkte bekanntgeben können. Hinter unserer Implementierung steht das Autodiscover-Framework, das bereits für Kubernetes und Docker eingesetzt wird. Die Implementierung ermöglicht die uneingeschränkte dynamische Konfiguration auf der Grundlage von Vorlagen. Wenn wir Jolokia Discovery für die Beispiele oben nutzen wollten, könnten wir zum Beispiel wie folgt vorgehen (hier nur mit Erfassung der Thread-Zahl):

metricbeat.autodiscover:
  providers:
  - type: jolokia
    templates:
    - condition:
        contains:
          jolokia.agent.version: "1.5.0"
      config:
      - module: "jolokia"
        metricsets: ["jmx"]
        hosts: ["${data.jolokia.url}"]
        namespace: "jvm"
        jmx.mappings: 
        - mbean: "java.lang:type=Threading"
          attributes:
          - attr: "ThreadCount"
            field: "thread.count"
    - condition:
        contains:
          jolokia.server.product: "tomcat"
      config:
      - module: "jolokia"
        metricsets: ["jmx"]
        hosts: ["${data.jolokia.url}"]
        namespace: "jvm"
        jmx.mappings:
        - mbean: "Catalina:name=*,type=ThreadPool"
          attributes:
          - attr: "currentThreadCount"
            field: "thread.count"

Diese Konfiguration besteht aus zwei Vorlagen, von denen eine auf alle gefundenen Jolokia-Instanzen und die andere nur auf Tomcat-Instanzen angewendet wird. Die Konfigurationen in den Vorlagen sind im Wesentlichen dieselben wie zuvor, aber für den Host wird eine Variable verwendet.

Dieses Feature wurde zwar ursprünglich für die Verwendung mit dem JMX-Metricset entwickelt, es kann aber ganz frei mit anderen Modulen oder auch mit Filebeat genutzt werden. Wenn Sie mehr erfahren möchten, sehen Sie sich die Dokumentation für das Jolokia-Modul und für Autodiscover an oder [fragen Sie uns bei Discuss].