Мы же решим практическую задачу по упаковке jvm приложения и получим контейнер с миниатюрным Linux, JDK и нашим приложением, который опубликуем на hub.docker.com и сможем запускать где угодно.
Исходное приложение
Если ваше приложение для jvm состоит из более чем одного файла и образ нужно генерировать регулярно/автоматически, то можно интегрировать процесс сборки в maven с помощью плагина com.spotify:docker-maven-plugin. Если же вы используете Gradle, то se.transmode.gradle:gradle-docker вам поможет сделать сборку docker образа. Пример для maven опубликовал на github.
В публикации используем jar из центрального maven репозитария. Это своя сборка груви, которая примечательна упакованным aether provide для разрешения зависимостей Grape и расширенной поддержкой протоколов для java.net.URL. Для пытливых умов:
Для меня это удобный инструмент, который часто использую. Как пример — загрузка jvm агента в запущенную JVM, инсталятор для crate.io, пример парсера сайта…
Теперь groovy-grape-aether доступен и в docker образе suhorukov/docker-groovy. В подобный образ вы может упаковать ваше приложение для jvm.
Dockerfile
Описывает на основе какого существующего образа и с помощью каких команд сформировать новый образ.
Воспользуемся образом frolvlad/alpine-oraclejdk8, с благодарностью frol за его труд! Этот образ на основе Alpine Linux image + glibc и Oracle JDK 8u102.14. Достаточно компактный, по сравнению с образами на основе на debian/ubuntu. Как альтернатива возможен популярный образ anapsix/alpine-java.
FROM frolvlad/alpine-oraclejdk8
EXPOSE 8080
ENV GROOVY_VERSION=2.4.5.4
RUN mkdir "/usr/lib/groovy" && wget "http://repo1.maven.org/maven2/com/github/igor-suhorukov/groovy-grape-aether/$GROOVY_VERSION/groovy-grape-aether-$GROOVY_VERSION.jar" -O /usr/lib/groovy/groovy-grape-aether.jar
ENTRYPOINT ["java","-jar","/usr/lib/groovy/groovy-grape-aether.jar"]
CMD ["--help"]
Здесь я указал что порт 8080 будет слушать приложение внутри контейнера. При запуске контейнера можно указать параметр -P и тогда он будет привязан к динамическому порту хост-машины, либо параметр -p и можно указать какой порт хоста соответствует порту контейнера.
ENTRYPOINT определяет какая команда с параметрами будет запущена при старте контейнера. Для команды возможно указать с помощью CMD параметры по-умолчанию.
Собираем образ локально
Запускаем в той же директории, где находится наш Dockerfile команду
docker build -t docker-groovy .
Публикация в hub.docker.com
Этот раздел можете пропустить, если у вас не opensource проект и вам не нужно делиться образом со всем миром.
Публикуем Dockerfile на github
В своей учетной записи на github выбираем New Repository и помещаем туда подготовленный Dockerfile. В этом примере используется репозитарий docker-groovy.
Настраиваем сборку в hub.docker.com
Создаем бесплатный аккаунт на главной странице hub.docker, заполнив форму и нажав «Sign Up». Обязательно активируем его с помощью письма в email.
В настройках аккаунта заходим во вкладку «Linked Accounts & Services» и настраиваем «Link Github» на ваш github аккаунт( другой вариант Bitbucket аккаунт).
В меню вверху страници выбираем Create->Create Automated Build, затем жмем«Create Auto-build Github», выбираем репозитарий и указываем где в репозитарии Dockerfile. При следующем push в репозитарий на github сборка автоматичеки запустится. Можно запустить и сборку вручную.
В результате всех вышеописанных действий получился такой проект на hub.docker.com.
Загрузка и использование контейнера
Перед использованием, получим образ из hub.docker.
docker pull suhorukov/docker-groovy
И запустим контейнер с параметром-скриптом gitblit.groovy.
docker run -d -p 8080:8080 suhorukov/docker-groovy https://raw.githubusercontent.com/igor-suhorukov/git-configuration/master/gitblit.groovy
В случае, если для приложения нужен доступ к внешним для контейнера ресурсам в файловой системе (директории хост-машины, NFS, распределенной файловой системы) то нужно указать точки монтирования при создании образа в секции VOLUME в Dockerfile. Если же нужно распределенное выполнение и орекстрация контейнеров — Kubernetes / Mesos / Swarm / fabric8.io / Rancher более подходящие для этого технологии которые работают с docker.
На примере jvm приложения мы создали и опубликовали миниатюрный образ на hub.docker.com, после чего запустили docker контейнер с параметрами приложения.
Комментарии (35)
Ranckont
21.09.2016 15:14В смысле образ jvm для OS Linux, без привязки к какому-то дистрибутиву
igor_suhorukov
21.09.2016 15:31Не получится работать образу без дистрибутива ОС
frol
21.09.2016 15:50+2Это не совсем так. Go приложения могут работать на
scratch
(нулевом) образе, то есть без дистрибутива совсем. Однако, JVM, в отличие от Go, требует libc, так что совсем без "системных файлов" не выйдет и, опять-таки, собрать образ с минимально необходимым списком файлов тоже можно, но в итоге это получается достаточно муторная работа, которая в случае с рассматриваемым вариантом базирования на Alpine Linux даст выигрыш в < 5МБ при том, что урезанная Oracle JDK весит ~160МБ, а полная — ~340МБ.igor_suhorukov
21.09.2016 16:46Спасибо за уточнение! Go приложения статически линкуют все зависимости?
frol
21.09.2016 16:48+1Go реализовал свою собственную libc, на сколько мне известно (я мало имею отношения к Go, так что это на правах сведений из статей, которые я когда-то читал).
lkzcgfvf
21.09.2016 20:51+1Отличная статья для начинающих.
Не могли бы Вы еще осветить момент с открытием доступа извне по JMX к JVM внутри контейнера при условии что на хосте может быть запущено много таких контейнеров и хардкодить порты (com.sun.management.jmxremote.port + expose + маппинг через -p) для каждого контейнера не вариант (т.к. запускаются динамически из orchestration tool)
а JMX/RMI реализация требует чтобы порт в контейнере и на хосте совпадалиigor_suhorukov
21.09.2016 22:22Кстати, как самое простое решение — парсинг вывода docker ps для каждого контейнера с динамическими портами (-P) и определение мэпинга заранее известного jmx порта контейнера на динамический порта хост-машины.
lkzcgfvf
22.09.2016 05:35такой вариант не работает из-за особенностей реализации JMX/RMI протокола. Именно поэтому я просил осветить данній момент. Реализация требует чтобы порт переданный на старте JVM (Dcom.sun.management.jmxremote.port), порт внутри контейнера и порт снаружи на хосте имели одно и то же значение. Иначе не работает. И получается что либо
— порт надо статически хардкодить при каждом запуске контейнера — что есть проблема при запуске контейнера динамически в облаке
— порт снаружи хоста нужно знать до старта JVM в контейнере и передавать его в контейнер как параметр, чтобы сделать expose и передать в Dcom.sun.management.jmxremote.port
оба варианта не очень…
я не силен в джаве, может есть возможность запуска в контейнере промежуточного компонента который бы работал внутри контейнера с JVM по одному порту а наружу отдавал по другому.
igor_suhorukov
22.09.2016 10:08Честно — не сталкивался с такой потребностью и такой проблемой. Раз не устраивает агент+приложение в контейнере, который дампит стектрейсы/кучу на VOLUME.
Что если VPN между контейнерами и тем хостом откуда пытаетесь все профилировать?lkzcgfvf
22.09.2016 17:08VPN как вариант, спасибо за подсказку. Попробую сегодня.
Не подскажите ключевые слова для гугления по теме «агент+приложение в контейнере, который дампит стектрейсы/кучу на VOLUME»?igor_suhorukov
22.09.2016 17:39Если можете профилировать все без контейнеров и локально, то лучше делайте так!
Но если хотите сложностей, то я бы сделал так:
Например, используя jvm-tools -> stcap — семплирующий профайлер, с компактным форматом дампов стектрейсов.
Heap dump элементарно получаем через JMX в том же контейнере. Даже без JMX можно сделать то же самое, если запустить команду jcmd в том же контейнере от того же пользователя что и jvm.
И в том и в другом случае вам нужно указать путь в локальной файловой системе контейнера, куда будут записываться семплы и хипдамп. Этот же путь, вы должны указать в секции VOLUME, при создании контейнера.
А при запуске контейнера указать какой директории контейнера соответствует директория хост-машины.lkzcgfvf
22.09.2016 21:47+2я сложности не люблю :)) но у нас так построен процесс разработки. Все JVM микросервисы работают в контейнере которые живут в облаке. Код пишут и не тестируют на производительность одни, а troubleshoot на продакшене делают другие (в данном случае я). И мне приходится брать VisualVM и на живом проде разбираться чего тормозит.
нагуглил такое решение — https://github.com/OlegIlyenko/jmx-firewall-friendly-agent Вроде все работает.
Спасибо за ссылку на https://github.com/aragozin/jvm-tools
очень полезный набор для анализа работающей JVMjohndow
27.09.2016 20:08Ещё как вариант: http://hawt.io/ + https://jolokia.org/agent/jvm.html
igor_suhorukov
28.09.2016 11:47Вроде бы lkzcgfvf пытался объяснить для чего требуется JMX/RMI. Вариант про агент в контейнере я предлагал и это не подошло. jolokia — это JMX/HTTP
lkzcgfvf
21.09.2016 21:59JMX нужен не метрики или логить снимать, а профайлить приложение, делать дамп
igor_suhorukov
21.09.2016 22:17Я имел в виду нечто похожее на agent-bond, (binary)
Ничто не мешает объединить агент профайлера и приложение в одном контейнере и размещать собранную информацию либо по одному из путей VOLUME, либо отдавать ее другому контейнеру по p2p протоколу, например.
Собирать профайлинг семплингом можно, в том числе с помощью jvm-tools -> stcap
premier21
22.09.2016 09:58+1А почему jmx не подходит для снятия метрик?
lkzcgfvf
22.09.2016 19:16я наверное неправильно выразил свою мысль. JMX прекрасно подходит для снятия метрик, просто нам он нужен не для этих целей. У нас задача другая — когда на продакшене чтото происходит с производительностью приложения — надо разобраться какая часть тормозит и почему. в 90% случаев хватает подключиться к JVM используя VisualVM и запустить его сэмплер.
shuron
21.09.2016 23:18Спасибо. Какраз сегодная строили…
Вроде можно на стадии сборки добавить и корпоративный корневой сертификат так как keytool сохранен. Завтра будем пробовать…
Это позволит доверять SSL/ соединениям к серверма подпоисаным корневиком.
Но вот не придумали еще как быть с серверным сертификатом для SSL/TLS.
Ны портируем готовое приложение в облако…
Раньше сертификаты сервера устанавливались на машину а ява приложению просто передавался путь на кейстор, както-так.
А теперь все будет динамичнее, и не хочется вводить привязку к хосту в принципе.
Не совсем еще понятно куда класть сертфикаты сервера, особенно с учетом того что один и тот-же докер-имидж хочется тестировать на дев-энвайроменте и в продекшне…
Один из радикальных вариантов это запекать оба сертификата в Docker image и подставлять в зависимости от конфиги (дев, прод).
Другой вариант куда-то стучаться из стартующего контейнера шелскриптом перед запуском javы что-бы то куда постучались вернуло нужный серверный сертификат и ключ и он был положен в нужно еместо перед запуском апликухи…
Вот такие две версии крутятся. Может у вас есть опыт или идеи?igor_suhorukov
22.09.2016 11:07Я бы копировал в контейнере сертификат перед стартом приложения из mount point указанной в VOLUMES. Соответственно при старте контейнера в dev окружении передавал бы директорию с dev сертификатом, а при prod с директорию с prod сертификатом.
shuron
22.09.2016 17:00Это интереснее в определенный сценариях это ок.
Но мы планиеруем на будущее и как можно меньше хочется полагаться на информацию с хостов и провизионировать туда…
Если использовать какой-нибудь kubernetes или у нас в данном случае AWS ECS то размещением контейнеров на хостах занимается scheduler
Поэтому краткосрочно мы решим это с привязкой к хосту или переменной. Но в дальнешем похоже надо будет написать клиент который будет забирать себе сертефикат и прочее откуда-нибудь…igor_suhorukov
22.09.2016 17:28Еще проще не копировать, а создавать линк. Не думаю что с mount для контейнера в таких инфраструктурах есть проблемы.
shuron
22.09.2016 17:53Караз есть, если все совать в контенеры, то глупо полагаться на то что контеенер приземлится на определенной машине…
Более того имеет смысл использовать систему абстрагированую в принципе от маунтингда чего либо к одному хосту… в облаках это не делают…
Представьте что у вас 1000 > контейнеров
Но переде тем как переписать всю апплику хотелось найти что-то быстрое по середине как компромис. Пока у нас 3 контейнера, на след. неделе будет 7.
В перспективе >200 в этом году.igor_suhorukov
22.09.2016 17:57Так в облаке распределенные файловые системы маунтить надо, в крайнем случае старый энтерпрайзный подход с NFS
edubrovka
22.09.2016 22:00Я бы предложил использовать что-то из openjdk.
Oracle так просто не разрешает распространять его продукты, если пользователь не принимает лицензию самостоятельно. И в данном случае Oracle предоставляет только openjdk образ, но не свой. Так что могут быть проблемы с лицензией.
Собрать свой образ с java или oracle-xe у себя дома и пользоваться — можно. Распространять — скорее нет (если ответ короткий).
Ranckont
А можно сделать jvm образ без OS?
igor_suhorukov
Нельзя. jvm без ОС это baremetal прямо)
aibar
Можно, jvm-у на oracle.com нужен лишь glibc