- Jar файл как служба Ubuntu
- 1) Пара слов о systemctl.
- 2) Создание службы.
- А) Где?
- Б) Как?
- Б.0) Создаём файл
- Б.1) Запуск java напрямую
- Б.2) Запуск sh-скрипта
- Б.3) Что тут к чему?
- Б.4) Нюансы.
- SuccessExitStatus=143
- Type = forking
- Остановка и рестарт приложения по эксепшену.
- В) Ошибки.
- В.1) Как смотреть?
- В.2) Кавычки!
- В.3) Права доступа.
- Общий вывод.
- Как запустить jar в Linux
- Как запустить jar Linux
- Выводы
Jar файл как служба Ubuntu
Jan 13, 2020 · 10 min read
Чтобы создать кастомную службу, нужно чуточку знать о systemd.
systemd — это набор базовых утилит для Linux. Набор предоставляет диспетчер системы и служб, запаускаемый с PID 1, и запускающий остальную часть системы. Systemd управляет службами, запускает демонов, поддерживает монтирование, управляет конфигурацией системы, ведёт логи, отслеживает входящих в систему юзеров, запуск контейнеров и вирт машин и т.д.
С 8-ой версии Debian по умолчанию используется systemd вместо init.d, который использует unit-файлы из каталога /etc/systemd/system, вместо скриптов из /etc/init.d/.
systemctl — основная команда для контроля и отслеживания состояния systemd. С помощью неё мы и будем запускать кастомные службы.
1) Пара слов о systemctl.
Тольк о касательно запуска служб, ибо возможностей у неё гораздо больше.
Вывести список служб ( — no-legend — без шапки и пояснений; — all — не только активные, а все):
Юнитами могут быть как службы (.services), так и точки монтирования (.mount), устройства (.device) или сокеты (.socket)
если интересны только зафэйлиные службы:
Если юнит не указан, то считается, что это . service, в данном случае smbd. service; также точки монтирования преобразуются из / home в home. mount, а имена устройств в юниты-девайсы: /dev/sda2 в dev-sda2.device. При выводе статуса перечисляются все запущенные службой процессы и выводится последняя часть лога.
Посмотреть службы и зависящие процессы юзера:
Если в имени юнита содержится @, значит это экземпляр юнита-шаблона. Например, user@1000.service — экземпляр шаблона user@.service. Экземпляры отличаются идентификаторами, указывающимися после @.
После создания службы может потребоваться перезагрузка настроек systemd:
А также добавить службу в автозапуск при загрузке системы:
2) Создание службы.
А) Где?
Для начала надо создать файл описания службы. Где это сделать? Посмотрим, откуда вообще загружаются юниты:
Положим в приоритетную папку /etc/systemd/system.
Б) Как?
Посмотреть пример имеющейся службы можно так:
А вообще у нас как минимум два варианта:
— Запуск джавы напрямую (не надо никаких доп скриптов)
— Запуск скрипта, который запускает джаву (можно описать доп действия в скрипте)
Б.0) Создаём файл
Б.1) Запуск java напрямую
Б.2) Запуск sh-скрипта
Б.3) Что тут к чему?
Часть строк интуитивно понятна, а часть не очень. Разберём же их (и немного того, что ещё можно указать в файле службы):
Description = Java Test Service
# Зависимость службы от подключения к сети (или любой другой службы):
# если зависимость необходима
# если зависимость не обязательна (взаимоисключаемо с requires)
# если нужно запускать только после подключения к сети. Если не указать, то служба может быть запущена параллельно с поднятием сети (или любой другой указанной службой)
# Type — без указания будет simple — заупск cmd и получение от неё ответа об ЕЁ успешном запуске.
# exec — запуск cmd и получение ответа об успешном запуске именно итогового приложения.
# forking — ждёт, когда указанная служба сделает fork() от главного процесса, потом ждёт завершения главного процесса и стартует последующие за ним юниты.
(дальше в разделе «нюансы» будет объяснено дополнительно)
# пользователь, под которым запускать приложение
# Рабочая директория вашего приложения. Т.е. при получении внешних файлов ваша программа будет отталкиваться от указанной здесь папки.
WorkingDirectory=/home/snatchub/Documents/Share/jar as service test
# Путь к исполняемому файлу — либо к java с параметрами, либо к скрипту, запускающему ваше приложение.
ExecStart=/home/snatchub/Documents/Share/jar as service test/my_amazing.sh
# Скрипт, запускаемый по команде «Стоп». Если не указан, то просто шлётся SIGTERM
ExecStop = /home/snatchub/Documents/Share/jar as service test/my_amazing.sh stop
# скрипт для перезапуска
ExecReload = /home/snatchub/Documents/Share/jar as service test/my_amazing.sh reload
# Путь к PID файлу сервиса (туда пишется PID процесса приложения). Нужен для отслеживания основного процесса.
# Сервис менеджер сам не пишет в этот файл, только читает оттуда pid, а также удаляет его после завершения приложения.
# Это дополнительный «успешный» статус окончания работы приложения.
( дальше в разделе «нюансы» будет объяснено дополнительно)
# Если указан тайм-аут, то сначала мы ждём указанное время окончания выполнения команды ExecStop, потом если служба не остановилась, шлём SIGTERM и ждём указанное время, после чего шлём SIGKILL, если у нас очень живучее приложение.
# on-failure — перезапускать приложение при ошибке (Unclean exit code, Unclean signal, Timeout, Watchdog).
# Можно указать always , если приложение всегда должно быть запущено. На команду “ systemctl stop” это не распространяется.
( дальше в разделе «нюансы» будет объяснено дополнительно)
# Через какое время после остановки произойдёт рестарт. По дефолту время перезапуска 100ms
# Служба должна регулярно вызывать sd_notify(3) с “ WATCHDOG=1” (считай это “ keep-alive ping”-ом).
# Если время между двумя вызовами больше указанного, то служба считается «нерабочей» и останавливается сигналом SIGABRT или тем, что указан в « WatchdogSignal».
# По дефолту в значении control-group , что значит при остановке службы будут убиты все оставшиеся процессы в контрольной группе основного процесса (после выполнения скрипта, указанного в команде « ExecStop»).
# Если указан « process », то только главный процесс будет убит.
# уровень запуска. multi-user.target соответствует уровню runlevel3 и значит, что служба запустится ещё до запуска графической оболочки, но когда юзеры смогут входить в систему через консоль.
Кратко: если pid файл не существует, то стартуем джава-процесс и пишем его pid в файл, иначе убиваем процесс по pid’у из файла и удаляем файл.
if [ ! -f $PID_PATH_NAME ]; then
-f — истина, если файл существует.
nohup “$PATH_TO_JAVA” $JVM_PARAMS -jar $PATH_TO_JAR $APP_PARAMS >> amazing.out 2>&1 &
nohup — утилита, запускающая указанную команду с игнорированием сигналов потери связи (SIGHUP). Т.е. команда будет продолжать выполняться в фоновом режиме даже после выхода юзера из системы.
Знак & в конце означает старт джава-приложения как демона, т.е. скрипт не будет дожидаться окончания джава приложения и пойдёт дальше по своим командам.
2>&1 означает stderr(2) перенаправляется (>) в stdout(&1). Здесь & указывает, что последующую за ней единицу следует воспринимать как дескриптор файла, а не как имя файла.
>> amazing.out — перенаправление потоков в файл.
“ ! — Expands to the process ID of the most recently executed background (asynchronous) command.” (с) man bash
Из этой ни разу не витиеватой фразы делаем глубокомысленный вывод, что $! содержит ID процесса.
2>/dev/null — перенаправление потока ошибок в никуда. Просто подавление ошибок.
Б.4) Нюансы.
SuccessExitStatus=143
Как я уже упомянул — это дополнительный «успешный» статус окончания работы приложения, от которого зависит будет ли ваше приложение перезапускаться или нет в случае ошибки, т.е. когда указано Restart=on-failure или always.
Когда мы останавливаем службу « systemctl stop», то шлётся сигнал SIGTERM с кодом завершения приложения 15, который по умолчанию для службы считается успешным. Но джава завершается с кодом 143 вместо 15, поэтому нужно сказать убунте, что наш код 143 тоже должен считаться успешным.
Если не добавить этот статус, то при попытке остановить службу « systemctl stop», мы увидим статус “ failed” вместо “ inactive”, якобы приложение завершилось по ошибке, и если у нас стоит рестарт, то оно будет перезапущено, вместо остановки.
Почему 143? Потому что по Posix спецификации “The exit status of a command that terminated because it received a signal shall be reported as greater than 128”.
Джава решила следовать спецификации и, таким образом, 143–128 = 15 — это сигнал SIGTERM.
Type = forking
Если запустить джаву напрямую с типом « forking», то увидим такую картину в статусе службы:
Через 1,5 минуты служба отвалится по тайм-ауту и попробует рестартовать приложение, но снова безуспешно. Джава не может форкнуться. Поэтому если в службе вызываете джаву напрямую, то либо уберите этот пункт, либо ставьте явно « Type=simple».
Если запускаете .sh скрипт, то вот что будет при Type=forking:
Скрипт успешно завершился, а джава продолжает работать и может быть остановлена менеджером служб.
Основной процесс мёртв и джава не работает в фоне.
Если type=simple, то служба должна запускать джава приложение напрямую, либо через .sh-скрипт, который должен работать и ждать окончания джава приложения (т.е. не запускать её демоном).
Если type=forking, служба должна стартовать .sh-скрипт, который может завершиться после запуска джава-приложения (т.е. можно запустить её демоном).
Остановка и рестарт приложения по эксепшену.
Давайте сымитируем ошибку в пользовательском треде (не демоническом) и посмотрим, перезапустится ли служба ( пример кода на гитхабе). Если сделать это прямо в main-thread, то всё работает, как надо, приложение возвращает статус ошибки и перезапускается. Вот только main-thread как правило заканчивает свою жизнь быстро по выходу из метода main(String[] args), а приложение дальше живёт на остальных пользовательских тредах. Создадим такой отдельный тред и вызовем ошибку там. Оп-па, приложение завершается, вот только успешно, а не с ошибкой! Соответственно, перезапущена она при « Restart=on-failure» не будет. И если у нас не установлен общий « uncaught exception handler», мы даже увидим эту ошибку в логах службы:
Почему же так происходит?
Потому что джава возвращает только статус окончания main-треда и игнорирует статусы остальных пользовательских тредов.
Если ваше приложение должно всегда работать, то можно выставить « Restart=always». В ином случае рассмотрите вариант использования « Thread.setDefaultUncaughtExceptionHandler()» с логированием ошибки и « System.exit(1)» внутри этого обработчика.
В) Ошибки.
В.1) Как смотреть?
После запуска не забываем смотреть статус службы, там могли быть проблемы:
В таком случае стопорим службу (дабы не перезапускалась) и смотрим снова статус и последние строки лога службы:
Более полный лог можно посмотреть с помощью команды:
Также можем отслеживать живой лог с опцией -f :
В.2) Кавычки!
Важно не забывать знак равенства и не напутать с кавычками, если у вас в путях есть пробелы. Где-то кавычки нужны (в exec секции), а где-то нет (в working directory). Иначе рискуете наткнуться на кучу сообщений об ошибках, типа:
В.3) Права доступа.
Не забудьте добавить прав на запуск скрипта.
u означает чьи права на доступ будут изменены: юзера-владельца в данном случае.
Оператор + вызывает добавление выбранных битов режима файла к существующим. Т.е. добавляет права, а не переписывает их.
x — само право: выполнение (или поиск для директорий)
Если дали прав на скрипт, но не дали на директорию с логами и ресурсами:
Проверяем рестарт службы по ошибке:
Видно, что служба вылетела по ошибке и рестартовала через 5 секунд.
Общий вывод.
Встроенных средств в убунту достаточно для запуска jar-ников как служб, используем systemctl и радуемся безотказной работе!
Примеры кода лежат на гитхабе
Не стесняйтесь пожать руку на Medium или Telegram.
Источник
Как запустить jar в Linux
Java — это кроссплатформенный язык программирования, благодаря которому программы, написанные один раз, можно запускать в большинстве операционных систем: в Windows, Linux и даже MacOS. И всё это без каких-либо изменений.
Но программы, написанные на Java, распространяются в собственном формате .jar, и для их запуска необходимо специальное ПО — Java-машина. В этой небольшой статье мы рассмотрим, как запустить jar-файл в Linux.
Как запустить jar Linux
Как я уже сказал, для запуска jar-файлов нам необходимо, чтобы на компьютере была установлена Java-машина. Если вы не собираетесь ничего разрабатывать, вам будет достаточно Java Runtime Environment или JRE. Что касается версии, то, обычно, большинство программ работают с 7 или 8 версией. Если нужна только восьмая, то разработчики прямо об этом сообщают. Посмотреть версию Java и заодно убедиться, что она установлена в вашей системе, можно с помощью команды:
У меня установлена восьмая версия, с пакетом обновлений 171. Если вы получаете ошибку, что команда не найдена, то это значит, что вам нужно установить java. В Ubuntu OpenJDK JRE можно установить командой:
sudo apt install openjdk-8-jre
Если вы хотите скомпилировать пример из этой статьи, то вам понадобиться не JRE, а JDK, её можно установить командой:
sudo apt install openjdk-8-jdk-headless
Чтобы узнать, как установить Java в других дистрибутивах, смотрите статью по ссылке выше. Когда Java будет установлена, вы можете очень просто запустить любой jar-файл в Linux, передав путь к нему в качестве параметра Java-машине. Давайте для примера создадим небольшое приложение:
public class Main <
public static void main(String[] args) <
System.out.println(» Losst test app! «);
>
>
Затем скомпилируем наше приложение в jar-файл:
javac -d . Main.java
jar cvmf MANIFEST.MF main.jar Main.class
Теперь можно запустить наш jar-файл командой java с параметром -jar:
java -jar main.jar
Таким образом вы можете запустить любой jar-файл, который собран для вашей версии Java. Но не очень удобно каждый раз открывать терминал и прописывать какую-либо команду. Хотелось бы запускать программу по щелчку мышки или как любую другую Linux-программу — по имени файла.
Если мы дадим программе право на выполнение:
chmod u+x ./main.jar
И попытаемся её запустить, то получим ошибку:
Чтобы её исправить, нам понадобиться пакет jarwrapper:
sudo apt install jarwrapper
Теперь можно запускать java в Linux по щелчку мыши или просто командой.
Выводы
В этой небольшой статье мы рассмотрели, как запустить jar Linux с помощью java-машины, а также как упростить команду запуска. Если у вас остались вопросы, спрашивайте в комментариях!
Источник