Как подключить 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