如何使用 JMX 连接到在 EC2 上运行的 Java 实例
Posted
技术标签:
【中文标题】如何使用 JMX 连接到在 EC2 上运行的 Java 实例【英文标题】:How to connect to Java instances running on EC2 using JMX 【发布时间】: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 客户端。
【问题讨论】:
【参考方案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。您还需要打开第二个分配的端口,对吗?见:***.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 隧道有点不同
(在远程机器上)将以下标志传递给 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
(在远程机器上)检查 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
(在本地机器上)为所有端口建立 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>`
(在本地机器上)通过 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 看到这个答案:***.com/questions/8386001/… 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)
我发现它很清晰,也不需要任何其他服务端初始化脚本。
【讨论】:
以上是关于如何使用 JMX 连接到在 EC2 上运行的 Java 实例的主要内容,如果未能解决你的问题,请参考以下文章
如何将 redshift 数据库连接到在 ec2 实例上运行的 bash 脚本