【问题标题】:How to access JMX interface in docker from outside?如何从外部访问 docker 中的 JMX 接口?
【发布时间】:2015-09-24 07:34:05
【问题描述】:

我正在尝试远程监控在 docker 中运行的 JVM。配置如下:

  • 机器 1:在 ubuntu 机器上的 docker 中运行 JVM(在我的例子中,运行 kafka);本机IP为10.0.1.201;在 docker 中运行的应用程序位于 172.17.0.85。

  • 机器 2:运行 JMX 监控

请注意,当我从机器 2 运行 JMX 监控时,它会失败并出现以下错误版本(注意:当我运行 jconsole、jvisualvm、jmxtrans 和 node-jmx/npm:jmx 时会出现相同的错误):

对于每个 JMX 监控工具,失败时的堆栈跟踪如下所示:

java.rmi.ConnectException: Connection refused to host: 172.17.0.85; nested exception is
    java.net.ConnectException: Operation timed out
    at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:619)
    (followed by a large stack trace)

现在有趣的部分是当我在运行 docker 的同一台机器(上面的机器 1)上运行相同的工具(jconsole、jvisualvm、jmxtrans 和 node-jmx/npm:jmx)时,JMX 监控工作正常。

我认为这表明我的 JMX 端口处于活动状态并且工作正常,但是当我远程执行 JMX 监控(从机器 2)时,JMX 工具似乎无法识别内部 docker IP (172.17.0.85)

以下是 JMX 监控工作的机器 1 上的相关(我认为)网络配置元素(注意 docker ip,172.17.42.1):

docker0   Link encap:Ethernet  HWaddr ...
      inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
      inet6 addr:... Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:6787941 errors:0 dropped:0 overruns:0 frame:0
      TX packets:4875190 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:0
      RX bytes:1907319636 (1.9 GB)  TX bytes:639691630 (639.6 MB)

wlan0     Link encap:Ethernet  HWaddr ... 
      inet addr:10.0.1.201  Bcast:10.0.1.255  Mask:255.255.255.0
      inet6 addr:... Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:4054252 errors:0 dropped:66 overruns:0 frame:0
      TX packets:2447230 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:2421399498 (2.4 GB)  TX bytes:1672522315 (1.6 GB)

这是我收到 JMX 错误的远程机器(机器 2)上的相关网络配置元素:

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
    options=3<RXCSUM,TXCSUM>
    inet6 ::1 prefixlen 128 
    inet 127.0.0.1 netmask 0xff000000 
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
    nd6 options=1<PERFORMNUD>

en1: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether .... 
    inet6 ....%en1 prefixlen 64 scopeid 0x5 
    inet 10.0.1.203 netmask 0xffffff00 broadcast 10.0.1.255
    nd6 options=1<PERFORMNUD>
    media: autoselect
    status: active

【问题讨论】:

  • 我创建了一个GitHub project,其中包含从 Docker 容器中准备好的 JMX 实现。它包含一个Dockerfile 和一个适当的entrypoint.sh,以及一个docker-compose.yml,以便于部署。

标签: java docker jmx jmxtrans


【解决方案1】:

为了完整起见,以下解决方案有效。 JVM 应使用特定参数运行,以启用远程 docker JMX 监控,如下所示:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.port=<PORT>
-Dcom.sun.management.jmxremote.rmi.port=<PORT>
-Djava.rmi.server.hostname=<IP>

where:

<IP> is the IP address of the host that where you executed 'docker run'
<PORT> is the port that must be published from docker where the JVM's JMX port is configured (docker run --publish 7203:7203, for example where PORT is 7203). Both `port` and `rmi.port` can be the same. 

完成此操作后,您应该能够从本地或远程计算机执行 JMX 监控(jmxtrans、node-jmx、jconsole 等)。

感谢 @Chris-Heald 让这个问题变得非常快速和简单!

【讨论】:

  • 这行得通,但要连接到 Docker 容器,我还必须添加:-Dcom.sun.management.jmxremote.port=1098 并连接到该端口而不是 rmi.port
  • -Djava.rmi.server.hostname=&lt;IP&gt; 对我来说很重要。谢谢!
  • 如果我不想将固定 IP 连接到我的 Dockerfile 中,您是否有任何最佳实践方法来实现这一点?问题是,我正在使用一个使用“CMD java -jar ...”来后台处理应用程序的图像,并且我不能依赖主机的固定 IP。
  • 非常重要的是传递给-Dcom.sun.management.jmxremote.rmi.port的“相同端口”被客户端(即VisualVM)用于连接,即使使用SSH端口转发也是如此。例如,如果您设置了-Dcom.sun.management.jmxremote.rmi.port=8888 并且您设置了docker run -p25000:8888 ...,那么您使用端口转发ssh -L 8888:localhost:25000 your-docker-host.com 进行SSH,然后您可以使用localhost:8888 与VisualVM 连接。
【解决方案2】:

我发现尝试通过 RMI 设置 JMX 很痛苦,尤其是因为您必须在启动时指定 -Djava.rmi.server.hostname=&lt;IP&gt;。我们在 Kubernetes 中运行我们的 docker 镜像,其中一切都是动态的。

我最终使用 JMXMP 而不是 RMI,因为这只需要打开一个 TCP 端口,并且不需要主机名。

我目前的项目使用的是Spring,可以通过添加这个来配置:

<bean id="serverConnector"
    class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

(在 Spring 之外,您需要设置自己的 JMXConncetorServer 才能使其工作)

连同这种依赖关系(因为 JMXMP 是一个可选扩展,而不是 JDK 的一部分):

<dependency>
    <groupId>org.glassfish.main.external</groupId>
    <artifactId>jmxremote_optional-repackaged</artifactId>
    <version>4.1.1</version>
</dependency>

并且您需要在启动 JVisualVM 时添加与您的类路径相同的 jar,以便通过 JMXMP 进行连接:

jvisualvm -cp "$JAVA_HOME/lib/tools.jar:<your_path>/jmxremote_optional-repackaged-4.1.1.jar"

然后用下面的连接字符串进行连接:

service:jmx:jmxmp://<url:port>

(默认端口为 9875)

【讨论】:

    【解决方案3】:

    对于开发环境,您可以将java.rmi.server.hostname 设置为catch-all IP address 0.0.0.0

    例子:

     -Djava.rmi.server.hostname=0.0.0.0 \
                    -Dcom.sun.management.jmxremote \
                    -Dcom.sun.management.jmxremote.port=${JMX_PORT} \
                    -Dcom.sun.management.jmxremote.rmi.port=${JMX_PORT} \
                    -Dcom.sun.management.jmxremote.local.only=false \
                    -Dcom.sun.management.jmxremote.authenticate=false \
                    -Dcom.sun.management.jmxremote.ssl=false
    

    【讨论】:

    • 不适用于 Docker for Windows + kind + kubectl port-forward
    • @RobinGreen 应该可以。 JMX_PORT你设置了什么?
    【解决方案4】:

    找了好久,终于找到了这个配置

    -Dcom.sun.management.jmxremote.ssl=false 
    -Dcom.sun.management.jmxremote.authenticate=false 
    -Dcom.sun.management.jmxremote.port=1098
    -Dcom.sun.management.jmxremote.rmi.port=1098
    -Djava.rmi.server.hostname=localhost
    -Dcom.sun.management.jmxremote.local.only=false
    

    与上面另一个不同的是java.rmi.server.hostname设置为localhost而不是0.0.0.0

    【讨论】:

      【解决方案5】:

      为了补充一些额外的见解,我使用了一些 Docker 端口映射,之前的答案都没有直接对我有用。经过调查,我在这里找到了答案:How to connect with JMX from host to Docker container in Docker machine? 以提供所需的见解。

      这就是我认为会发生的事情:

      我按照此处其他答案中的建议设置了 JMX:

      -Dcom.sun.management.jmxremote.ssl=false 
      -Dcom.sun.management.jmxremote.authenticate=false 
      -Dcom.sun.management.jmxremote.port=1098
      -Dcom.sun.management.jmxremote.rmi.port=1098
      -Djava.rmi.server.hostname=localhost
      -Dcom.sun.management.jmxremote.local.only=false
      

      程序流程:

      • 我运行 Docker 容器并将端口从主机公开/映射到容器。假设我在 Docker 中映射端口 host:1099->container:1098。
      • 我使用上述 JMX 设置在 docker 内运行 JVM。
      • Docker 容器内的 JMX 代理现在侦听给定的端口 1098。
      • 我使用 URL localhost:1099 在主机(Docker 外部)上启动 JConsole。我使用 1099,因为我使用了 1099:1098 的 host:docker 端口映射。
      • JConsole 可以很好地连接到 Docker 内的 JMX 代理。
      • JConsole 询问 JMX 从何处读取监控数据。
      • JMX 代理响应配置的信息和地址:localhost:1098
      • JConsole 现在尝试连接到给定地址localhost:1098
      • 这会失败,因为 localhost(Docker 外部)上的端口 1098 没有监听。端口 1099 映射到 Docker:1098。 JMX 应该告诉 JConsole 从 localhost:1099 读取监控信息,而不是 localhost:1098,因为 1099 是 Docker 容器内从主机映射到 1098 的端口。

      作为修复,我将host:docker 端口映射从1099:1098 更改为1098:1098。现在,JMX 仍然告诉 JConsole 连接到localhost:1098 以获取监控信息。但现在它可以工作了,因为外部端口与 Docker 内部的 JMX 通告的端口相同。

      我希望这同样适用于 SSH 隧道和类似情况。您必须匹配您配置的 JMX 广告以及 JConsole 视为您运行它的主机上的地址空间。

      也许可以使用jmxremote.portjmxremove.rmi.porthostname 属性来使用不同的端口映射来完成这项工作。但我有机会使用相同的端口,所以使用它们简化了它,这很有效(对我来说)。

      【讨论】:

        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 2020-02-21
        • 1970-01-01
        • 2018-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多