Engenharia

Monitoramento de aplicações em Java com Metricbeat e Jolokia

A máquina virtual Java (JVM) conta com um framework completo de gerenciamento e monitoramento operacional. Nesta postagem veremos o que é JMX (Java Management eXtensions), como explorar as informações que ele expõe e como aproveitá-la com o Jolokia e o Elastic Stack. Se você conhece o JMX e o Jolokia, pode pular a primeira parte e ir direto para a parte sobre os recursos do Metricbeat.

JMX e JConsole

JMX é uma tecnologia que define uma arquitetura completa e um conjunto de padrões de desenho para monitorar e gerenciar aplicações em Java. É baseada em beans gerenciados, mais conhecidos como MBeans, classes que são instanciadas por injeção de dependência que representam recursos em uma JVM. Essas representações podem ser usadas para gerenciar certos aspectos da aplicação ou, mais frequentemente, para coletar estatísticas sobre o uso desses recursos. No centro do JMX está o MBean Server, um elemento que age como intermediário entre os MBeans, as aplicações na mesma JVM e o mundo externo. Qualquer interação com MBeans é feita através desse servidor. Em geral, somente o código Java pode acessar diretamente a API do JMX, mas existem adaptadores que traduzem essa API para protocolos padrão, por exemplo, o Jolokia, que o traduz para HTTP.

Uma ferramenta útil para se trabalhar com o JMX é o JConsole, que está incluído nas distribuições usuais do Java Runtime. Quando você o abre, ele recebe você com uma lista de processos em Java que estão executando na sua máquina. Se não houver nenhum, você verá, pelo menos, o JConsole.

JConsole welcome screen

Ao se conectar a qualquer um desses processos, uma janela se abre com diferentes guias com um monitoramento genérico de informações sobre diferentes aspectos dos processos como memória ou threads. Também se pode ver uma guia com um navegador MBeans.

JConsole main window

No navegador, você pode encontrar a lista de MBeans do processo agrupados por espaços de nomes. Alguns deles, como os que estão sob o espaço de nome java.lang, podem ser encontrados em qualquer JVM, enquanto outros são específicos àquela aplicação. Para cada MBean, você poderá ver diferentes categorias para atributos, operações e notificações. Para monitoramento, focaremos em atributos. Existem alguns MBeans que, apesar de serem diferentes, implementam a mesma interface. Por exemplo, é comum descobrir que aplicações diferentes como Tomcat ou Kafka usam Garbage Collectors diferentes dependendo do uso, mas no JMX, existem objetos do mesmo tipo, só que com nomes diferentes.

MBean browser in JConsole

Conseguir descobrir essas informações é muito bom, mas, na hora de monitorar a infraestrutura, algo como o JConsole pode não estar disponível. Além disso, você pode precisar agregar informações de diferentes processos que podem estar em servidores diferentes. Felizmente, isso pode ser feito pelo Metricbeat e pelo Jolokia.

Como coletar métricas de JMX com o Metricbeat e o Jolokia

O Metricbeat pode coletar informações de diferentes servidores, enviá-las para a Elasticsearch e, a partir de lá, elas podem ser visualizadas em uma série de formas com o Kibana. Mas, como já vimos, o JMX só pode ser consumido com as aplicações em Java, e é aí que o Jolokia entra em campo. O Jolokia é um agente que pode ser implantado em JVMs para expor seus MBeans através de um endpoint HTTP REST-like, tornando toda essa informação disponível de forma fácil para aplicações não Java no mesmo host. Ele pode ser empregado como um agente de JVM comum, como um WAR para ambientes Java EE ou como agente OSGI ou Mule.

Para implantar o Jolokia como um agente, deve-se usar o flag -javaagent na hora de executar a aplicação Java. Ao usar o comando Java diretamente, ele pode ser passado como um argumento, mas algumas aplicações podem ter seus próprios scripts de inicialização e pode ser que sua documentação recomende outras formas de fazer isso. Por exemplo, ao usar o Kafka com seus scripts iniciais, você pode precisar fazer uso da variável de ambiente KAFKA_OPTS:

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

Para implantar o Jolokia como um WAR, o agente precisa estar instalado no servidor Java EE. No Tomcat, por exemplo, isso é feito através da cópia do arquivo WAR para seu diretório de webapps. Para cada aplicação, é recomendado verificar sua documentação para saber a melhor forma de executar agentes Java. Também recomendamos consultar a documentação do Jolokia para ver que agentes estão disponíveis e quais são suas opções. Para casos em que não é possível implantar o Jolokia na mesma JVM, o Jolokia também tem um modo proxy que pode ser usado para consultas JMX de outra JVM.

Uma vez que o agente do Jolokia esteja sendo executado em uma aplicação de JVM, é muito simples coletar métricas com o Metricbeat usando o conjunto de métricas JMX do módulo Jolokia apresentado no Metricbeat 5.4. Esse módulo precisa ser configurado com o host e a porta do agente Jolokia e um conjunto de mapeamentos entre as métricas do JMX e os campos de eventos do Metricbeat. Vamos ver em um exemplo.

Exemplo: Monitoramento de uma aplicação em Java com Metricbeat e Jolokia

Digamos que temos o Jolokia escutando em um localhost, porta 8778, como seria o caso do exemplo anterior com o Kafka. Podemos usar o JConsole para procurar os MBeans e atributos que queremos monitorar. Ao selecionar o MBean, podemos ver seu nome, o que podemos copiar diretamente para o arquivo de configuração.

Threading MBean in JConsole

Para esse exemplo, vamos monitorar o número de threads de java.lang:type=Threading MBean e uso de memória de java.lang:type=Memory. A configuração seria assim:

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

O Metricbeat coleta a informação periodicamente e envia os eventos com os quatro valores. Você deve ter notado no JConsole que os atributos de uso de memória não são valores simples como a contagem de threads, são objetos que contém quatro campos cada. O Metricbeat cuida da reestruturação dos dados no evento, para que no final ele contenha 10 campos sob o espaço do nome jolokia.jvm:

Event with the collected jolokia metrics

Configurações mais avançadas

Você pode querer ter seus campos em eventos diferentes. Começando com o Metricbeat 6.3, os mapeamentos do JMX também podem definir como os campos precisam ser agrupados usando a configuração do evento. Dois eventos com o mesmo valor serão agrupados. Por exemplo, a seguinte configuração geraria dois eventos diferentes, um para campos de memória e outro para campos de thread:

  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"

Outro novo recurso introduzido na versão 6.3 é o suporte a wildcards. Isso permite que nós usemos um mapeamento único para diversos MBeans, o que é útil quando uma aplicação contém diversas instâncias do mesmo tipo ou quando o nome específico é desconhecido. O Tomcat, por exemplo, tem múltiplos pools de thread. Poderíamos ampliar nossos mapeamentos anteriores com uma configuração adicional para obter também o número de threads e conexões por pool:

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

Com essa configuração, um novo evento é enviado para cada mbean correspondente, contendo o nome do mbean como um novo campo:

Event with the collected Jolokia metrics when using wildcards

Para completar o suporte ao Jolokia, no Metricbeat 6.4, nós adicionaremos o modo proxy graças a uma contribuição da comunidade, além de uma implementação do Jolokia Discovery, que permitirá o monitoramento das aplicações em Java em ambientes mais dinâmicos.

O Jolokia Discovery é uma tecnologia baseada em UDP multicast que permite aos agentes Jolokia anunciar seus endpoints junto com algumas informações adicionais sobre o serviço aos quais eles estão vinculados. Nossa implementação é facilitada pelo framework autodiscover, que já usamos para Kubernetes e Docker e traz recursos completos para configuração dinâmica com base em modelos. Se quiséssemos aproveitar o Jolokia Discovery nos exemplos anteriores, poderíamos fazer algo assim (reduzido para coletar somente a contagem de threads):

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"

Essa configuração consiste em dois modelos, um deles será aplicado a todas as instâncias descobertas pelo Jolokia e o outro será aplicado somente às instâncias do Tomcat. Observe como as configurações nos modelos são as mesmas de antes, mas usando uma variável para o host.

Embora esse recurso tenha sido criado para ser usado com o conjunto de métricas do JMX, ele é aberto a qualquer outro uso criativo com outros módulos ou até mesmo com filebeat. Se você quiser saber mais, dê uma olhada na documentação do módulo Jolokia e autodiscover ou tire suas dúvidas no Discuss.