如何让JMX绑定到特定的接口?

24

我目前正在使用com.sun.management.jmxremote.*属性启动Java虚拟机,以便可以通过JConsole进行管理和监控。不幸的是,它在机器上监听所有接口(IP地址)。

在我们的环境中,经常会出现同一台机器上运行多个Java虚拟机的情况。虽然可以告诉JMX在不同的TCP端口上进行侦听(使用com.sun.management.jmxremote.port),但最好的方法是让JMX使用标准的JMX端口并绑定到特定的IP地址(而不是所有IP地址)。

这将使我们更容易通过JConsole确定连接到哪个VM(因为每个VM实际上“拥有”自己的IP地址)。是否有人找到了如何使JMX侦听单个IP地址或主机名的方法?

7个回答

33

值得注意的是,在openjdk 1.8.0_312上,如果你想绑定到特定接口,则还需要使用-Dcom.sun.management.jmxremote.ssl=false禁用SSL,否则-Dcom.sun.management.jmxremote.host=127.0.0.1将被忽略。 - RubenLaguna

8

Fernando已经提供了一个链接到我的博客文章 :) ..这不是一件简单的事情。你必须提供自己的RMIServerSocketFactoryImpl,以在所需地址上创建套接字。

如果内部/外部接口是问题,并且你有本地访问权限,则设置本地防火墙可能更容易。


谢谢 - 我并没有想到这会很简单或漂亮。 :) - Marc Novakowski

0

我没有尝试过这个,但这可能会有所帮助。

主要的问题在于没有简单的方法来指定JMX绑定到的主机IP地址,它总是绑定到所有接口。'java.rmi.server.hostname'属性不起作用,我也不想为同一主机上的所有不同实例选择不同的端口。

此外,我不想创建自己的RMIServerSocketFactory,因为它涉及到的复杂性太多了,我只是想对现有代码进行简单的修补。

我通过修补默认的JVM RMI套接字工厂来解决了这个问题,该工厂负责创建此服务器套接字。现在它支持新的'com.sun.management.jmxremote.host'属性。

要使其正常工作,请将以下Java代码保存到名为sun/rmi/transport/proxy/RMIDirectSocketFactory.java的文件中。

从中编译并创建jmx_patch.jar,并将其放置在tomcat lib/文件夹中。

然后,您需要将以下行添加到bin/setenv.sh中:

CLASSPATH=$CLASSPATH:$CATALINA_HOME/lib/mx_patch.jar

添加此选项到Tomcat实例启动。

-Dcom.sun.management.jmxremote.host=192.168.100.100"

这将仅将JMX服务绑定到地址192.168.100.100。另外2个随机的RMI监听端口仍将绑定到所有接口,但这没关系,因为它们总是选择一个空闲端口。

现在,您可以在单个主机上运行多个Tomcat实例,并保留所有默认端口(例如,对于所有Tomcat实例的JMX为8080)。

package sun.rmi.transport.proxy;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;

public class RMIDirectSocketFactory extends RMISocketFactory {

    public Socket createSocket(String host, int port) throws IOException
    {
     return new Socket(host, port);
    }

   public ServerSocket createServerSocket(int port) throws IOException
   {
   String jmx_host = System.getProperty("com.sun.management.jmxremote.host");
   String jmx_port = System.getProperty("com.sun.management.jmxremote.port");

  // Allow JMX to bind to specific address
  if (jmx_host != null && jmx_port != null && port != 0 && integer.toString(port).equals(jmx_port)) {
    InetAddress[] inetAddresses = InetAddress.getAllByName(jmx_host);
    if (inetAddresses.length > 0) {
    return new ServerSocket(port, 50, inetAddresses[0]);
   }
}

 return new ServerSocket(port);
  }

}


0

您需要设置至少com.sun.management.jmxremote.{host,port,ssl},尽管com.sun.management.jmxremote.host没有文档记录,但它至少在OpenJDK 1.8.0_312中可以使用。

您还需要(很可能)设置java.rmi.server.hostname。请记住,JMX客户端首先连接到jmxremot主机和端口,然后从那里获取RMI主机和端口,最后将通过RMI连接到该主机和端口,如果您不设置java.rmi.server.hostname,则jmx客户端将获得的主机(ip)将没有任何侦听器用于该端口(因为您仅在127.0.0.1上进行侦听)

这是一个可行的示例,注意我还禁用了身份验证并强制使用IPv4:

/usr/lib/jvm/java-8-openjdk-amd64//bin/java \
-Dcom.sun.management.jmxremote.host=127.0.0.1  \
-Djava.rmi.server.hostname=127.0.0.1 \
-Dcom.sun.management.jmxremote.port=2222 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.registry.ssl=false  \
-Dcom.sun.management.jmxremote.authenticate=false \
-Djava.net.preferIPv4Stack=true \
-jar your-uber.jar
  

从netstat的输出可以确认,端口222仅绑定到127.0.0.1而不是0.0.0.0:

netstat -plnt|grep 2222
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:2222          0.0.0.0:*               LISTEN      15390/java

0

当仅使用com.sun.management.jmxremote.host而不更改com.sun.management.jmxremote.ssl(默认为true)或com.sun.management.jmxremote.registry.ssl(默认为false)时,它仍将绑定到所有接口的com.sun.management.jmxremote.port,并且仅对com.sun.management.jmxremote.rmi.port(默认为随机端口)使用com.sun.management.jmxremote.host。不确定为什么会这样,看起来像是一个错误。对于这些jmxremote.ssljmxremote.registry.ssl值的任何其他组合,它将正确地使用给定的jmxremote.host用于两个端口。

例如,使用

  -Dcom.sun.management.jmxremote.authenticate=false
  -Dcom.sun.management.jmxremote.port=1234
  -Dcom.sun.management.jmxremote.host=interface1
  

它仍将绑定到端口1234的所有接口,并仅绑定到随机jmxremote.rmi.portinterface1

请注意,JMX客户端首先连接到RMI注册表上的jmxremote.port,通过该注册表接收实际的JMX服务器端口jmxremote.rmi.port,因此客户端仍然只能通过jmxremote.host接口与实际的JMX服务器通信。但是,当使用相同的jmxremote.port值启动不同的VM时,RMI注册表在所有接口上侦听仍然是不可取的,因为它会导致端口冲突,这是本问题的情况。


-2

我刚试过了

-Dcom.sun.management.jmxremote.host=

使用OpenJDK 1.8,一切正常。根据netstat的显示,它绑定到了那个地址,并且一切看起来都很正确(并且工作正常)。

3
这是一个重复的回答 - 也许你想要在原始答案下添加评论? - Richlv

-3

被接受的答案相当陈旧。有一些迹象表明Java现在提供了一些选项来启用此功能。例如,我已经看到:

-Djava.rmi.server.hostname=<YOUR_IP>

...以及...

-Dcom.sun.management.jmxremote.host=<YOUR_IP>

然而,在我的 jdk 1.7 系统下,这些似乎没有任何效果 - JMX 连接器仍然绑定到 *。更新的答案(具有特定适用版本)将不胜感激。这应该很简单。


这两个都不能控制绑定地址。java.rmi.server.hostname不是新的,它已经存在了17年,我怀疑另一个也不是新的。 - user207421
我没说它们是新的。我说过我见过它们,但它们并不好用。 - sosiouxme
我曾希望有一种不可怕的方法可以真正起作用。但是到目前为止,我还没有找到任何一种方法。 - sosiouxme
你说“Java 现在 提供了”。Java一直提供java.rmi.server.hostname。它们确实有效。它们只是没有做你认为它们会做的事情。你还说“有一些迹象”表明这些可以做你想要的事情,但实际上并没有。很难看出这个答案的意义所在。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接