Wie hänge ich VisualVM an einen einfachen Java-Prozess an, der in einem Docker-Container ausgeführt wird?

72

Eigentlich wollte ich eine Lösung für JEE-Container, speziell für Glassfish, aber nachdem ich viele Kombinationen von Einstellungen ausprobiert hatte und keinen Erfolg hatte, reduzierte ich das Setup auf den einfachsten Fall.

Hier ist mein Hello World-Daemon, der in einem Docker-Container gestartet wurde. Ich möchte anhängen jconsoleoder VisulaVMdaran. Alles ist auf derselben Maschine.

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

Gebäude: docker build -t hello-world-daemon .

Laufen: docker run -it --rm --name hwd hello-world-daemon

Fragen:

  • Welche JVM-Parameter sollten zur CMDBefehlszeile hinzugefügt werden?
  • Welche Ports sollten verfügbar gemacht und veröffentlicht werden?
  • Welchen Netzwerkmodus sollte der Docker-Container verwenden?

Ich zeige meine fehlgeschlagenen Versuche hier nicht, damit die richtigen Antworten nicht verzerrt werden. Dies sollte ein ziemlich häufiges Problem sein, aber ich konnte keine funktionierende Lösung finden.

Aktualisieren. Gearbeitete Lösung

Diese Docker-Datei funktioniert

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 Kombination mit dem Docker-Befehl run

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

VisualVMDie Verbindung wird hergestellt, indem Sie mit der rechten Maustaste auf Lokal-> JMX-Verbindunglocalhost:9010 hinzufügen klicken und dann eingeben oder einen Remote-Host hinzufügen.

JConsoleverbindet sich über die Auswahl eines Remote-Prozesses mit localhost:9010.

Wenn Sie die Verbindung als remote definieren, kann jede von aufgeführte Schnittstelle ifconfigverwendet werden. Zum Beispiel funktioniert die docker0Schnittstelle mit der Adresse 172.17.0.1. Die Adresse des Containers 172.17.0.2funktioniert auch.

Nolexa
quelle

Antworten:

53

Zuerst sollten Sie Ihre Anwendung mit den folgenden JVM-Parametern ausführen:

-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

Dann sollten Sie den Port für Docker verfügbar machen:

EXPOSE 9010

Geben Sie außerdem die Portbindung mit dem Docker-Befehl run an:

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

Danach können Sie mit Jconsole eine Verbindung zum lokalen 9010-Port herstellen und die in Docker ausgeführte Anwendung verwalten.

eg04lt3r
quelle
7
Nein .. 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
Warum legen Sie denselben Port zweimal frei?
Nolexa
Stellen Sie keine Verbindung zu localhost her, sondern zu Ihrer Netzwerkschnittstelle.
eg04lt3r
5
Es hat endlich funktioniert. Mein Fehler war, dass ich JVM-Optionen nach dem MainKlassennamen in der Befehlszeile angehängt habe . Alle -DOptionen wurden von stillschweigend ignoriert java.
Nolexa
1
@EthanLeroy alle nachfolgenden Argumente -jar foo.jarwerden an die Hauptfunktion der Hauptklasse gesendet (definiert wie Main-Classim JAR-Manifest); Grundsätzlich sind Argumente vor -jarfür die JVM, Argumente nach -jarsind für das Programm, das ausgeführt wird
kbolino
13

Ich folgte einer anderen SO-Antwort auf eine ähnliche Frage und es funktionierte.

Ich habe meinen Java-Prozess im Container gestartet, indem ich die folgenden JVM-Parameter hinzugefügt habe:

-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

und startete den Docker-Container, -e HOST_HOSTNAME=$HOSTNAME -p <port>der den docker runBefehl angibt .

Dann konnte ich von meinem lokalen JVisualVm aus auf diese Remote-Java-App zugreifen, indem ich eine Remote-JMX-Verbindung hinzufügte ("Datei"> "JMX-Verbindung hinzufügen ...") und <dockerhostname>:<port>in der Eingabe "Verbindung" spezifizierte und " Keine SSL-Verbindung erforderlich ".

Anthony O.
quelle
Was ist $HOST_HOSTNAMEgenau? Ist es ein Host, auf dem Docker ausgeführt wird, oder etwas anderes?
Albert Bikeev
1
Ja, es ist der Hostname des Hosts, auf dem Docker ausgeführt wird. Es kann das Ergebnis des hostnameBefehls sein, so dass Sie es an Docker übergeben können, während Sie Ihren Container wie folgt starten:-e HOST_HOSTNAME=`hostname`
Anthony O.
3

Wie von Anthony beantwortet . Ich musste die -Djava.rmi.server.hostnameJava-Option auf meinem Windows-Computer verwenden.

Stellen Sie nur sicher, dass Sie das CMD nicht im JSON-Format in Ihrer Docker-Datei verwenden, da dies keine Shell-Erweiterung unterstützt.

Dockerfile-Beispiel:

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
quelle
2

FWIW, so konnte ich VisualVM an einen Java-Prozess in einem Docker-Container anhängen, der unter macOS ausgeführt wird:

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

Kompilieren Sie den Java-Code, erstellen Sie das Image und führen Sie den Container folgendermaßen aus:

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

Verbinden Sie dann VisualVM mit JMX mit localhost: 9010

Rob van der Leek
quelle
Diese Antwort funktionierte perfekt für mich und das Beispiel Dockerfile machte es einfach.
Paul
1

An alle, die immer noch unter einem Fehler wie dem folgenden leiden:

Geben Sie hier die Bildbeschreibung ein

In meinem Fall habe ich in meinem Docker YML verschiedene Portzuordnungen für die Ports verwendet:

z.B:

15100:9090

Aber anscheinend müssen Sie in Ihren Portbindungen den gleichen Port für den externen Port und den internen Port zuweisen !

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

Robocide
quelle
Haben Sie seit Dezember 2019 eine Problemumgehung für dieses Problem gefunden? Oder bleibt es immer noch bestehen?
vab2048
0

Vielen Dank an Sie alle, dass Sie mich in die richtige Richtung geleitet haben. Endlich habe ich es in einer komplexeren Konfiguration zum Laufen gebracht: Kubernetes über Docker Desktop unter Windows 10 auf dem lokalen Computer.

Die Konfiguration meiner App:

-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

Pods Hafen:

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

Port des Dienstes:

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