JMX Remote

Par accès remote, on entend surtout accès externe, par opposition à l’accès intra-VM au MBean server. Cet accès est celui utilisé par des outils comme JConsole, VisualVM ou JDK Mission Control. Il existe deux cas d’accès externes : local ou distant.

Accès local

L’accès local se fait par l'Attach API. Cette technique ne nécessite aucune configuration pour être utilisée (depuis Java 6) : on lance une application java, puis on peut y connecter notre outil (jconsole par exemple). La connexion initiale se fait par un échange entre les process, puis une socket est ouverte sur un port aléatoire pour le reste de la communication. Le dialogue se fait donc par le réseau par l’interface locale.

Il est possible de désactiver l'Attach API, et donc l’accès local, avec le flag -XX:+DisableAttachMechanism. Il semble qu’il soit impossible de désactiver l’accès local si on active l’accès distant.

Accès distant

Pour activer l’accès distant, il faut ouvrir un port d’accès, avec le flag com.sun.management.jmxremote.port=<port>. Une fois cet accès activé, se posent des questions concernant la sécurité (authentification et SSL) et la configuration réseau.

Authentification

La solution la plus simple est de désactiver l’authentification avec com.sun.management.jmxremote.authenticate=false. Simple pour le développement, mais risqué en production.

Pour un accès sécurisé, on a deux fichiers properties : celui des mots de passe et celui des droits d’accès. Par défaut ces fichiers sont dans <JRE_HOME>/lib/management, ce qui n’est pas très pratique. On changera leur localisation avec com.sun.management.jmxremote.password.file=<jmxremote.password> et com.sun.management.jmxremote.access.file=<jmxremote.access>.

Le fichier password contient des couples username=password :

alexis=h@ssl3r
guest=s3cr3t

Les permissions sur ce fichier doivent être limitées à l’utilisateur qui lance l’application :

chmod 600 jmxremote.password

Le fichier access contient des couples username=permission :

alexis=readwrite
guest=readonly

Pour un utilisateur en readwrite, on peut affiner les droits, en ajouter les permissions de suppression (unregister) de MBeans, et de création (create) de MBeans, limités à certains packages ou certaines classes.

alexis=readwrite unregister create fr.sewatech.management.*

SSL

Là aussi, la solution la plus simple est de désactiver le SSL avec com.sun.management.jmxremote.ssl=false. Et là aussi, ça peut être risqué en production.

Pour sécuriser les communications, il faut donc générer une pair de clé dans un keystore.

keytool -genkeypair -keystore conf/jmxremote.jks -storepass javakey                \
                    -alias jmxremote -keypass javakey -dname "cn=www.sewatech.fr"

Puis on utilise ce keystore au démarrage de l’application, avec les propriétés javax.net.ssl.keyStore et javax.net.ssl.keyStorePassword.

java -Djavax.net.ssl.keyStore=conf/jmxremote.jks -Djavax.net.ssl.keyStorePassword=javakey ...

Du coté du client JMX, on exporte la clé publique, auto-signée, qu’on importe dans un truststore.

keytool -certreq    -keystore conf/jmxremote.jks -storepass javakey                       \
                    -alias jmxremote -keypass javakey -file temp/jmxremote.cer
keytool -gencert    -keystore conf/jmxremote.jks -storepass javakey                       \
                    -alias jmxremote -keypass javakey -infile temp/jmxremote.cer          \
                    -outfile temp/jmxremote.cert
keytool -importcert -keystore temp/jconsole.jks  -storepass jmxkey                        \
                    -alias jconsole  -keypass jmxkey  -file temp/jmxremote.cert

Puis pour lancer le client JMX, il faut les propriétés javax.net.ssl.trustStore et javax.net.ssl.trustStorePassword :

jconsole -J-Djavax.net.ssl.trustStore=temp/jconsole.jks     \
         -J-Djavax.net.ssl.trustStorePassword=jmxkey

Ports

Lorsqu’on démarre une application avec un accès JMX distant, trois ports sont ouverts :

  • le port registry, qu’on a spécifié avec -Dcom.sun.management.jmxremote.port,

  • le port server, qui est choisi aléatoirement et qui sert à la communication en accès distant,

  • le port attach, qui sert à la communication en accès local.

Ce port server aléatoire peut poser des problèmes s’il faut passer par un firewall. Pour contourner ce problème, il est possible de fixer la valeur de port avec com.sun.management.jmxremote.rmi.port=<port>.

Adresse IP

C’est une bizarrerie du protocole, mais si je connecte mon outil à <public-hostname>:<registry-port>, la communication se fera via le port server, en utilisant le hostname privé de la machine. Dans de nombreux cas, il n’y a pas de distinction entre nom (ou adresse IP) publique et privée, mais dans certains cas, comme sur AWS (le cloud d’Amazon), ce fonctionnement empêche la communication. Pour contourner ce problème, on peut forcer l’adresse qui sera utilisée pour la communication avec java.rmi.server.hostname=<public-hostname>.

Serveurs d’applications

Certains serveurs d’applications apportent des ajouts ou des modifications à l’accès remote. Comme je ne connais pas tous les serveurs du marché, je vais me concentrer sur ceux que je connais.

Apache Tomcat

Pour fixer les ports de JMX remote, Tomcat propose le JmxRemoteLifecycleListener. Ce composant doit être téléchargé séparément, puis configuré dans le fichier conf/server.xml :

<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
          rmiRegistryPortPlatform="1099" rmiServerPortPlatform="1098" />

Dans cette configuration, il faudra connecter jconsole (ou tout autre client JMX) au port 1099, et la communication se fera ensuite sur le port 1098. La configuration de l’authentification et du SSL se fait toujours par propriétés, dans le fichier bin/setenv.sh par exemple.

Cette configuration était surtout intéressante jusqu’en Java 6, avant l’apparition de la propriété com.sun.jmxremote.rmi.port. D’ailleurs le listener a disparu de Tomcat 10.

JBoss / WildFly

Concernant les vieux JBoss, le sujet est déjà traité dans la page dédiée au monitoring JMX dans JBoss 4 ou 5. A partir de JBoss AS 7, ou de JBoss EAP 6, le choses ont radicalement changé. Ces changements sont toujours valables dans WildFly.

Le changement est radical, puisqu’on n’utilise plus rien de l’accès JMX remote. A la place, on réutilise le port d’administration de JBoss (9999) ou de WildFly (9990), avec un protocole maison. D’un point de vue réseau, ça simplifie les choses, mais ça impose des adaptations du coté du client JMX.

Tout d’abord, le client doit enrichir son classpath avec des fichiers jar supplémentaires. Cette opération est faite par le script bin/jconsole.sh, qui peut être adapté pour un autre client. Pour JBoss AS 7, l’opération est légèrement fastidieuse puisqu’il faut ajouter les fichiers jar d’une quinzaine de modules. Depuis WidlFly 8, ça a été nettement simplifié, avec un seul fichier (bin/client/jboss-cli-client.jar).

jconsole -J-Djava.class.path=$JAVA_HOME/lib/jconsole.jar:$JAVA_HOME/lib/tools.jar;$JBOSS_HOME/bin/client/jboss-cli-client.jar

ou

visualvm -cp:a $JBOSS_HOME/bin/client/jboss-cli-client.jar

Ensuite, comme le protocole de communication n’est pas du RMI standard, l’adresse de connexion est plus compliquée :

  • service:jmx:http-remoting-jmx://myserver:9990 pour WildFly,

  • service:jmx:remoting-jmx://myserver:9999 pour JBoss AS 7.

L’authentification est la même que pour l’accès par jboss-cli ou par la console web.

Synthèse

Avec toutes les propriétés qu’on vient de voir, la ligne de commande pour démarrer une application peut être longue, avec beaucoup de -D. Par exemple, pour démarrer Tomcat avec JMX en mode développement, je mets ça dans mon fichier setenv.sh :

# Network
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=1099"
# SSL
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
# Authentication
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.authenticate=false"

Et pour démarrer Tomcat avec JMX en mode sécurisé, mon fichier setenv.sh devient :

# Network
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.rmi.port=1098   \
                              -Djava.rmi.server.hostname=vaurion.jtips.info"
# SSL
CATALINA_OPTS="$CATALINA_OPTS -Djavax.net.ssl.keyStore=$CATALINA_HOME/conf/jmxremote.jks  \
                              -Djavax.net.ssl.keyStorePassword=javakey"
# Authentication
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.password.file=$CATALINA_HOME/conf/jmxremote-pwd.properties  \
                              -Dcom.sun.management.jmxremote.access.file=$CATALINA_HOME/conf/jmxremote-access.properties"