How do I attach VisualVM to a simple Java process running in a Docker container
JavaDockerJmxJava Problem Overview
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.
Java Solutions
Solution 1 - Java
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.
Solution 2 - Java
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 3 - Java
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 4 - Java
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 5 - Java
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 6 - Java
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 7 - Java
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: