Interfaz de monitorización e instrumentalización con JMX en aplicaciones Java

Escrito por el .
java planeta-codigo programacion
Enlace permanente Comentarios

JMX es una forma sencilla e integrada en la plataforma Java de monitorizar e instrumentalizar ciertas operaciones de funcionamiento interno de la aplicación que no tenga que ver con el ámbito de negocio que resuelve sino en el aspecto técnico. Unos casos de uso son activar una característica de la aplicación mientras la aplicación está funcionando o limpiar una cache de modo que los datos que almacena se actualicen de nuevo de la fuente origen en tiempo real y sin necesidad de reniciarla, cualquier otro realizable con código Java es posible.

Java

Las aplicaciones Java tienen a su disposición integradas en la propia plataforma Java una interfaz para monitorizar su estado y realizar acciones de instrumentalización para modificar algún comportamiento o cambiar alguna configuración en tiempo real mientras la aplicación está funcionando sin necesidad de reiniciarla. La especificación que proporciona esta interfaz es Java Management Extensions (JMX).

JMX define una serie de recursos a ser administrados, estos ha de instrumentalizarse con el lenguaje Java definiendo unos objetos MBeans que accedan a los recursos. Una vez el recurso ha sido instrumentalizado es gestionado por una agente JMX. El agente JMX controla los recursos y los hace disponibles a las aplicaciones de gestión, el objeto principal del agente es el MBean server donde los MBean son registrados. Los recursos puede ser accedidos a través de diferentes protocolos mediante adaptadores y conectores. Un adaptador HTML muestra un MBean en el navegador y un conector se encarga de la comunicación entre la la aplicación de gestión y el agente JMX.

La instrumentalización se implementa con los objetos MBean similares a los objetos JavaBean que siguen varios patrones de diseño establecidos por la especificación JMX. Un MBean puede representar un dispositivo, una aplicación o un recurso que necesite ser administrado. Los MBean exponen una interfaz de gestión que consiste en:

  • Un conjunto de propiedades de lectura, escritura o ambas.
  • Un conjunto de operaciones invocables.
  • Una autodescripción.

Además de propiedades y operaciones los MBean también pueden emitir notificaciones cuando ocurren ciertos eventos. Una aplicación práctica de JMX es cambiar de forma dinámica los niveles de log para obtener más información de una funcionalidad o en caso de un error sin necesidad de reiniciar el servidor ni necesidad de hacer cambios en el repositorio de código de la aplicación para simplemente obtener mayor detalle de trazas.

Ejemplo de JMX en una aplicación Java

Un MBean no es más que una interfaz que una clase Java implementa.

1
2
3
4
5
6
7
8
package io.github.picodotdev.blogbitix.jmx.mbean;

public interface HelloMBean {

    void sayHello();
    int add(int x, int y);
    String getName();
}
HelloMBean.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.jmx.mbean;

public class Hello implements HelloMBean {

    public void sayHello() {
        System.out.println("hello, world");
    }

    public int add(int x, int y) {
        return x + y;
    }

    public String getName() {
        return "Reginald";
    }
}
Hello-java.java

Creada la interfaz y la implementación del MBean ha de registrarse en el servidor de MBean. Los MBean se registra en un dominio junto con una serie de propiedades clave/valor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.jmx.java;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import io.github.picodotdev.blogbitix.jmx.mbean.Hello;

public class Main {

    public static void main(String[] args) throws Exception {
        MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

        Hello mbean = new Hello();
        ObjectName mbeanName = new ObjectName("io.github.picodotdev.blogbitix:type=Hello");

        mbeanServer.registerMBean(mbean, mbeanName);

        System.out.println("Waiting for incoming requests...");
        Thread.sleep(Long.MAX_VALUE);
    }
}
Main-java.java

Iniciando la aplicación que registra un MBean en el servidor de MBean la plataforma Java incluye la herramienta JConsole de monitorización y gestión que cumple con la especificación JMX. VisualVM es otra herramienta de monitorización para una máquina virtual de Java, el soporte para visualizar y realizar operaciones sobre MBans hay que añadirlo con un complemento o plugin. Se inician con el siguiente comando y hay que abrir un diálogo para conectarse a uno de los agentes locales iniciados por una máquina virtual.

1
2
$ jconsole
$ ./visualvm
jconsole-visualvm.sh

Herramienta de monitorización e instrumentalización JConsole

Herramienta de monitorización e instrumentalización JConsole

Realizada la conexión al agente se muestran las propiedades y operaciones de los MBean registrados con la posibilidad de cambiar sus valores, invocar las operaciones y obtener sus resultados. La propia plataforma Java proporciona numerosos MBean como se muestra en el árbol lateral de la imagen.

Instrumentalización de un MBean en JConsole Instrumentalización de un MBean en VisualVM

Instrumentalización de un MBean en JConsole y VisualVM

En el caso de que la aplicación esté contenida dentro de una aplicación web y desplegada en un servidor de aplicaciones como Tomcat o WildFly registrar un MBean es similar al caso del ejemplo de la aplicación Java y posteriormente administrados con la herramienta JConsole.

Ejemplo de JMX con Spring Boot

El ejemplo anterior muestra como usar JMX en una aplicación Java, Spring ofrece soporte para implementar JMX en aplicaciones que usen este framework con las anotaciones @ManagedResource, @ManagedAttribute, @ManagedOperation, @ManagedOperationParameters, (@ManagedOperationParameter) y @EnableMBeanExport.

El mismo MBean de la aplicación Java implementado con spring es el siguiente, lo único que cambia son las anotaciones proporcionadas para que Spring descubra de forma automática los MBean disponibles y los registre sin necesidad de hacerlo de forma explícita.

1
2
3
4
5
6
7
8
package io.github.picodotdev.blogbitix.jmx.mbean;

public interface HelloMBean {

    void sayHello();
    int add(int x, int y);
    String getName();
}
HelloMBean.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.github.picodotdev.blogbitix.jmx.mbean;

import org.springframework.stereotype.Component;

import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;

@Component
@ManagedResource(objectName = "io.github.picodotdev.blogbitix:type=Hello")
public class Hello implements HelloMBean {

    @ManagedOperation
    public void sayHello() {
        System.out.println("hello, world");
    }

    @ManagedOperation
    public int add(int x, int y) {
        return x + y;
    }

    @ManagedAttribute
    public String getName() {
        return "Reginald";
    }
}
Hello-spring.java

Por autoconfiguración y la anotación @EnableMBeanExport los MBean se autodescubren y registran en el servidor MBean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package io.github.picodotdev.blogbitix.jmx.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.EnableMBeanExport;

import io.github.picodotdev.blogbitix.jmx.mbean.Hello;

@SpringBootApplication(scanBasePackageClasses = {Hello.class})
@EnableMBeanExport
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
Main-springboot.java

Tanto en el ejemplo de MBean con Java como con Spring el puerto RMI para acceder a JMX se configura con varias propiedades de la máquina virtual o con un archivo properties de configuración.

1
2
com.sun.management.jmxremote=false
com.sun.management.jmxremote.ssl=false
jmxremote-1.properties

Cómo añadir acceso remoto y añadir seguridad securizad a JMX

Por defecto JMX solo es accesible desde la maquina local, esto en producción no es muy útil pero activar el acceso remoto requiere añadir nuevas propiedades de configuración para proporcionar seguridad realizando autenticación y usando una comunicación segura con SSL. Para la comunicación segura se requiere crear un keystore.

1
2
3
$ keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 8192
$ keytool -export -keystore keystore.jks -file certificate.cer -storepass password
$ keytool -import -file certificate.cer -keystore trustore.jks -storepass password -noprompt
keystore.sh
1
2
3
4
5
6
7
com.sun.management.jmxremote=true
com.sun.management.jmxremote.port=1419
com.sun.management.jmxremote.rmi.port=31419
com.sun.management.jmxremote.ssl=true
com.sun.management.jmxremote.password.file=jmxremote.password
com.sun.management.jmxremote.access.file=jmxremote.access
com.sun.management.jmxremote.ssl.config.file=jmxremote-ssl.properties
jmxremote-2.properties
1
2
3
4
5
javax.net.ssl.keyStore=keystore.jks
javax.net.ssl.keyStorePassword=password
javax.net.ssl.trustStore=truststore.jks
javax.net.ssl.trustStorePassword=password

jmxremote-ssl.properties

Los archivos jmxremote.password y jmxremote.access configuran la autenticación mediante clave y contraseña además de la autorización a las operaciones que el usuario tiene permiso para realizar. Estos archivos han tener restringidos los permisos de lectura para el usuario que inicia la aplicación o se produce una excepción.

1
2
3
4
5
6
7
# The passwords in this file are hashed.
# In order to change the password for a role, replace the hashed password entry
# with a clear text password or a new hashed password. If the new password is in clear,
# it will be replaced with its hash when a new login attempt is made.

admin password
user password
jmxremote.password
1
2
admin readwrite
user  readonly
jmxremote.access
1
2
3
4
5
6
7
8
Error: Se debe restringir el acceso de lectura al archivo de contraseñas: jmxremote.password
jdk.internal.agent.AgentConfigurationError
    at jdk.management.agent/sun.management.jmxremote.ConnectorBootstrap.checkPasswordFile(ConnectorBootstrap.java:590)
    at jdk.management.agent/sun.management.jmxremote.ConnectorBootstrap.startRemoteConnectorServer(ConnectorBootstrap.java:436)
    at jdk.management.agent/jdk.internal.agent.Agent.startAgent(Agent.java:447)
    at jdk.management.agent/jdk.internal.agent.Agent.startAgent(Agent.java:599)

$ chmod 600 jmxremote.password jmxremote.access jmxremote-ssl.properties
permissions.sh
1
2
$ jconsole -J-Djavax.net.ssl.trustStore=truststore.jks -J-Djavax.net.ssl.trustStorePassword=password

jconsole.sh

Acceso remoto a la herramienta de monitorización e instrumentalización JConsole

Acceso remoto a la herramienta de monitorización e instrumentalización JConsole

El acceso remoto también es posible mediante una aplicación Java que actúe como cliente del servidor MBean.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: