'How do I attach VisualVM to a simple Java process running in a Docker container
Actually I wanted a solution working for JEE containers, specifically for Glassfish, but after I tried many combinations of settings and did not succeed, I reduced the setup to the simplest possible case.
Here is my Hello World daemon started in a Docker container. I want to attach jconsole
or VisulaVM
to it. Everything is on the same machine.
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"]
Building: docker build -t hello-world-daemon .
Running: docker run -it --rm --name hwd hello-world-daemon
Questions:
- what JVM parameters should be added to
CMD
command line? - what ports should be exposed and published?
- what network mode should Docker container be using?
I do not show my failed attempts here so that correct answers will not be biased. This should be a pretty common problem, yet I could not find a working solution.
Update. Worked solution
This Dockerfile works
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
in combination with the docker run command
docker run -it --rm --name hwd -p 9010:9010 hello-world-daemon
VisualVM
connects via right click Local->Add JMX Connection, and then entering localhost:9010
, or through adding a remote host.
JConsole
connects via selecting a Remote process with localhost:9010
.
When defining the connection as remote, any interface listed by ifconfig
can be used. For instance, docker0
interface with address 172.17.0.1
works. The container's address 172.17.0.2
works too.
Solution 1:[1]
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".
Solution 2:[2]
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
Solution 3:[3]
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
Solution 4:[4]
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
Solution 5:[5]
To all of you that still suffer from an error like the below:
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
Solution 6:[6]
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:
Then it will appear the process:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Community |
Solution 2 | Rob van der Leek |
Solution 3 | Chris |
Solution 4 | Alterant |
Solution 5 | Robocide |
Solution 6 |