Как подключить VisualVM к простому процессу Java, работающему в контейнере Docker

На самом деле я хотел, чтобы решение работало для JEE-контейнеров, особенно для Glassfish, но после того, как я попробовал много комбинаций настроек, но ничего не получилось, я сократил настройку до самого простого случая.

Вот мой Hello World демон, запущенный в контейнере Docker. Я хочу прикрепить 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;
      }
    }
  }
}

Dockerfile

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

Вопросы:

  • какие параметры JVM должны быть добавлены к CMD командная строка?
  • какие порты должны быть выставлены и опубликованы?
  • какой сетевой режим должен использовать контейнер Docker?

Я не показываю свои неудачные попытки здесь, чтобы правильные ответы не были предвзятыми. Это должно быть довольно распространенной проблемой, но я не мог найти рабочее решение.

Обновить. Отработанное решение

Этот Dockerfile работает

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, а затем вводя localhost:9010 или путем добавления удаленного хоста.

JConsole подключается через выбор удаленного процесса с localhost:9010,

При определении соединения как удаленного, любой интерфейс, перечисленный ifconfig может быть использован. Например, docker0 интерфейс с адресом 172.17.0.1 работает. Адрес контейнера 172.17.0.2 тоже работает

7 ответов

Решение

Сначала вы должны запустить ваше приложение с этими параметрами JVM:

-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

Тогда вы должны выставить порт для докера:

EXPOSE 9010

Также укажите привязку порта с помощью команды docker run:

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

После этого вы можете подключиться с помощью Jconsole к локальному порту 9010 и управлять запуском приложения в Docker.

FWIW, вот как я смог подключить VisualVM к процессу Java внутри контейнера Docker, работающего на 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"]

Скомпилируйте код Java, создайте образ и запустите контейнер следующим образом:

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

Затем прикрепите VisualVM с помощью JMX к localhost:9010

Я последовал другой SO ответ на аналогичный вопрос, и это сработало.

Я начал свой Java-процесс внутри контейнера, добавив эти параметры JVM:

-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

и запустил контейнер Docker, указав -e HOST_HOSTNAME=$HOSTNAME -p <port> к docker run команда.

Затем я смог получить доступ к этому удаленному приложению Java из моего локального JVisualVm, добавив удаленное соединение JMX ("Файл" > "Добавить соединение JMX...") и указав <dockerhostname>:<port> в поле "Соединение" и отметьте "Не требовать SSL-соединения".

Вы также можете использовать docker-compose для настройки своего контейнера. Шаги:

Создайте свой образ (Dockerfile)

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

Создайте свой образ

      docker build -t app .

Создать тег

      docker tag app:latest app:staging

Настройте свой 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

Порт 8050 — это тот, который я использую для запуска JVM, а 8051 устанавливает удаленное соединение. Я протестировал с помощью VisualVM, чтобы увидеть, могу ли я подключиться к JVM внутри контейнера, и это сработало. Вам просто нужно добавить соединение JMX:

Затем появится процесс:

Как ответил Энтони. Я должен был использовать -Djava.rmi.server.hostname опция java на моей машине Windows.

Только убедитесь, что не используете CMD в формате JSON в вашем Dockerfile, так как это не поддерживает расширение оболочки.

Пример Dockerfile:

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

Всем, кто все еще страдает от ошибки, подобной приведенной ниже:

В моем случае это было то, что я использовал в своем Docker YML разные сопоставления портов для портов:

например:

15100:9090

но, очевидно, в привязках портов вы должны назначить ОДИН порт для внешнего и внутреннего порта!

Ссылка: https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/5

Спасибо всем за то, что направили меня в правильном направлении. Наконец, я заставил его работать в более сложной конфигурации: Kubernetes через Docker Desktop под Windows 10 на локальном компьютере.

Конфигурация моего приложения:

-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

Порт пода:

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

Порт сервиса:

ports:
- name: jmx
  nodePort: 30491
  port: 9010
  protocol: TCP
  targetPort: jmx
Другие вопросы по тегам