JConsole 连上了注册表,RMI 却把它送到别处
注册表端口有应答只是第一跳。远程 JMX 服务器可能通告另一个主机和另一个连接器端口,隧道必须两个都能到达。
通过隧道连接注册表端口,看着握手开始,然后看着 JConsole 依然超时。原因在于第一个 TCP 连接并不是管理连接,而是一次查询。RMI 注册表会返回一个引用,里面写明了主机和第二个端口,JConsole 随后就照着去连。如果隧道只到服务器的第一个端口,或者引用里写的是只存在于服务器本地局域网的地址,客户端就会在看似成功之后卡住。
误区:一个开放端口就是一条连接
通过 RMI 的远程 JMX 至少涉及两个端点,而且它们由系统的不同部分决定。注册表端口是你在客户端里填的那个,它的职责是交出一个描述真实连接器的存根。把第一次成功的套接字当成管理通路已经打通的证据,正是把两分钟的排查变成一整个下午防火墙工单的原因。
要看客户端自己的报错,而不是服务器上的监听列表。握手开始之后才失败,说明没完成的是第二个连接,不是第一个。
注册表返回的是什么
存根里包含一个地址和一个端口,它们在连接器创建的那一刻被序列化。在多网卡的服务器上,协议栈可能选了一个从本地网络能通、从你这边却不通的地址。客户端并不知道这个区别,它只是照着拿到的值去连,数据包则按该地址的路由走,而在 VPN 客户端上这条路由往往根本不存在。第二个连接就是在这时被尝试的。
同一个服务器,在 JMX 配置完全没变的情况下,对局域网里的用户和对隧道里的用户表现不同,原因也在这里。
两个端口,以及把它们钉死的两个属性
注册表端口来自 com.sun.management.jmxremote.port,而连接器可以用 com.sun.management.jmxremote.rmi.port 钉死,而不是交给协议栈去分配。当客户端和服务器之间有防火墙或安全组时,两个端口都要明确设置,因为不可预测的第二个端口无法提前放行,而为它开一大段端口范围比钉死一个值更糟。
然后在服务器上用 ss -ltn 或 netstat -an 确认两个端口都在监听,以及监听在哪些地址上。绑定在 loopback 上的监听器对纯本地部署是合适的,对这个场景则毫无用处。
当通告的名字不对时
远程引用里出现的主机名由 java.rmi.server.hostname 设定。它影响存根通告什么;它本身并不是把所有监听器都绑到一个网卡上的指令,所以设置它并不总能得到预期的单端口行为。设好之后重启进程,再确认存根里实际写的是什么。选一个客户端能解析、并且能通过隧道到达的名字或地址,然后重启进程,让存根重新生成。
容器网络会让情况更糟,因为进程看到的地址可能是内部的,容器之外没有任何东西能把流量路由过去。要显式发布期望的主机名,而不是让平台自己挑。
如果你用 SSH 而不是 VPN 做隧道,同样的问题会换一副面孔出现:只转发注册表端口,第二个端点仍然指向客户端到不了的地址。两个端口都要转发,并让通告的名字解析到客户端本地能到达的东西上。
不要为了省事关掉管理安全
关掉认证和 TLS 的片段很容易找到,而且会让连接以你以后必须撤销的方式工作起来。远程 JMX 是管理接口,拥有和被检查进程一样的权限;要保留它的访问控制,如果客户端拿不出凭据,就去解决这个问题。
端点都正确之后,把接下来看到的报错保存下来。信任库或账号被拒是比连接器不可达更靠后的阶段,意味着网络这部分已经做完了。