【问题标题】:How to connect to Java instances running on EC2 using JMX如何使用 JMX 连接到在 EC2 上运行的 Java 实例
【发布时间】:2012-11-23 22:51:42
【问题描述】:

我们在连接到在 Amazon 的 EC2 集群中运行的 Java 应用程序时遇到问题。我们肯定已经允许“JMX 端口”(通常是 RMI 注册表端口)服务器端口(完成大部分工作)到相关实例的安全组。 Jconsole 连接但似乎挂起并且从不显示任何信息。

我们正在运行我们的 java,如下所示:

java -server -jar foo.jar other parameters here > java.log 2>&1

我们已经尝试过:

  • Telnet 到端口连接,但没有显示任何信息。
  • 我们可以通过 ssh 使用 remote-X11 在实例本身上运行jconsole,它会连接并显示信息。所以 JRE 正在在本地导出它。
  • 打开安全组中的所有端口。哎呀。
  • 使用tcpdump 确保流量不会流向其他端口。
  • 在本地模拟它。我们始终可以使用相同的应用程序参数连接到本地 JRE 或网络上其他地方运行的 JRE。

java -version 输出:

OpenJDK Runtime Environment (IcedTea6 1.11.5) (amazon-53.1.11.5.47.amzn1-x86_64)
OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)

顺便说一句,我们正在使用我的Simple JMX 包,它允许我们设置两个 RMI 注册表和服务器端口,这些端口通常由 RMI 注册表半随机选择。您还可以使用以下 JMX URI 强制执行此操作:

service:jmx:rmi://localhost:" + serverPort + "/jndi/rmi://:" + registryPort + "/jmxrmi"

如今,我们为服务器和注册表使用相同的端口。过去我们使用X 作为注册端口,X+1 作为服务器端口,以简化安全组规则。您连接到jconsole 中的注册表端口或您正在使用的任何 JMX 客户端。

【问题讨论】:

    标签: java amazon-ec2 jmx jconsole


    【解决方案1】:

    我们在连接到在 Amazon 的 EC2 集群中运行的 Java 应用程序时遇到问题。

    事实证明,问题是两个缺失设置的组合。第一个强制 JRE 更喜欢 ipv4 和 not v6。这是必要的(我猜),因为我们试图通过 v4 地址连接到它:

    -Djava.net.preferIPv4Stack=true
    

    真正的阻碍是 JMX 通过首先联系 RMI 端口来工作,该端口以 主机名 和 JMX 客户端连接的端口进行响应。如果没有其他设置,它将使用框的本地 IP,这是一个远程客户端无法路由到的 10.X.X.X 虚拟地址。我们需要添加以下设置,即服务器的 external 主机名或 IP ——在本例中,它是服务器的弹性主机名。

    -Djava.rmi.server.hostname=ec2-107-X-X-X.compute-1.amazonaws.com
    

    如果您尝试自动化您的 EC2 实例(以及为什么不这样做),诀窍是如何在运行时找到该地址。为此,您需要在我们的应用程序启动脚本中添加如下内容:

    # get our _external_ hostname
    RMI_HOST=`wget -q -O - http://169.254.169.254/latest/meta-data/public-hostname`
    ...
    java -server \
        -Djava.net.preferIPv4Stack=true -Djava.rmi.server.hostname=$RMI_HOST \
        -jar foo.jar other parameters here > java.log 2>&1
    

    上述wget 命令中的神秘169.254.169.254 IP 提供了EC2 实例可以请求的关于自身的信息。我很失望这包含仅在经过身份验证的调用中可用的标签。

    我最初使用的是 extern ipv4 地址,但看起来 JDK 在启动时尝试连接到服务器端口。如果它使用外部 IP,那么这会减慢我们的应用程序启动时间,直到超时。 public-hostname 在本地解析为 10-net 地址,在外部解析为 public-ipv4。所以应用程序现在启动很快,JMX 客户端仍然可以工作。呜呼!

    希望这对其他人有所帮助。今天花了我 3 个小时。

    要强制您的 JMX 服务器在指定端口上启动服务器 RMI 注册表,以便您可以在 EC2 安全组中阻止它们,请参阅以下答案:

    How to close rmiregistry running on particular port?

    编辑:

    我们刚刚再次出现此问题。似乎 Java JMX 代码正在对盒子的主机名进行一些主机名查找,并使用它们来尝试连接并验证 JMX 连接。

    问题似乎是要求盒子的本地主机名应该解析为盒子的本地 IP。例如,如果您的/etc/sysconfig/network 具有HOSTNAME=server1.foobar.com,那么如果您对server1.foobar.com 进行DNS 查找,您应该会到达10-NET 虚拟地址。我们正在生成我们自己的/etc/hosts 文件,并且文件中缺少本地主机的主机名。这导致我们的应用程序在启动时暂停或根本不启动。

    最后

    一种简化 JMX 创建的方法是使用我的SimpleJMX package

    【讨论】:

    • 如果您确实需要(例如通过 EC2 命令行实用程序),您可以发出 DescribeInstances 调用来确定哪些标签适用于您的实例,但更好的做法是通过以下方式将配置信息传达给实例用户数据。
    • 谢谢@willglynn。我希望获得标签而不必将我的访问/密钥添加到我的实例中。是的,我们现在使用 UserData 但我宁愿它是 key=value 类型的东西,这样操作员就不会打错字了。
    • 有一种机制可以通过同一实例元数据通道自动生成和交付 AWS 凭证:请参阅 IAM roles for EC2 instances。您可以创建一个仅限于 EC2 DescribeInstances 访问的角色,让您自动执行所有操作,而不会因凭证管理而发疯。
    • 安全组@RuiGonçalves?您不能只打开端口 12345。您还需要打开第二个分配的端口,对吗?见:stackoverflow.com/questions/8386001/…
    • @Gray 你是对的,在我的实例中通过netstat -lp 我可以看到java打开了两个额外的端口。我不知道我还需要一个,我想当我在测试之前测试所有为我打开的端口时......我傻了。没有一种非编程方式(即通过系统属性的方式)来定义第二个端口,是吗?如果没有,我想我会试试你的 SimpleJmx 包,然后!
    【解决方案2】:

    根据第二个答案Why does JMX connection to Amazon EC2 fail?,这里的困难在于默认情况下随机选择 RMI 端口,并且客户端需要同时访问 JMX 和 RMI 端口。如果您运行的是 jdk7u4 或更高版本,则可以通过 app 属性指定 RMI 端口。使用以下 JMX 设置启动我的服务器对我有用:

    无需认证:

    -Dcom.sun.management.jmxremote 
    -Dcom.sun.management.jmxremote.port=9999 
    -Dcom.sun.management.jmxremote.rmi.port=9998 
    -Dcom.sun.management.jmxremote.ssl=false 
    -Dcom.sun.management.jmxremote.authenticate=false 
    -Djava.rmi.server.hostname=<public EC2 hostname>
    

    通过身份验证:

    -Dcom.sun.management.jmxremote 
    -Dcom.sun.management.jmxremote.port=9999 
    -Dcom.sun.management.jmxremote.rmi.port=9998 
    -Dcom.sun.management.jmxremote.ssl=false 
    -Dcom.sun.management.jmxremote.authenticate=true 
    -Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password
    -Djava.rmi.server.hostname=<public EC2 hostname>
    

    我还在我的实例的 EC2 安全组中打开了端口 9998-9999。

    【讨论】:

    • 如果您的实例没有公共 EC2 主机名(例如,将其隐藏在 ELB 后面)并且您只希望 jmx 在内部工作,然后按照 Mark 的描述设置 jmx 端口和安全组,并将实例主机名放入 java.rmi.server.hostname。如果您调用@Gray 的答案中列出的 /meta-data/public-hostname/ 端点并且您得到一个空响应,您就会知道您的实例是以这种方式设置的。
    • 添加 -Dcom.sun.management.jmxremote.rmi.port 为我解决了这个问题。谢谢。
    • 您可以将两个端口设置为相同的数字,我认为@mmindenhall。
    【解决方案3】:

    使用 ssh 隧道有点不同

    1. (在远程机器上)将以下标志传递给 JVM

      -Dcom.sun.management.jmxremote.port=1099
      -Djava.net.preferIPv4Stack=true
      -Dcom.sun.management.jmxremote.ssl=false
      -Dcom.sun.management.jmxremote.authenticate=false
      -Djava.rmi.server.hostname=127.0.0.1
      
    2. (在远程机器上)检查 java 开始使用哪些端口

      $ netstat -tulpn | grep java
      tcp      0      0 0.0.0.0:37484         0.0.0.0:*               LISTEN      2904/java
      tcp      0      0 0.0.0.0:1099          0.0.0.0:*               LISTEN      2904/java
      tcp      0      0 0.0.0.0:45828         0.0.0.0:*               LISTEN      2904/java
      
    3. (在本地机器上)为所有端口建立 ssh 隧道

      ssh -N -L 1099:127.0.0.1:1099 ubuntu@<ec2_ip>
      ssh -N -L 37484:127.0.0.1:37484 ubuntu@<ec2_ip>
      ssh -N -L 45828:127.0.0.1:45828 ubuntu@<ec2_ip>`
      
    4. (在本地机器上)通过 Java Mission Control 连接到 localhost:1099

    【讨论】:

    • 此方法在您自动化 EC2 实例时很有用,因为您不知道您的服务将在哪个实例中运行,因此您事先不知道 IP。
    • 问题是你在附加你需要转发的端口之前并不知道。但是,您可以将端口和 com.sun.management.jmxremote.rmi.port 设置为同一个端口以使其正常工作。
    • @Gray--SOstopbeingevil 这个问题可以通过第 2 步解决,不是吗?
    • 我的意思是你必须通过 ssh 连接到盒子,运行 netstat 命令,然后使用转发的端口再次连接。如果你设置了两个端口,那么你只需要转发 1099。
    • 这个肯定比较简单,不过需要测试一下
    【解决方案4】:

    Gray 给出的答案对我有用,但是我发现我必须打开 TCP 端口 0 到 65535 否则我无法进入。我认为您可以连接到主 JMX端口,然后分配另一个端口。我从 this blog post 那里得到的,它一直对我很有效。

    【讨论】:

    • 我们确实必须这样做@Eric。我们只打开了 RMI 端口和 JMX 服务器端口。创建 JMX 服务器时,您确实需要指定这两个端口。顺便说一句,simpleJmx 包很容易为您做到这一点:256.com/sources/simplejmx
    • Gray,这很有趣......但是,我正在使用 Coda Hale Metrics 库,它在设置 JMX 时有自己的魔力......我想知道它是否可以让我做 simplejmx 所做的事情?
    • 请更新链接。给定的链接似乎给出了 404
    • 我修复了链接!
    【解决方案5】:

    我们正在使用 AWS Elastic Container Service 来运行我们的 Spring Boot 服务。 下面的配置允许我们连接到我们的 docker 容器。

    无需身份验证:

    -Dcom.sun.management.jmxremote \
        -Dcom.sun.management.jmxremote.port=9090 \
        -Dcom.sun.management.jmxremote.rmi.port=9090 \
        -Dcom.sun.management.jmxremote.authenticate=false \
        -Dcom.sun.management.jmxremote.ssl=false \
        -Djava.rmi.server.hostname=$(/usr/bin/curl -s --connect-timeout 2 \
                        http://169.254.169.254/latest/meta-data/public-ipv4)
    

    我发现它很清晰,也不需要任何其他服务端初始化脚本。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-15
      • 1970-01-01
      相关资源
      最近更新 更多