103

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.

nolexa
  • 2,392
  • 2
  • 19
  • 19

7 Answers7

66

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.

eg04lt3r
  • 2,467
  • 14
  • 19
  • 8
    Nope.. 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 Jan 31 '16 at 00:59
  • 1
    why do you expose same port twice? – nolexa Jan 31 '16 at 01:03
  • 1
    Connect not to localhost, to your network interface. – eg04lt3r Jan 31 '16 at 01:22
  • You expose docker port to port on your network interface. – eg04lt3r Jan 31 '16 at 01:22
  • To which interface? If I connect on `docker0` interface `172.17.0.1`, or on `wlan1` interface `192.168.0.103`, I get *Connection faild: Connection refused* – nolexa Jan 31 '16 at 01:37
  • Understood. So, when run container specify this bind with -p 9010:9010 option and all will be fine. – eg04lt3r Jan 31 '16 at 02:00
  • Here's my command line `docker run -it --rm --name hwd -p 9010:9010 hello-world-daemon` The java process: `root 4398 2673 0 03:09 pts/29 00:00:00 java Main -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` Does not work – nolexa Jan 31 '16 at 02:15
  • Add this option --bind 127.0.0.1 and try to connect to localhost. – eg04lt3r Jan 31 '16 at 02:43
  • I'm sorry, I don't understand. Docker run does not have such option. – nolexa Jan 31 '16 at 03:56
  • 6
    It worked finally. My mistake was that I appended JVM options after the `Main` class name in the command line. All `-D` options were silently ignored by `java`. – nolexa Jan 31 '16 at 14:16
  • Same applies when using `-jar`. For some reason the `-Dcom.sun.management` parameters must be passed before the `-jar` option. Weird in my eyes. – Ethan Leroy Apr 18 '17 at 07:59
  • 2
    @EthanLeroy any arguments after `-jar foo.jar` are sent to the main function of the main class (defined as `Main-Class` in the JAR manifest); basically, arguments before `-jar` are for the JVM, arguments after `-jar` are for the program that's being run – kbolino Feb 28 '18 at 19:20
  • 1
    Beware: both `jmxremote.rmi.port` AND `jmxremote.port` need to be set. – johanwannheden Mar 10 '20 at 07:43
  • I was also missing `=true` for `-Dcom.sun.management.jmxremote` as suggests [this article](https://technicaldifficulties.io/2017/12/19/connecting-visualvm-to-a-local-docker-container-from-scratch). – StaNov Jul 13 '22 at 12:53
16

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

Community
  • 1
  • 1
Anthony O.
  • 22,041
  • 18
  • 107
  • 163
  • What is `$HOST_HOSTNAME` exactly? Is it host of machine running docker or something else? – Albert Bikeev Jun 09 '18 at 17:02
  • 1
    Yes, it is the hostname of the host running docker. It can be the result of the `hostname` command, so you could pass it to docker while launching your container like this: `-e HOST_HOSTNAME=\`hostname\`` – Anthony O. Jun 12 '18 at 09:15
  • Thank you, `-Djava.rmi.server.hostname` was the missing part for me – Paul Lysak Jan 17 '22 at 13:27
16

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

Rob van der Leek
  • 1,486
  • 15
  • 13
5

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

Guilherme Alencar
  • 1,243
  • 12
  • 21
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
Chris
  • 958
  • 8
  • 19
2

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

Robocide
  • 6,353
  • 4
  • 37
  • 41
2

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
Alterant
  • 177
  • 1
  • 2
  • 10