如何将VisualVM附加到在Docker容器中运行的简单Java进程


72

实际上,我想要一个适用于JEE容器(特别是Glassfish)的解决方案,但是在尝试了多种设置组合但没有成功后,我将设置简化为最简单的情况。

这是我在Docker容器中启动的Hello World守护程序。我想附加jconsole或附加VisulaVM到它。一切都在同一台机器上。

public class Main {
  public static void main(String[] args) {
    while (true) {
      try {
        Thread.sleep(3000);
        System.out.println("Hello, World");
      } catch (InterruptedException e) {
        break;
      }
    }
  }
}

Docker文件

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java", "Main"]

建造: docker build -t hello-world-daemon .

运行: docker run -it --rm --name hwd hello-world-daemon

问题:

  • 哪些JVM参数应添加到CMD命令行?
  • 应该公开和发布哪些端口?
  • Docker容器应使用哪种网络模式?

我不会在这里显示我的失败尝试,以便不会对正确答案造成偏见。这应该是一个非常普遍的问题,但是我找不到可行的解决方案。

更新。可行的解决方案

这个Dockerfile有效

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java", \
"-Dcom.sun.management.jmxremote", \
"-Dcom.sun.management.jmxremote.port=9010", \
"-Dcom.sun.management.jmxremote.local.only=false", \
"-Dcom.sun.management.jmxremote.authenticate=false", \
"-Dcom.sun.management.jmxremote.ssl=false", "Main"]
EXPOSE 9010

结合docker run命令

docker run -it --rm --name hwd -p 9010:9010 hello-world-daemon

VisualVM通过右键单击Local-> Add JMX Connection,然后输入localhost:9010,或通过添加远程主机进行连接。

JConsole通过选择远程进程进行连接localhost:9010

将连接定义为远程时,ifconfig可以使用列出的任何接口。例如,docker0与地址的接口172.17.0.1起作用。容器的地址172.17.0.2也可以。

Answers:


53

首先,您应该使用以下JVM参数运行应用程序:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

然后,您应该为docker公开端口:

EXPOSE 9010

还使用docker run命令指定端口绑定:

docker run -p 9010:9010 -it --rm --name hwd hello-world-daemon

之后,您可以使用Jconsole连接到本地9010端口并管理在Docker中运行的应用程序。


7
都能跟得上.. VisualVM的:Cannot connect to localhost:9010 using service:jmx:rmi:///jndi/rmi://localhost:9010/jmxrmi。Jconsole:Connection failed: error during JRMP connection establishment; nested exception is: java.net.SocketException: Connection reset
nolexa

1
为什么要两次暴露同一端口?
nolexa '16

不要将localhost连接到您的网络接口。
eg04lt3r '16

5
终于奏效了。我的错误是我Main在命令行中的类名称后附加了JVM选项。所有-D选项都被默默忽略java
nolexa '16

1
@EthanLeroy之后的所有参数-jar foo.jar都被发送到主类的主函数(如Main-ClassJAR清单中所定义);基本上,之前-jar的参数适用于JVM,之后-jar的参数适用于正在运行的程序
kbolino

13

我跟随另一个SO回答了类似的问题,它奏效了。

我通过添加以下JVM参数在容器内启动了Java流程:

-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.rmi.port=<port> \
-Djava.rmi.server.hostname=$HOST_HOSTNAME

并启动了指定-e HOST_HOSTNAME=$HOSTNAME -p <port>docker run命令的Docker容器。

然后,我可以通过添加远程JMX连接(“文件”>“添加JMX连接...”)并<dockerhostname>:<port>在“连接”输入中指定,然后选中“不需要SSL连接”。


到底是$HOST_HOSTNAME什么?它是运行docker的机器的主机还是其他主机?
艾伯特·比基耶夫

1
是的,它是运行docker的主机的主机名。它可能是hostname命令的结果,因此您可以在启动容器时将其传递给docker,如下所示:-e HOST_HOSTNAME=`hostname`
Anthony O.18年

3

正如安东尼回答的。我必须-Djava.rmi.server.hostname在Windows计算机上使用java选项。

请确保不要在Dockerfile中使用JSON格式的CMD,因为它不支持shell扩展。

Dockerfile示例:

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
#Do not use CMD in JSON format here because shell expansion doesn't work in JSON format
#Shell expansion is needed for the ${HOST} variable.
CMD java -Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.rmi.port=9010 \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.local.only=false \
-Djava.rmi.server.hostname=${HOST} \
Main

2

FWIW,这就是我将VisualVM附加到在macOS上运行的Docker容器内的Java进程的方式:

Main.java:

public class Main {
    public static void main(String args[]) throws Exception {
        while (true) {
            System.out.print("Hello ");
            System.out.println("world");
            Thread.sleep(1000);
        }
    }
}

Dockerfile:

FROM openjdk:11.0.2-slim
COPY Main.class /
WORKDIR /
ENTRYPOINT ["java", \
"-Dcom.sun.management.jmxremote=true", \
"-Dcom.sun.management.jmxremote.port=9010", \
"-Dcom.sun.management.jmxremote.local.only=false", \
"-Dcom.sun.management.jmxremote.authenticate=false", \
"-Dcom.sun.management.jmxremote.ssl=false", \
"-Dcom.sun.management.jmxremote.rmi.port=9010", \
"-Djava.rmi.server.hostname=localhost", \
"Main"]

编译Java代码,构建映像并像这样运行容器:

$ javac Main.java
$ docker build -t main .
$ docker run -p 9010:9010 -it main

然后使用JMX将VisualVM附加到localhost:9010


这个答案对我来说非常有效,给出示例Dockerfile使其变得容易。
保罗


0

感谢大家将我引向正确的方向。最终,我在更复杂的配置中工作:通过本地计算机上Windows 10下的Docker Desktop通过Kubernetes。

我的应用程序的配置:

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

豆荚的港口:

ports:
- name: jmx
  containerPort: 30491
  protocol: TCP

服务端口:

ports:
- name: jmx
  nodePort: 30491
  port: 9010
  protocol: TCP
  targetPort: jmx
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.