Удивительно, но факт — дистрибьюция Java приложений в 21 веке по прежнему огромный костыль. Разработчики до сих пор придумывают способы вроде rsync/copy-paste/wget для установки java приложений на сервер. И только монструозные enterprise production ready платформы иногда позволяют сделать чуть больше — откатить приложение на предыдущую версию. В этой статье я хотел бы рассказать о доступном и простом способе организации дистрибьюции.
deb и apt
В мире существует множество действительно гигантских репозиториев приложений и инструментов по их дистрибьюции. Самые большие, по ощущениям, — это AppStore, Google Play, Debian/Ubuntu репозитории и CentOS/Fedora YUM репозитории. Например в Ubuntu репозитории для версии 15.04 содержится около 90000 приложений (без учета различных версий). Почему бы не воспользоваться провернной временем системой и для дистрибьюции Java приложений? Тем более, что:
— большинство серверов и так используют Debian/Ubuntu
— проверенный временем инструмент: первый релиз был 16 лет назад
— нативная поддержка в операционной системе
Для начала немного о системе дистрибьюции приложений в Debian/Ubuntu. Она состоит из двух основных частей:
— deb пакеты
— apt (Advanced Package Tool) инструменты
deb пакеты
deb это бинарный дистрибутив приложения. Он состоит из 3 основных частей:
— метаинформация. Производитель, версия, зависимости на другие пакеты (очень похоже на maven), описание и пр.
— непосредственно приложение. .tar.gz архив
— (опционально) скрипты, которые будут выполняться во время установки
Структура .tar.gz архива может быть абсолютно произвольной. Однако, чтобы Ваше приложение было похоже на все остальные приложения системы, оно должно следовать структуре каталогов Debian/Ubuntu:
— /etc — конфиги
— /etc/init.d/ — скрипты запуска демонов
— /usr/bin — исполняемые файлы
— /usr/lib — библиотеки
— /var/log — логи
В зависимости от Вашего приложения каталоги могут немного отличаться, но общая структура надеюсь понятна.
Еще одной важной особенностью deb пакетов является возможность запускать скрипты во время установки. Эти скрипты тоже хранятся в deb пакете и имеют стандартное именование. Каждый скрипт может выполняться в определенную фазу установки. Установка пакета делится на несколько фаз:
— preinst
— inst
— postinst
— prerm
— rm
— postrm
Существует множество различных промежуточных фаз и различные комбинации состояния инсталляции. Нас они мало интересуют, но тем кто хочет разобраться можно почитать официальную документацию. Обычно эти скрипты настраивают ротацию и архивирование логов, задают начальные значения конфигураций (например, root-пароль для mysql). Если же у вас конечное бизнес-приложение, то лучше взять какое-нибудь нормальное средство автоматизации вроде Ansible, Chief, Puppet.
apt
apt — это набор инструментов для работы с deb пакетами. Он позволяет:
— конфигурировать репозитории и работать с ними: добавлять, удалять, менять, обновлять индекс
— управлять пакетами: устанавливать, удалять, обновлять, искать
apt репозиторий в упрощенном виде — это HTTP сервер, который раздает deb пакеты. У него есть индекс (файл), который доступен по стандартному пути и непосредственно бинарники, путь к которым находится в индексе.
Связывая все воедино
После того, как стала понятна примерная схема работы связки deb + apt, можно попробовать их съинтегрировать. Примерная схема такая:
— создания deb пакета в фазе package
— публикация получившегося пакета в фазе deploy
Для этого есть несколько maven плагинов.
jdeb
Схема его работы достаточно проста:
— перечислить файлы и директории, которые попадут в результирующий .tar.gz архив
— указать пермиссии
Более полная документация о возможностях плагина можно узнать на официальной странице.
apt-maven-plugin
Работает с репозиторием заданным в distributionManagement как с apt репозиторием, а не maven репозиторием. Хотя ничего не мешает их использовать одновременно под одним url. Их layout'ы совместимы между собой.
Пример конфигурации выглядит следующим образом:
<plugin>
<groupId>com.aerse.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>deploy</id>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<configuration>
<component>main</component>
<codename>strepo</codename>
</configuration>
</plugin>
И секция distributionManagement (ничего необычного):
<distributionManagement>
<repository>
<id>maven-release-repo</id>
<url>http://example.com/maven</url>
</repository>
</distributionManagement>
После выполнения фазы deploy, example.com/maven станет еще и apt репозиторием. И можно смело писать:
sudo add-apt-repository "deb http://example.com/maven strepo main"
sudo apt-get update
sudo apt-get install <artifactId>
Немного любимого enterprise
Манца любого java-enterprise разработчика звучит следующим образом:
— security
— stability
— high availability production ready platform
Все это отлично решается, если устроить apt репозиторий из самого популярного хостинга для разработчиков: s3. Вкупе с cloudfront, он даёт гарантию 99.9% надёжности и географическую распределённость.
Делается это опять же достаточно просто. Надо подключить плагин для работы с s3:
<build>
<extensions>
<extension>
<groupId>org.springframework.build</groupId>
<artifactId>aws-maven</artifactId>
<version>5.0.0.RELEASE</version>
</extension>
</extensions>
...
</build>
Поменять url в секции distributionManagement на имя bucket'a:
<distributionManagement>
<repository>
<id>maven-release-repo</id>
<url>s3://example.bucket</url>
</repository>
</distributionManagement>
И настроить доступ к вашему bucket'у:
<servers>
<server>
<id>maven-release-repo</id>
<username>apikey</username>
<password>apisecret</password>
</server>
...
</servers>
На конечных серверах для доступа к такому репозиторию существует специальный плагин: apt-transport-s3. К сожалению его еще нет в официальных репозиториях, поэтому необходимо добавить вручную один из репозиториев, где он содержится:
sudo add-apt-repository ppa:leonard-ehrenfried/apt-transport-s3
sudo apt-get install apt-transport-s3
После чего можно уже указывать наш s3 репозиторий:
sudo add-apt-repository "deb s3://apikey:apisecret@s3.amazonaws.com/example.bucket strepo main"
Итого
В результате всех манипуляций установка приложения:
— mvn clean deploy
На любом Debian/Ubuntu сервере в любой точке мира:
— apt-get update
— apt-get install artifactId
Комментарии (43)
nehaev
18.08.2015 16:37+1А насколько легко предложенный способ позволяет иметь несколько версий приложения на одном сервере?
eaa
18.08.2015 16:56Вообще при установке .deb можно задать каталог, куда ставить
--instdir=dir Change default installation directory which refers to the directory where packages are to be installed. instdir is also the directory passed to chroot(2) before running package's installation scripts, which means that the scripts see instdir as a root directory. (Defaults to /)
Вообще, это вопрос архитектуры, должно ли вообще быть несколько версий, как они будут взаимодействовать между собой, и вопрос отнюдь не ограничивается только системой упаковки.
Например, несколько версий апача на одной машине… как они порты делить будут между собой и вообще зачем такой изврат? Может вообще контейнерами делать?nehaev
18.08.2015 18:19> Вообще при установке .deb можно задать каталог, куда ставить
Т.е. можно одновременно иметь в системе две версии одного пакета в разных instdir, правильно?
> Например, несколько версий апача на одной машине…
Что за апач? Мы вроде про джаву говорим…
> Вообще, это вопрос архитектуры, должно ли вообще быть несколько версий, как они будут взаимодействовать между собой, и вопрос отнюдь не ограничивается только системой упаковки.
Ну, например, в случае с rsync/wget или деплоем с дженкинса по SSH, вопрос с системой упаковки вообще не стоит.eaa
18.08.2015 19:51-11. Да, можно
2. Не важно, апач или нет, вообще без разницы, но если Вы настаиваете, возьмите два сервиса на java, которые слушают один и тот же порт и подумайте, как быть с разными версиями одного сервсиса при их одновременной установке. И таких проблем надо решать множество и сборка — ото очень малая часть этих проблем. Как собирать — вытекает из архитектуры приложения в целом.
3. Вы еще предложите конечному пользователю из исходников собирать по несколько часов, да еще вручную, запуская javac, тогда не только упаковка не нужна, тогда и maven/ant не нужны вообще.shuron
20.08.2015 23:24Это гемор. Яву дистрибьютить в пакетных мэнеджерах было круто лет 5 назад.
Сейчас, в пору микросервисов, пакеты начинают упираться в новые границы… Контейнеры уже мэйнстрим… там одинаковый порт никого не пугает, даже захардкоденый. ;)dernasherbrezon
21.08.2015 15:56И как же докер решает проблему одного и того же порта? Проблема просто переносится уровнем выше. В каждом из двух контейнерах можно держать 80 порт, но как их bind'ить в хост системе?
shuron
07.09.2015 19:20Да конечно, но вам не надо думать он этом при разработке а при деплойменте…
А если вы деплоите в нормальный клауд то и там не надо… ;)
shuron
20.08.2015 23:21Отличный вопрос…
Это фундаментальное отличие пакетных менеджеров от контейнеров…
Если у вас столь динамичные требования то сразу смотрите например в сторону Докера…
voidnugget
18.08.2015 16:44+1Эм… ну вообщем-то Ынтырпрайс это конечно хорошо, но фраза
Разработчики до сих пор придумывают способы вроде rsync/copy-paste/wget для установки java приложений на сервер.
очень смутила.
С чего вы взяли что сейчас Разработчики для деплоя используют Java инфраструктуру?
Обычно это готовые системы оркестрации типа Puppet / Ansible / Saltstack / Chef.
На худой конец можно спокойно всё заскриптовать на groovy и покрыть интеграционными тестами.
Для того что бы откатить версию или сделать hot reload какого-то сервиса без даунтайма: нужно создать дочерний и сделать dup() syscall для текущих открытых сокетов (это позволит обработать флаг FD_CLOEXEC) и передать в SIGHUP родителю afaik, но могу ошибаться с сигналом. Непосредственно к языку или платформе фича hot reload'a и отката в продакшене не имеет прямого отношения — росли бы руки с нужного места, и было бы желание разобраться как оно работает…
Имхо, Ынтырпрайсовские java бутерброды ничего общего с понятием производительности не имеют.dernasherbrezon
18.08.2015 17:28+1- Puppet/Ansible/Sailstack/Chief ничего не знают о зависимостях и транзитивных зависимостях проекта. Maven знает.
- откуда эти системы будут загружать дистрибутив приложения?
- в настоящее время java не умеет ловить SIGHUP
eaa
18.08.2015 17:33Одной из задач процесса билда в случае deb/rpm является выявление транзитивных зависимостей и прописывание их в зависимости deb/rpm пакетов.
voidnugget
18.08.2015 18:32+11. Им и не нужно знать — они подгружают эту информацию с maven / gradle.
2. Системы деплоя и оркестрации имеют встроенные средства дистрибуции: ведения репозиториев и сборки пакетов под различные дистрибутивы, не deb'ом единым.
3. Есть sun.misc.SignalHandler и можно делать так
Signal.handle(new Signal("HUP"), new SignalHandler() { public void handle(Signal signal) { reloadService(); } });
dernasherbrezon
19.08.2015 13:46- Ansible не умеет подтягивать транзитивные зависимости. Chef не умеет складывать все транзитивные зависимости в одно место. Puppet скачивает весь Maven, чтобы потом скачать нужные артефакты.
- Ansible, Chef, Puppet — это в простейшем случае скрипты. Они не хранят внутри себя бинарников приложений. Где это репозитории будут находиться?
- sun.misc.* — это проприетарное API, которое не рекомендуется использовать и которое в будущем может быть удалено
vsb
18.08.2015 20:53+2Для 99% проектов это ненужные лишние действия. Остановил сервер, удалил webapps/ROOT, создал его, распаковал туда jar-ник, опционально скопировал поверх конфиги (а лучше их хранить в другом месте), запустил сервер. Скрипт пишется за 10 минут, работает железобетонно.
amarao
18.08.2015 21:46+4Описаны не настоящие deb'ы. Настоящие должны иметь сырцы (source deb) и воспроизводиться при сборке из сырцов с помощью dpkg-buildpackage или им подобным. Должны быть правильные changelog, секции установки и помеченные конфиги.
Всё это можно не делать — и будет халтура обыкновенная.dernasherbrezon
18.08.2015 23:26К сожалению, да. Классического debian-way здесь нет. Но:
- Большинство проектов на java — проприетарные системы. Все остальные уже имеют deb пакеты и собственных maintainer'ов
- deb начинался разрабатываться 16 лет назад. Это одновременно и плюс: множество необходимого функционала уже есть, и минус — множество устаревшего функционала есть. Если Вам нужны исходники в apt репозитории то это не проблема — можно создать pull request в maven-apt-plugin и поработать над улучшением интеграции
Mistx
19.08.2015 09:50Это с каких это пор в Debian\Ubuntu логи стали хранить в /usr/log? Всегда хранились в /var/log
Lure_of_Chaos
19.08.2015 10:03А если надо собрать rpm для других линуксов, не дебиановских?
eaa
19.08.2015 10:59Под это дело есть соответствующие плагины, типа redline для ant (может он есть и под maven, я не искал за ненадобностью).
grossws
19.08.2015 20:24У меня возникают некоторые сомнения в качестве работы maven-plugin'а, не соблюдающего даже базовые рекомендации об именовании:
Important Notice: Plugin Naming Convention and Apache Maven Trademark
You will typically name your plugin<yourplugin>-maven-plugin
.
Calling itmaven-<yourplugin>-plugin
(note «Maven» is at the beginning of the plugin name) is strongly discouraged since it's a reserved naming pattern for official Apache Maven plugins maintained by the Apache Maven team with groupId org.apache.maven.plugins. Using this naming pattern is an infringement of the Apache Maven Trademark.grossws
21.08.2015 20:05Дополнение: автор быстро пофиксил проблему (см. issue#1).
Предыдущий комментарий получился довольно резким, но, надеюсь, автор меня простит. Да, dernasherbrezon?)
artspb
10.09.2015 21:43В одном из проектов использовали схему war-в-deb. Мне она показалась достаточно удобной, хотя со скриптами пришлось изрядно повозиться.
shuron
13.09.2015 16:02Вот тут хорошо описано почему пакеты уже морально устарели по сравенению с Докером
dernasherbrezon
16.09.2015 00:00Отличная статья для дискуссии!
Насколько я понял сравнивается только одна сторона: dependency hell. Мне же кажется что с докером ситуация будет просто еще хуже.
Допустим есть 3 контейнера с руби. Руби разных версий внутри. И вот однажды выходит CVE-2015-3900. Каким образом обновить руби на всех трех контейнерах?
Или вот еще пример про зависимости: есть все те же 3 контейнера с руби разных версий внутри. И внезапно происходит что то магическое связанное с ipv6 и резолвингом хостнейма. Вопрос: сколько приложений в контейнерах упадут? И что потом делать с контейнерами в которых настолько старый руби в котором этой баги еще нет? Добавить в календарь после каждого релиза «проверить тот древнющий контейнер»?
Вывод такой: если хочется разных версий одного и того же продукта, то значит в консерватории что то не то.shuron
20.09.2015 17:43+1Не знаю насколько это серьезная проблема… с руби.
Но да если эта проблема открывет брешь в контейнер придется обновить базовый контейнер и перестроить все что на него опираются…
Ну это как-бы не то чего я боюсь с рабочим CI/CD.
Вывод такой: если хочется разных версий одного и того же продукта, то значит в консерватории что то не то
Ерунда. Да и разговор не оразных версиях, руби или явы…
А о разных версиях вашего приложения. Например в рамках «canary testing»
П.С. Не только dependency hell
иметь разделение Export services via port binding
Тоже правильная вешь…
Дело в том что она контролируемая, например можно повесить хуки и оповестить кого нужно автоматически (Прокси, конзьюмнеров, мониторинг). Тоесь все то что делают возможным клауд.dernasherbrezon
22.09.2015 00:09Секунду, секунду. Давайте обо всем по-порядку:
- Перестройка контейнера и CI/CD никак не помогут против security обновлений. Как вы узнаете, что bash в вашем контейнере нужно обновить? А заодно обновить все, что на него линкуется.
- «canary testing». Есть два варианта:
- Вы делаете его в разных контейнерах на одном физическом хосте и имеете проблему пересечения портов. Ее можно решать двумя конфигурациями: «real prod» и «canary testing» с непересекающимися портами, дисками и путями. Поправьте меня, если это делается как-то по-другому.
- Вы делаете его на двух физически разных хостах. Тогда это ничем не отличается от:
sudo apt-get install exampleapp=1.3.2-canary1
на определенном хосте. Или даже больше — можно определить experimental репозиторий, добавить его на определенные хосты и на этих хостах обновлять всегда до последней версии. Делается это примерно в 3 команды:
sudo add-apt-repository "deb s3://apikey:apisecret@s3.amazonaws.com/example.bucket strepo experimental" sudo apt-get update sudo apt-get install exampleapp
- По поводу Export services via port binding. Не могу найти ничего противоречещее идеи пакетной сборки. Все то же самое можно делать и с пакетами.
shuron
24.09.2015 00:20Перестройка контейнера и CI/CD никак не помогут против security обновлений. Как вы узнаете, что bash в вашем контейнере нужно обновить? А заодно обновить все, что на него линкуется.
В контенере вашь меньше всего интересует версиай башь и всего что на него линкуется… с наружи видно порт и сервис на него повешен…
Проблема с башем — если сервис на баше написан ;) А это решемо ;)
Вы делаете его в разных контейнерах на одном физическом хосте и имеете проблему пересечения портов. Ее можно решать двумя конфигурациями: «real prod» и «canary testing» с непересекающимися портами, дисками и путями. Поправьте меня, если это делается как-то по-другому.
Вы не думаете о портах впринципе особенно в build-time
И в идеале при деплойменте контейнра инфраструктура сама линкует новый контейнер к лоадбэленсеру…
И вообще вы не думаете о том на каком хосте он запустится и вообще вы можете перестать думать хостами
По поводу Export services via port binding. Не могу найти ничего противоречещее идеи пакетной сборки. Все то же самое можно делать и с пакетами.
нет пакетами вы не можете стартануть два пакета с одним портом не конфигурируя что-то руками на каком-то хосте и препадавать это апликации (например пермеными окружения)
Хотя даже инсталлировать один и тот-зех пакет разных версий уже не сможете ;)
eaa
Хм, по-моему так вообще не важно, java или бинари или еще что-то: в линукс-подобных системах все ставится через пакеты.
Всегда собирал java в пакеты RPM через связку ant + redline, все пучком.
Про костыли «rsync/copy-paste/wget» слышу от Вас впервые.
zloddey
Проблема в следующем: для linux-программиста эти вещи действительно очевидны. Но далеко не все Java-программисты являются linux-программистами. Мой личный опыт как раз включает в себя весёлые приключения с уговорами (к счастью, успешными) завернуть дистрибуцию продуктов в нормальные пакеты под используемый дистрибутив.
Причём, стоит добавить, что rsync/wget ещё продвинутые товарищи используют. В запущенных случаях приходилось видеть и ручное перекладывание jar-ников на сервер. Вот уж где настоящий угар!
guai
Ява-программисты привыкли избегать платформозависимых инструментов. Инсталляторов и на яве хватает.
eaa
Ага, Eclipse идет на убунте почему-то в виде .deb
Android studio для винды — в виде .exe
guai
Захотели покрыть все платформы родными инсталлерами — покрыли. Но если делается 1 инсталлер, то чаще явавский, или вообще архивчик, JAVA_HOME прописан — больше ничего и не надо.
eaa
Ох, я помню, когда совсем новичком пробовал поставить что-то явовское на комп… сколько я матом крыл то, что надо кучу всего прописывать в переменных, часто одним JAVA_HOME дело не ограничивается.
Нормальному обычному пользователю нужен нормальный простой инсталлятор для той платформы, на которой он работает. Никакой магии. Никаких архивчиков. Никаких JAVA_HOME.
Ему вообще пофиг, на чем написан инструмент, лишь бы работал.
guai
нормальный обычный пользователь линукса, умеющий apt/yum, я думаю, яву тоже осилит
eaa
Я, как нормальный пользователь, не хочу ничего осиливать, я хочу пользоваться инструментами.
guai
И поэтому все должны писать софт под те инструменты, которые вы знаете, так что ли?
Для человека, не знающего ни яву, ни yum, разбираться придется одинаково, что с архивчиком, что с юмом. С юмом кстати косяков может быть больше, если нет сетки, если за прокси, если нет пряв на yum и т.п.
Вы наверн никогда не сидели с жестко зарезанными правами под линуксом. Я сидел, с тех пор даже блокнот явавский привык язать, потому что возможность просто укачать архивчик сложнее зарезать.
eaa
Именно! Софт пишется для пользователя, а не для человека, который готов плясать с бубном и настраивать по несколько часов то, что должно работать с пол пинка.
И обычному пользователю вообще не надо знать yum/apt-get, есть вполне адекватные графические оболочки под все это дело, любое приложение ставится за пару кликов мышкой. Если оно конечно адекватно написано.
nehaev
О, эклипс это богатая тема.
1. В моей убунте (14.04 LTS) из репозитория доступен только Eclipse 3.8, в то время как актуальная версия — 4.5. Сидеть на версии трехлетней давности? Даже PPA для 4.5 что-то не нашелся сходу.
2. На сайте эклипса для линукс можно скачать только tar.gz. Похоже, как верно отметил автор предыдущего коммента, разработчики избегают платформозависимых инструментов.
3. Для разных проектов мне нужны разные версии эклипса. Что я делаю сейчас? Скачиваю несколько архивов и распаковываю в папки, которые выбираю сам. Больше ничего делать не надо, разные эклипсы с разными конфигурациями запускаются из разных папок без проблем. Насколько просто достичь того же эффекта deb-пакетами?
4. Страшная вещь, но внутри эклипса есть своя система дистрибуции пакетов — Eclipse Marketplace. И работает она не на deb-пакетах. Потому что с точки зрения джава, jar — это более универсальный формат распространения программ.
eaa
Все становится намного сложнее, когда в проекте еще есть с/с++ код, который, конечно, можно вынести отдельно, но его тоже надо как-то ставить на ОС.
Можно городить зоопарк, что бинари ставим через .deb, java — через .zip, при этом как в .zip указать правильную зависимость от нужного бинарного .deb — это никак. Т.е. надо изобретать какую-то свою систему зависимостей, либо паковать java опять же в .deb и использовать готовую. Ну или наоборот через maven таскать сишные скомпиленные .so.
В общем, подходов масса, если проект чисто на java — там одно, если он смешанный — то все уже идет по другому сценарию.
Все пакеты (jar, war, zip, deb, rpm) на самом деле работают на разных уровнях, юзеру надо одно, админу на сервере может быть проще другое.
Lure_of_Chaos
есть новый www.eclipse.org/downloads/installer.php, который упрощает жизнь по 3ему, немного по 2 и 1 пунктам, ну и частично 4