Все плюсы docker для приложения, уже много раз описывали на Хабре, как и его архитектуру.

Мы же решим практическую задачу по упаковке 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)


  1. Ranckont
    21.09.2016 15:01

    А можно сделать jvm образ без OS?


    1. igor_suhorukov
      21.09.2016 15:05

      Нельзя. jvm без ОС это baremetal прямо)


    1. aibar
      21.09.2016 22:57

      Можно, jvm-у на oracle.com нужен лишь glibc


  1. Ranckont
    21.09.2016 15:14

    В смысле образ jvm для OS Linux, без привязки к какому-то дистрибутиву


    1. igor_suhorukov
      21.09.2016 15:31

      Не получится работать образу без дистрибутива ОС


      1. frol
        21.09.2016 15:50
        +2

        Это не совсем так. Go приложения могут работать на scratch (нулевом) образе, то есть без дистрибутива совсем. Однако, JVM, в отличие от Go, требует libc, так что совсем без "системных файлов" не выйдет и, опять-таки, собрать образ с минимально необходимым списком файлов тоже можно, но в итоге это получается достаточно муторная работа, которая в случае с рассматриваемым вариантом базирования на Alpine Linux даст выигрыш в < 5МБ при том, что урезанная Oracle JDK весит ~160МБ, а полная — ~340МБ.


        1. igor_suhorukov
          21.09.2016 16:46

          Спасибо за уточнение! Go приложения статически линкуют все зависимости?


          1. frol
            21.09.2016 16:48
            +1

            Go реализовал свою собственную libc, на сколько мне известно (я мало имею отношения к Go, так что это на правах сведений из статей, которые я когда-то читал).


    1. Borz
      21.09.2016 15:34

      она и не привязана к ОС — использует минимальный docker-образ


    1. Borz
      21.09.2016 16:52

      например так — выбрана конкретная версия JVM и указана в качестве ОС Debian Jessie


  1. lkzcgfvf
    21.09.2016 20:51
    +1

    Отличная статья для начинающих.
    Не могли бы Вы еще осветить момент с открытием доступа извне по JMX к JVM внутри контейнера при условии что на хосте может быть запущено много таких контейнеров и хардкодить порты (com.sun.management.jmxremote.port + expose + маппинг через -p) для каждого контейнера не вариант (т.к. запускаются динамически из orchestration tool)
    а JMX/RMI реализация требует чтобы порт в контейнере и на хосте совпадали


    1. igor_suhorukov
      21.09.2016 21:16

      Контейнеры сами могут отправлять например в Elasticsearch.


    1. igor_suhorukov
      21.09.2016 22:22

      Кстати, как самое простое решение — парсинг вывода docker ps для каждого контейнера с динамическими портами (-P) и определение мэпинга заранее известного jmx порта контейнера на динамический порта хост-машины.


      1. lkzcgfvf
        22.09.2016 05:35

        такой вариант не работает из-за особенностей реализации JMX/RMI протокола. Именно поэтому я просил осветить данній момент. Реализация требует чтобы порт переданный на старте JVM (Dcom.sun.management.jmxremote.port), порт внутри контейнера и порт снаружи на хосте имели одно и то же значение. Иначе не работает. И получается что либо
        — порт надо статически хардкодить при каждом запуске контейнера — что есть проблема при запуске контейнера динамически в облаке
        — порт снаружи хоста нужно знать до старта JVM в контейнере и передавать его в контейнер как параметр, чтобы сделать expose и передать в Dcom.sun.management.jmxremote.port

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


        1. igor_suhorukov
          22.09.2016 10:08

          Честно — не сталкивался с такой потребностью и такой проблемой. Раз не устраивает агент+приложение в контейнере, который дампит стектрейсы/кучу на VOLUME.

          Что если VPN между контейнерами и тем хостом откуда пытаетесь все профилировать?


          1. lkzcgfvf
            22.09.2016 17:08

            VPN как вариант, спасибо за подсказку. Попробую сегодня.
            Не подскажите ключевые слова для гугления по теме «агент+приложение в контейнере, который дампит стектрейсы/кучу на VOLUME»?


            1. igor_suhorukov
              22.09.2016 17:39

              Если можете профилировать все без контейнеров и локально, то лучше делайте так!

              Но если хотите сложностей, то я бы сделал так:
              Например, используя jvm-tools -> stcap — семплирующий профайлер, с компактным форматом дампов стектрейсов.

              Heap dump элементарно получаем через JMX в том же контейнере. Даже без JMX можно сделать то же самое, если запустить команду jcmd в том же контейнере от того же пользователя что и jvm.

              И в том и в другом случае вам нужно указать путь в локальной файловой системе контейнера, куда будут записываться семплы и хипдамп. Этот же путь, вы должны указать в секции VOLUME, при создании контейнера.
              А при запуске контейнера указать какой директории контейнера соответствует директория хост-машины.


              1. lkzcgfvf
                22.09.2016 21:47
                +2

                я сложности не люблю :)) но у нас так построен процесс разработки. Все JVM микросервисы работают в контейнере которые живут в облаке. Код пишут и не тестируют на производительность одни, а troubleshoot на продакшене делают другие (в данном случае я). И мне приходится брать VisualVM и на живом проде разбираться чего тормозит.
                нагуглил такое решение — https://github.com/OlegIlyenko/jmx-firewall-friendly-agent Вроде все работает.

                Спасибо за ссылку на https://github.com/aragozin/jvm-tools
                очень полезный набор для анализа работающей JVM


                1. johndow
                  27.09.2016 20:08

                  Ещё как вариант: http://hawt.io/ + https://jolokia.org/agent/jvm.html


                  1. igor_suhorukov
                    28.09.2016 11:47

                    Вроде бы lkzcgfvf пытался объяснить для чего требуется JMX/RMI. Вариант про агент в контейнере я предлагал и это не подошло. jolokia — это JMX/HTTP


                    1. johndow
                      28.09.2016 12:06

                      Не заметил сразу, что ему нужен профайлинг…


  1. lkzcgfvf
    21.09.2016 21:59

    JMX нужен не метрики или логить снимать, а профайлить приложение, делать дамп


    1. igor_suhorukov
      21.09.2016 22:17

      Я имел в виду нечто похожее на agent-bond, (binary)

      Ничто не мешает объединить агент профайлера и приложение в одном контейнере и размещать собранную информацию либо по одному из путей VOLUME, либо отдавать ее другому контейнеру по p2p протоколу, например.
      Собирать профайлинг семплингом можно, в том числе с помощью jvm-tools -> stcap


    1. premier21
      22.09.2016 09:58
      +1

      А почему jmx не подходит для снятия метрик?


      1. lkzcgfvf
        22.09.2016 19:16

        я наверное неправильно выразил свою мысль. JMX прекрасно подходит для снятия метрик, просто нам он нужен не для этих целей. У нас задача другая — когда на продакшене чтото происходит с производительностью приложения — надо разобраться какая часть тормозит и почему. в 90% случаев хватает подключиться к JVM используя VisualVM и запустить его сэмплер.


  1. shuron
    21.09.2016 23:18

    Спасибо. Какраз сегодная строили…
    Вроде можно на стадии сборки добавить и корпоративный корневой сертификат так как keytool сохранен. Завтра будем пробовать…
    Это позволит доверять SSL/ соединениям к серверма подпоисаным корневиком.

    Но вот не придумали еще как быть с серверным сертификатом для SSL/TLS.
    Ны портируем готовое приложение в облако…
    Раньше сертификаты сервера устанавливались на машину а ява приложению просто передавался путь на кейстор, както-так.

    А теперь все будет динамичнее, и не хочется вводить привязку к хосту в принципе.
    Не совсем еще понятно куда класть сертфикаты сервера, особенно с учетом того что один и тот-же докер-имидж хочется тестировать на дев-энвайроменте и в продекшне…
    Один из радикальных вариантов это запекать оба сертификата в Docker image и подставлять в зависимости от конфиги (дев, прод).
    Другой вариант куда-то стучаться из стартующего контейнера шелскриптом перед запуском javы что-бы то куда постучались вернуло нужный серверный сертификат и ключ и он был положен в нужно еместо перед запуском апликухи…
    Вот такие две версии крутятся. Может у вас есть опыт или идеи?


    1. AotD
      22.09.2016 07:25

      Или поставить перед docker nginx и пусть он только и знает о хостах и сертификатах


      1. shuron
        22.09.2016 16:53

        А если все контейтенер то и nginx контейнер и стоит перед тойже проблемой.
        Ктому же трафик между nginx и явой тоже должен быть подписал сертификатом…


    1. igor_suhorukov
      22.09.2016 11:07

      Я бы копировал в контейнере сертификат перед стартом приложения из mount point указанной в VOLUMES. Соответственно при старте контейнера в dev окружении передавал бы директорию с dev сертификатом, а при prod с директорию с prod сертификатом.


      1. shuron
        22.09.2016 17:00

        Это интереснее в определенный сценариях это ок.
        Но мы планиеруем на будущее и как можно меньше хочется полагаться на информацию с хостов и провизионировать туда…
        Если использовать какой-нибудь kubernetes или у нас в данном случае AWS ECS то размещением контейнеров на хостах занимается scheduler

        Поэтому краткосрочно мы решим это с привязкой к хосту или переменной. Но в дальнешем похоже надо будет написать клиент который будет забирать себе сертефикат и прочее откуда-нибудь…


        1. igor_suhorukov
          22.09.2016 17:28

          Еще проще не копировать, а создавать линк. Не думаю что с mount для контейнера в таких инфраструктурах есть проблемы.


          1. shuron
            22.09.2016 17:53

            Караз есть, если все совать в контенеры, то глупо полагаться на то что контеенер приземлится на определенной машине…
            Более того имеет смысл использовать систему абстрагированую в принципе от маунтингда чего либо к одному хосту… в облаках это не делают…
            Представьте что у вас 1000 > контейнеров
            Но переде тем как переписать всю апплику хотелось найти что-то быстрое по середине как компромис. Пока у нас 3 контейнера, на след. неделе будет 7.
            В перспективе >200 в этом году.


            1. igor_suhorukov
              22.09.2016 17:57

              Так в облаке распределенные файловые системы маунтить надо, в крайнем случае старый энтерпрайзный подход с NFS


            1. igor_suhorukov
              22.09.2016 17:58

              etcd, consul ближе?


  1. edubrovka
    22.09.2016 22:00

    Я бы предложил использовать что-то из openjdk.


    Oracle так просто не разрешает распространять его продукты, если пользователь не принимает лицензию самостоятельно. И в данном случае Oracle предоставляет только openjdk образ, но не свой. Так что могут быть проблемы с лицензией.


    Собрать свой образ с java или oracle-xe у себя дома и пользоваться — можно. Распространять — скорее нет (если ответ короткий).