Превращаем Java/JVM приложение в демона

  • Tutorial
Часто ли вы пишите shell скрипт и пакетный файл для запуска своего jvm приложения, а как часто копируете из другого проекта?



Можно воспользоваться appassembler-maven-plugin для генерации скриптов запуска нашей программы и создания из нее демона. Плагин делает всю рутинную работу по конфигурации java service wrapper, генерации скриптов и сборке приложения за нас.

Но мы упростим нашу жизнь и воспользуемся автоматизированным решением для создание скелета maven артефактов для сборки своих демонов. Плагином-генератором com.github.igor-suhorukov:daemon-archetype для maven, который доступен в центральном репозитарии и на github. И под капотом все равно appassembler-maven-plugin!

Все что надо сделать для создания скелета демона — выполнить команду создания в интерактивном режиме, а затем сконфигурировать что получилось под проект:

mvn archetype:generate -DarchetypeGroupId=com.github.igor-suhorukov -DarchetypeArtifactId=daemon-archetype -DarchetypeVersion=0.1

Важное замечание! Демон java service wrapper останавливается сигналом SIGINT, поэтому для корректного освобождения ресурсов надо зарегистрировать свой Runtime.getRuntime().addShutdownHook(...).

Конфигурация плагина daemon-archetype.


Параметры groupId, artifactId, version не заслуживают особого внимания, так как они требуются любым archetype плагином и это то, что будет в соответствующих тегах pom.xml.

Параметром entry-point-class нужно указать полностью квалифицированное имя класса, метод public static void main(String[]) которого демон будет вызывать при старте. В случае java приложения можно указать класс с методом main как из зависимости проекта main-artifact*, так и из директории src/main/java текущего проекта.

Параметры main-artifact-artifactId, main-artifact-groupId, main-artifact-version указывают на зависимость, которая содержит entry-point-class. В сборку пакуются также транзитивные зависимости для main-artifact*.

launcher-name определяет имя скрипта демона в директории bin.

Пример: демон git сервера gitblit.


Для пробы создадим демон запуска git сервера. Выполнив в консоли команду, либо указав те же параметры в интерактивном режиме:

mvn archetype:generate -DarchetypeGroupId=com.github.igor-suhorukov -DarchetypeArtifactId=daemon-archetype -DarchetypeVersion=0.1 -DgroupId=com.github.igor-suhorukov -DartifactId=gitblit-launcher -Dversion=1.0-SNAPSHOT -Dpackage=com.github.igor-suhorukov -Dentry-point-class=com.github.igorsuhorukov.groovy.GroovyMain -Dlauncher-name=launcher -Dmain-artifact-artifactId=groovy-grape-aether -Dmain-artifact-groupId=com.github.igor-suhorukov -Dmain-artifact-version=2.4.5.4 -DinteractiveMode=false



После отредактируем получившийся pom.xml, добавив в теги program и daemon следующий фрагмент конфигурации:

<commandLineArguments>
    <commandLineArgument>https://raw.githubusercontent.com/igor-suhorukov/git-configuration/master/gitblit.groovy </commandLineArgument>
</commandLineArguments>

Выполняем команду:

mvn package

После сборки в директории target будет архив с демоном: gitblit-launcher-1.0-SNAPSHOT-daemon.tgz и два архива в формате tgz и zip с обычными скриптами для запуска консольного приложения gitblit-launcher-1.0-SNAPSHOT-assembly.tgz, gitblit-launcher-1.0-SNAPSHOT-assembly.zip.

В нашем примере в сборку с демоном упакуется jar файл из com.github.igor-suhorukov:groovy-grape-aether:2.4.5.4. Демон запустит JVM с указанием main класса com.github.igorsuhorukov.groovy.GroovyMain и передаст ему параметром путь к Groovy скрипту raw.githubusercontent.com/igor-suhorukov/git-configuration/master/gitblit.groovy.

Groovy скрипт скачивает gitblit.war из репозитария проекта, распаковывает его в домашнюю директорию пользователя и заменяет в конфигурации gitblit путь к хранилищу репозитариев. После этого запускает jetty сервер и gitblit внутри него.

gitblit.groovy
import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader
@Grab(group='org.codehaus.plexus', module='plexus-archiver', version='2.10.2')
import org.codehaus.plexus.archiver.zip.ZipUnArchiver
@Grab(group='org.codehaus.plexus', module='plexus-container-default', version='1.6')
import org.codehaus.plexus.logging.console.ConsoleLogger
@Grab(group = 'org.eclipse.jetty', module = 'jetty-runner', version = '9.3.7.RC1' )
import org.eclipse.jetty.runner.Runner

def gitblit = new File(MavenClassLoader.using('http://gitblit.github.io/gitblit-maven').resolveArtifact('com.gitblit:gitblit:war:1.7.1').getFile())

File gitblitDirectory = new File(System.getProperty('user.home'), gitblit.getName().replace('.war',''))

if(!gitblitDirectory.exists()){
    gitblitDirectory.mkdir()
    ZipUnArchiver unArchiver = new ZipUnArchiver()
    unArchiver.setSourceFile(gitblit)
    unArchiver.enableLogging(new ConsoleLogger(ConsoleLogger.LEVEL_DEBUG,"Logger"))
    unArchiver.setDestDirectory(gitblitDirectory)
    unArchiver.extract()

    def dataPath = new File(System.getProperty('user.home'), '.gitblit_data')
    if(!dataPath.exists()){ dataPath.mkdir() }
    def webXml = new File(gitblitDirectory.getAbsoluteFile(), 'WEB-INF/web.xml')
    webXmlText = webXml.text
    webXml.withWriter { w -> w << webXmlText.replace('${contextFolder}/WEB-INF/data', dataPath.getAbsolutePath()) }
}

Runner.main([gitblitDirectory] as String[])

Можете посмотреть скринкаст с примером создания и работы демона.



Создать демон и скрипты для сборки приложения можно с помощью всего лишь одной команды.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 15

    0
    а зачем?
    Оно вполне нормально запускается напрмер из upstart, и шатдаунится штатно.
      0
      У java service wrapper есть функциональность watchdog, которая перезапускает JVM в случае зависания. stdout и stderr пишутся в ротируемые логи. Это первое что приходит на ум. А если ОС windows или solaris?
        0
        Upstart и systemd делают все то же штатно безо всяких врапперов. Лишний враппер им только мешает.

        > если ОС windows
        тогда надо запускать сервисом

        > solaris
        Я в нем совсем не разбираюсь, но разве в SMF нет штатной функциональности для new style daemons?
          0
          Тот же враппер есть и под Windows, по-умолчанию конфигурируется в pom.xml "windows-x86-32".
          Плюс нет необходимости в самой программе следить в разных платформах, что запущен только один ее процесс, это есть «из коробки».

          Я вас не переубеждаю, каждый использует то что ему удобно. Лично мне так проще, так же как и разработчикам Nexus, Sonar и т.п.
            0
            В солярисе я не специалист, но сколько раз сталкивался на работах с устаревшими серверами на solaris, каждый раз без проблем использовал java service wrapper.

            Когда пытался решить штатными средствами запуск одного процесса в приложении, в том же jruby на solaris не работал file lock.
        0
        «Мы» делаем из java-приложения демона только тогда, когда этого невозможно избежать. Да и то стараемся обходить такой случай.

        Контрольные вопросы ( само собой, они возникают как послеследствие jsvc и daemon(ize) ):

        1) что с пид-файлом?
        2) решили ли вы проблему HUP (напоминаю, что по-апачевски это start/stop, а по-сановски это «Signal is internal proprietary API and may be removed in a future release.»)
        0
        1) В директории logs создается файл launcher.pid (или как назвали свойство «launcher-name») создается при старте;
        2) «wrapper.signal.mode.hup» поведение конфигурируется.
          0
          То есть поймать сигнал и что-то сделать без гашения процесса всё равно не получится?
            0
            Как вариант проприетарные sun.misc.Signal и sun.misc.SignalHandler или addShutdownHook, как указывал в публикации(и жди себе, пока не прийдет SIGKILL).
              0
              Так в том-то и дело :) Сановские ругательства javac на использование «sun.misc.Signal*» продолжаются уже много лет, но никак в реальную опасность не превращаются. Их и используем.
                0
                Не самые страшные ругательства в enterprise приложении)
                  0
                  После -Wall -pedantic -pedantic-errors странно это :) Не говоря уж о MISRA.
          +2
          Мы используем spring-boot и systemd для этих целей. systemd вообще оказалось очень просто — никаких сложных скриптов, конфиг-файл для сервиса занимает 5-10 строк.

          http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started-first-application-executable-jar
          http://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html#deployment-systemd-service

        Only users with full accounts can post comments. Log in, please.