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

实际上,我想要一个可以用于 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;
}
}
}
}

文件夹

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 容器应该使用什么网络模式?

我不会在这里展示我失败的尝试,这样正确的答案就不会有偏见。这应该是一个非常普遍的问题,但是我找不到一个可行的解决方案。

更新,成功的解决方案

这个文件管用

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 本地-> 添加 JMX 连接进行连接,然后进入 localhost:9010,或者通过添加远程主机进行连接。

JConsole通过选择 远程处理localhost:9010连接。

当将连接定义为远程连接时,可以使用 ifconfig列出的任何接口。例如,带有地址 172.17.0.1docker0接口可以工作。集装箱的地址 172.17.0.2也可以工作。

43753 次浏览

At first you should run you application with these JVM params:

-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

Then you should expose port for docker:

EXPOSE 9010

Also specify port binding with docker run command:

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

After that you can connect with Jconsole to local 9010 port and manage application run in Docker.

I followed an other SO response to a similar question and it worked.

I started my Java process inside the container by adding those JVM params:

-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

and started the Docker container specifying -e HOST_HOSTNAME=$HOSTNAME -p <port> to the docker run command.

Then I've been able to access to this remote Java app from my local JVisualVm by adding a remote JMX connection ("File" > "Add a JMX Connection...") and specifying <dockerhostname>:<port> in the "Connection" input, and checking "Do not require SSL connection".

As answered by Anthony. I had to use the -Djava.rmi.server.hostname java option on my Windows machine.

Just be sure not to use the CMD in JSON format in your Dockerfile as this doesn't support shell expansion.

Dockerfile example:

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

To all of you that still suffer from an error like the below:

enter image description here

In my case it was that i used in my Docker YML different port mappings for the ports:

e.g:

15100:9090

but apparently in your port bindings you must assign the SAME port for external port and internal port !

Reference: https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/5

FWIW, this is how I was able to attach VisualVM to a Java process inside a Docker container running on macOS:

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"]

Compile the Java code, build the image and run the container like this:

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

Then attach VisualVM using JMX to localhost:9010

Thanks to all of you for routing me to the right direction. Finally I got it working in more complex config: Kubernetes via Docker Desktop under Windows 10 on local machine.

My app's config:

-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's port:

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

Service's port:

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

You can also use docker-compose to set up your container. Steps:

Create your image (Dockerfile)

FROM openjdk:11
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp

Build your image

docker build -t app .

Create a tag

docker tag app:latest app:staging

Set up your 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

Port 8050 is the one I am using to run the JVM and the 8051 makes the remote connection. I have tested using VisualVM to see if I can connect to the JVM inside the container and it worked. You just need to Add a JMX connection:

Add Jmx connection

Then it will appear the process:

Jvm running inside docker