Ma question spécifique concerne JMX tel qu’il est utilisé dans JDK 1.6: si j’exécute un processus Java utilisant JRE 1.6 avec
com.Sun.management.jmxremote
dans la ligne de commande, Java choisit-il un port par défaut pour les connexions JMX distantes?
Backstory: J'essaie actuellement de développer une procédure à donner à un client pour lui permettre de se connecter à l'un de nos processus via JMX à partir d'une machine distante. L'objectif est de faciliter le débogage à distance d'une situation se produisant sur une console d'affichage en temps réel. En raison de leur contrat de niveau de service, ils sont fortement motivés pour capturer le plus de données possible et, si la situation semble trop compliquée à résoudre rapidement, redémarrer la console d'affichage et lui permettre de se reconnecter côté serveur.
Je sais que je pourrais exécuter jconsole sur les processus JDK 1.6 et jvisualvm sur les processus postérieurs à JDK 1.6.7 étant donné l'accès physique à la console. Toutefois, en raison des exigences opérationnelles et des problèmes de personnel, nous sommes fermement motivés pour récupérer les données dont nous avons besoin à distance et les remettre en service.
EDIT: je suis au courant de la propriété du port de ligne de commande
com.Sun.management.jmxremote.port=portNum
La question à laquelle j'essaie de répondre est la suivante: si vous ne définissez pas cette propriété sur la ligne de commande, Java choisit-il un autre port pour la surveillance à distance? Si oui, comment pourriez-vous déterminer ce que cela pourrait être?
La documentation suggère que l'agent JMX utilise un port local, ce qui est inaccessible de l'extérieur de la machine, à moins que vous ne spécifiiez la propriété suivante:
com.Sun.management.jmxremote.port=portNum
C'est pour des raisons de sécurité, ainsi que pour la raison donnée par M. Potato Head. Ainsi, il semble que Java 6 n'ouvre pas un port accessible à distance par défaut pour JMX.
EDIT: ajouté après que l'OP ait ajouté une réponse avec plus d'informations.
Une autre option consiste à créer en quelque sorte un proxy local qui écoute toutes les connexions JMX locales et exporte ces informations. De cette façon, vous n'avez pas besoin d'une telle configuration magique de chaque instance JVM sur le serveur. Au lieu de cela, le proxy local peut se connecter à toutes les machines virtuelles via JMX, puis exposer ces informations à distance. Je ne suis pas certain de la manière dont vous allez implémenter cela, mais cela peut être moins fastidieux que ce que vous devez faire autrement pour exposer toutes vos machines virtuelles à distance via JMX.
AFAIK,
Voici les possibilités pour connecter un processus client JMX (une application management comme jconsole, jmxterm, mc4j, jvmstat, jmxmonitor, jps, ...) à un processus serveur JMX (le agent).
Le protocole connectant le client JMX et le serveur JMX est supposé être «Java RMI» (ou «RMI-JRMP»). Cela devrait être le défaut. On peut configurer autres protocoles , en particulier 'RMI-IIOP' et 'JMXMP'. Des protocoles spéciaux sont possibles: le projet MX4J , par exemple, fournit en outre SOAP/HTTP et divers protocoles de sérialisation sur HTTP.
Reportez-vous à docs Sun/Oracle pour plus de détails sur la configuration.
Consultez également le fichier jre/lib/management/management.properties
dans votre distribution JDK.
Donc, les possibilités:
Cas 0: la JVM est démarrée sans configuration particulière
Avant Java 6: la machine virtuelle Java ne se comportait pas comme un serveur JMX. Tout programme exécuté dans la machine virtuelle Java peut accéder au programme MBeanServer de la machine virtuelle et l'utiliser pour effectuer des échanges de données intéressants entre les threads ou pour effectuer la surveillance de la machine virtuelle, mais aucune gestion en dehors du processus de la machine virtuelle n'est possible.
Depuis Java 6: Même sans configuration explicite, on peut accéder à la fonctionnalité JMX de la machine virtuelle localement (à partir du même ordinateur), comme décrit dans le "Cas 1".
Cas 1: La JVM est démarrée avec -Dcom.Sun.management.jmxremote
La machine virtuelle Java est configurée pour fonctionner en tant que serveur JMX local (sur la même machine uniquement).
Dans ce cas (et en principe uniquement pour les machines JVM Sun/Oracle), un client JMX peut se connecter au serveur JMX via des fichiers mappés en mémoire situés dans /tmp/hsperfdata_[user]
. Ceci est mentionné dans la documentation de Sun et appelé "surveillance locale" (ainsi que la Attach API ). Cela ne fonctionne pas sur les systèmes de fichiers FAT car les autorisations ne peuvent pas être définies correctement. Voir cette entrée de blog .
Sun recommande d'exécuter jconsole
sur une machine distincte du serveur JMX, car jconsole
est apparemment un gros problème de ressources. Cette opération de "surveillance locale" n'est donc pas forcément une bonne idée.
Cependant, la surveillance locale est plutôt sécurisée, elle ne peut être utilisée que localement et facilement contrôlée par le biais d'autorisations de système de fichiers.
Cas 2: le serveur JMX est démarré avec -Dcom.Sun.management.jmxremote.port=[rmiregistryport]
La machine virtuelle Java est configurée pour fonctionner en tant que serveur JMX à l'écoute de plusieurs ports TCP.
Le port spécifié sur la ligne de commande sera alloué par la JVM et un registre RMI y sera disponible. Le registre annonce un connecteur nommé "jmxrmi". Il pointe vers un deuxième port TCP alloué de manière aléatoire (un port "éphémère") sur lequel le serveur RMI JMX écoute et à travers lequel l'échange de données a lieu.
Local tel que décrit dans «Cas 1» est toujours activé dans «Cas 2».
Le serveur JMX écoute toutes les interfaces par défaut. Vous pouvez donc vous y connecter (et le contrôler) en vous connectant localement à 127.0.0.1:[rmiregistryport] et en vous connectant à distance à [toute adresse IP extérieure]: [certains ports] à distance. .
Cela implique que vous devez examiner les implications pour la sécurité. Vous pouvez faire écouter la machine virtuelle Java sur 127.0.0.1:[rmiregistryport] uniquement en définissant -Dcom.Sun.management.jmxremote.local.only=true
.
Il est plutôt regrettable qu’on ne puisse pas spécifier où le port éphémère sera alloué - il est toujours choisi au hasard au démarrage. Cela signifie peut-être que votre pare-feu doit devenir le fromage suisse des damnés! Cependant, il existe solutions de contournement . Apache Tomcat définit notamment le port éphémère du serveur RMI JMX via son écouteur de cycle de vie distant JMX . Le code pour effectuer cette petite magie peut être trouvé à org.Apache.catalina.mbeans.JmxRemoteLifecycleListener }.
Si vous utilisez cette méthode, vous pouvez également vous assurer que:
La procédure à suivre est décrite dans Documentation de Sun/Oracle }
Autres approches
Vous pouvez faire des permutations intéressantes pour éviter de devoir utiliser le protocole RMI. En particulier, vous pouvez ajouter un moteur de servlet (comme Jetty) à votre processus. Ajoutez ensuite des servlets qui traduisent en interne un échange basé sur HTTP en accès directs à la variable MBeanServer
de la machine virtuelle. Vous seriez alors dans le 'cas 0' mais auriez toujours des capacités de gestion, éventuellement via une interface HTML. La console JBoss JMX en est un exemple.
Plus hors sujet, vous pouvez utiliser SNMP directement (quelque chose que je n’ai pas essayé) selon ce document .
Montrer et indiquer l'heure
Et maintenant, il est temps que du code illustre un échange JXM. Nous nous inspirons de tutoriel Sunoracle .
Cela fonctionne sous Unix. Nous utilisons une machine virtuelle configurée en tant que serveur JMX en utilisant:
-Dcom.Sun.management.jmxremote.port=9001
Nous utilisons lsof
pour vérifier quels ports TCP il maintient ouverts:
lsof -p <processid> -n | grep TCP
On devrait voir quelque chose comme ceci, le port de registre et le port éphémère:
Java 1068 user 127u IPv6 125614246 TCP *:36828 (LISTEN)
Java 1068 user 130u IPv6 125614248 TCP *:9001 (LISTEN)
Nous utilisons tcpdump
pour inspecter l’échange de paquets entre le client JMX et le serveur JMX:
tcpdump -l -XX port 36828 or port 9001
Nous avons créé un fichier .Java.policy
dans le répertoire de base pour permettre au client de se connecter à distance:
grant {
permission Java.net.SocketPermission
"<JMX server IP address>:1024-65535", "connect,resolve";
};
Et ensuite, nous pouvons exécuter ceci et voir ce qui se passe:
package rmi;
import Java.rmi.registry.LocateRegistry;
import Java.rmi.registry.Registry;
import javax.management.remote.rmi.RMIConnection;
import javax.management.remote.rmi.RMIServer;
public class Rmi {
public static void main(String args[]) throws Exception {
// We need a Security Manager (not necessarily an RMISecurityManager)
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
//
// Define a registry (this is just about building a local data structure)
//
final int comSunManagementJmxRemotePort = 9001;
Registry registry = LocateRegistry.getRegistry("<JMX server IP address>", comSunManagementJmxRemotePort);
//
// List registry entries. The client connects (using TCP) to the server on the
// 'com.Sun.management.jmxremote.port' and queries data to fill the local registry structure.
// Among others, a definition for 'jmxrmi' is obtained.
//
System.out.print("Press enter to list registry entries");
System.in.read();
String[] names = registry.list();
for (String name : names) {
System.out.println("In the registry: " + name);
}
//
// 'Looking up' the entry registered under 'jmxrmi' involves opening and tearing down
// a TCP connection to the 'com.Sun.management.jmxremote.port', as well as a TCP
// connection to an ephemeral secondary port chosen at server startup.
// The actual object locally obtained is a "javax.management.remote.rmi.RMIServerImpl_Stub"
// indicating where the ephemeral port is.
// "RMIServerImpl_Stub[UnicastRef [liveRef: [endpoint:[$IP:$EPHEMERAL_PORT](remote),objID:[-62fb4c1c:131a8c709f4:-7fff, -3335792051140327600]]]]"
//
System.out.print("Press enter to get the 'jmxrmi' stub");
System.in.read();
RMIServer jmxrmiServer = (RMIServer)registry.lookup("jmxrmi");
System.out.println(jmxrmiServer.toString());
//
// Now get a "RMI Connection" to the remote. This involves setting up and tearing
// down a TCP connection to the ephemeral port.
//
System.out.print("Press enter to get the 'RMIConnection'");
System.in.read();
RMIConnection rcon = jmxrmiServer.newClient(null);
//
// Ask away. This involves setting up and tearing
// down a TCP connection to the ephemeral port.
//
System.out.print("Press enter to get the 'domains'");
System.in.read();
for (String domain : rcon.getDomains(null)) {
System.out.println("Domain: " + domain);
}
//
// Ok, that will do. For serious applications, we better use the higher-level JMX classes
//
}
}
En réalité, il existe une propriété non documentée que vous pouvez utiliser pour forcer JMX à créer des connecteurs accessibles à distance sur des numéros de port aléatoires.
-Dcom.Sun.management.jmxremote.authenticate="false"
-Dcom.Sun.management.jmxremote="true"
-Dcom.Sun.management.jmxremote.ssl="false"
-Dcom.Sun.management.jmxremote.port="0"
-Dcom.Sun.management.jmxremote.local.only="false"
Les deux dernières propriétés sont de la plus haute importance.
La documentation semble indiquer que l'agent JMX utilise un port éphémère local, sauf si vous spécifiez la propriété suivante:
com.Sun.management.jmxremote.port=portNum
Les ports par défaut sont évités car vous pourriez avoir plusieurs applications Java sur un système, et s'il existait un port par défaut, seule une application pourrait être gérée! La propriété de configuration ci-dessus est fournie dans le but express de distant management.
Si vous devez insister sur l'utilisation d'un port éphémère, l'URL de l'agent JMX doit être accessible depuis la machine virtuelle Java, via la propriété système suivante (bien qu'il s'agisse probablement d'une adresse locale):
com.Sun.management.jmxremote.localConnectorAddress
Note: J'imagine que vous pouvez toujours ouvrir un socket sur une adresse accessible à distance et des requêtes de proxy sur le socket local, mais utiliser l'option disponible semble bien plus attrayant!
Donc, la réponse courte à ma question est "non".
Cependant, il est intéressant d'examiner pourquoi. Regardez la sortie netstat
d'une connexion locale valide. Voici les ports que je vois s'ouvrir à la suite d'une jconsole
établissant une connexion locale avec lui-même. Comme vous pouvez le constater, le port 1650 est le port local utilisé pour les informations JMX:
Proto Local Address Foreign Address State
TCP Gandalf:1650 Gandalf:1652 ESTABLISHED
TCP Gandalf:1650 Gandalf:1653 ESTABLISHED
TCP Gandalf:1650 Gandalf:1654 ESTABLISHED
TCP Gandalf:1650 Gandalf:1655 ESTABLISHED
TCP Gandalf:1650 Gandalf:1656 ESTABLISHED
TCP Gandalf:1652 Gandalf:1650 ESTABLISHED
TCP Gandalf:1653 Gandalf:1650 ESTABLISHED
TCP Gandalf:1654 Gandalf:1650 ESTABLISHED
TCP Gandalf:1655 Gandalf:1650 ESTABLISHED
TCP Gandalf:1656 Gandalf:1650 ESTABLISHED
Cependant, il n'est pas suffisant d'essayer de connecter jconsole
à localhost:1650
. Malheureusement, tout ce qui vous intéressera sera un message "La connexion a échoué: aucun objet de ce type dans la table".
La conclusion de mon récit initial est donc que, si nous voulons faciliter la surveillance à distance à l'aide de JMX pour nos clients, nous devons vraiment identifier des ports d'accès à distance individuels uniques pour les divers processus Java démarrés dans notre système. Heureusement, tout cela nécessite une utilisation judicieuse de l'argument VM:
com.Sun.management.jmxremote.port=portNum
où nous aurons presque certainement une plage séquentielle pré-spécifiée de portNum
afin que le client puisse sélectionner l’application distante appropriée à l’aide du numéro de port.
Je me suis récemment mis au travail pour savoir comment activer la gestion JMX à distance à partir de code Java, sans exiger que la machine virtuelle Java soit démarrée avec un ensemble de propriétés spéciales. La solution que j'ai choisie consiste à démarrer mon propre registre privé RMI - assez facilement - et à exposer le service JMX sur ce registre. Je crée mon propre MBeanServer, puis je crée un nouveau JMXConnectorServer. Le JMXConnectorServer est créé par un appel comme
connector = JXMConnectorServerFactory.newJMXConnectorServer(url, null, server);
Où serveur est le MBeanServer, et url est une instance de JMXServiceURL.
L'URL est de la forme "service: jmx: rmi: /// jndi/rmi: // localhost:/jmxrmi" où port est le numéro de port du registre privé (local). "jmxrmi" est le nom de service standard du service JMX.
Après avoir configuré et démarré le connecteur, je constate que je peux me connecter à partir de jconsole en utilisant le nom d’hôte: port.
Cela répond entièrement à mes besoins. Je serais intéressé de savoir si quelqu'un voit une faille dans cette approche.
Référence: Tutoriel JMX, Chap. 3
Si vous exécutez votre application sur le serveur d'applications Glassfish, exécutez simplement la commande asadmin suivante. Vous devrez redémarrer tous les serveurs en cours d'exécution pour que les modifications prennent effet.
./asadmin enable-secure-admin
Il existe d'autres configurations de serveur Glassfish pour renforcer la sécurité. Pour plus d'informations, reportez-vous à la section Connexion à distance à Glassfish via JMX .