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

105

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

这里是我的Hello World守护程序在Docker容器中启动。我想连接jconsoleVisulaVM。所有东西都在同一台机器上。

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

Dockerfile

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

问题:

  • CMD 命令行应该添加哪些 JVM 参数?
  • 哪些端口应该被暴露和发布?
  • 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可以通过选择Remote process并输入localhost:9010来连接。
在将连接定义为远程连接时,可以使用ifconfig列出的任何接口。例如,具有地址172.17.0.1docker0接口可用。容器的地址172.17.0.2也可以使用。
7个回答

66

首先,您应使用以下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中运行的应用程序。


8
不行。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
1
连接时不要使用本地主机,而要使用您的网络接口。 - eg04lt3r
6
最终它运行成功了。我的错误在于我将 JVM 选项追加到了命令行中 Main 类名之后。所有 -D 选项都被 java 静默忽略了。 - nolexa
2
@EthanLeroy 在 -jar foo.jar 后的任何参数都会被发送到主类中的主函数(在 JAR 清单中定义为 Main-Class);基本上,-jar 之前的参数是针对 JVM 的,而 -jar 之后的参数是针对正在运行的程序的。 - kbolino
显示剩余9条评论

16

我跟随了StackOverflow上对类似问题的另一个回答,并且它起作用了。

我通过添加这些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连接”,从本地的JVisualVm访问这个远程Java应用程序。


$HOST_HOSTNAME 是什么?它是运行 Docker 的机器的主机名还是其他什么东西? - Albert Bikeev
1
是的,它是运行Docker的主机的主机名。它可以是hostname命令的结果,因此您可以在启动容器时将其传递给Docker,如下所示:-e HOST_HOSTNAME=\hostname`` - Anthony O.
谢谢,-Djava.rmi.server.hostname 对我来说是缺失的部分。 - Paul Lysak

16

顺便提一下,以下是我如何在运行在macOS上的Docker容器内部附加VisualVM到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 示例使它变得容易。 - Paul
感谢您提供的DockerFile示例,这个例子真是救了我一命。 - Andrés Planás

5
您也可以使用docker-compose来设置容器。步骤如下:
创建您的镜像(Dockerfile)
FROM openjdk:11
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp

打造你的形象

docker build -t app .

创建标签
docker tag app:latest app:staging

配置docker-compose

app:
    image: app:staging
    ports:
      - 8050:8050
      - 8051:8051
    volumes:
      - ./target/app.jar:/usr/src/myapp/app.jar
    entrypoint:
      - java 
      - -Dspring.profiles.active=local 
      - -Dcom.sun.management.jmxremote=true
      - -Dcom.sun.management.jmxremote.port=8051
      - -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=8051
      - -Djava.rmi.server.hostname=localhost
      - -jar 
      - ./app.jar

我正在使用8050端口来运行JVM,而8051端口用于远程连接。我已经使用VisualVM进行测试,以查看是否可以连接到容器内的JVM,并且测试成功。你只需要添加一个JMX连接即可:

添加 Jmx 连接

然后进程会出现如下图所示:

在 Docker 容器内运行 JVM


3

Anthony所述,我在我的Windows机器上必须使用-Djava.rmi.server.hostname 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
感谢大家为我指明正确的方向。最终,我在更复杂的配置中让它工作了:在本地机器上通过Windows 10下的Docker桌面版使用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

Pod的端口:

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

服务的端口:

ports:
- name: jmx
  nodePort: 30491
  port: 9010
  protocol: TCP
  targetPort: jmx

2

自2019年12月以来,您是否找到了解决此问题的方法?还是问题仍然存在? - vab2048

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接