- Как сделать простым и понятным запуск Java-процессов в Linux / Docker
- Откуда такая сложность?
- Что же делать?
- Пример
- Заключение
- Разворачиваем окружение для Java-приложения с помощью Ansible
- Роль rhel_install_new_server
- Guide Установка и настройка Java версии сервера на Linux (на примере Ubuntu) Thread starter mr.Slink Start date Sep 20, 2020 Tags bdoguideinstallunixустановка
- mr.Slink
- Evgen
- mr.Slink
Как сделать простым и понятным запуск Java-процессов в Linux / Docker
По профилю работы DevOps-инженером я часто занимаюсь автоматизацией установки и настройки разнообразных IT-систем в различных средах: от контейнеров до облака. Приходилось работать со многими системами, основанными на Java-стеке: от небольших (вроде Tomcat), до масштабных (Hadoop, Cassandra и др.).
При этом почти каждая такая система, даже самая простая, почему-то имела сложную неповторимую систему запуска. Как минимум, это были многострочные shell-скрипты, как в Tomcat, а то и целые фреймворки, как в Hadoop. Мой нынешний «пациент» из этой серии, вдохновивший меня на написание этой статьи — хранилище артефактов Nexus OSS 3, скрипт запуска которого занимает
Непрозрачность, избыточность, запутанность startup-скриптов создает проблемы даже при ручной установке одного компонента на локальной системе. А теперь представьте, что набор таких компонентов и сервисов нужно запаковать в Docker-контейнер, попутно написав еще один слой абстракции для мало-мальски адекватного оркестрирования, развернуть в Kubernetes-кластере и реализовать этот процесс в виде CI/CD-пайплайна.
Короче говоря, давайте на примере упомянутого Nexus 3 разберемся, как вернуться из лабиринта shell-скриптов к чему-то более похожему на java -jar
, учитывая наличие удобных современных DevOps-инструментов.
Откуда такая сложность?
Если в двух словах, то в древние времена, когда при упоминании UNIX не переспрашивали: «в смысле, Linux?», не было Systemd и Docker и др., для управления процессами использовались переносимые shell-скрипты (init-скрипты) и PID-файлы. Init-скрипты задавали необходимые настройки окружения, которые в разных UNIX-ах были свои, и, в зависимости от аргументов, запускали процесс или перезапускали/останавливали его с помощью ID из PID-файла. Подход простой и понятный, но эти скрипты переставали работать при каждой нестандартной ситуации, требуя ручного вмешательства, не позволяли запустить несколько копий процесса… но не суть.
Так вот, если внимательно посмотреть на упомянутые выше startup-скрипты в Java-проектах, то можно в них разглядеть явные признаки этого доисторического подхода, включая даже упоминания SunOS, HP-UX и других UNIX-ов. Как правило, такие скрипты делают примерно следующее:
- используют синтаксис POSIX shell со всеми его костылями для UNIX/Linux-переносимости
- определяют версию и релиз ОС через uname , /etc/*release и т.п.
- ищут JRE/JDK в укромных уголках файловой системы и выбирают наиболее «подходящую» версию по хитрым правилам, иногда еще и специфичным для каждой ОС
- рассчитывают числовые параметры JVM, например, размер памяти ( -Xms , -Xmx ), количество потоков GC и др.
- оптимизируют JVM через -XX -параметры с учетом специфики выбранной версии JRE/JDK
- отыскивают свои компоненты, библиотеки, пути к ним по окружающим директориям, конфигурационным файлам и т.п.
- настраивают окружение: ulimits, переменные среды и т.п.
- генерируют CLASSPATH циклом типа: for f in $path/*.jar; do CLASSPATH=»$
:$f»; done - парсят аргументы командной строки: start|stop|restart|reload|status|.
- собирают Java-команду, которую в итоге нужно выполнить, из перечисленного выше
- и, наконец, выполняют эту Java-команду. Зачастую при этом явно или неявно используются все те же пресловутые PID-файлы, & , nohup , специальные TCP-порты и прочие трюки из прошлого столетия (см. пример из Karaf)
Упомянутый скрипт запуска Nexus 3 — подходящий пример такого скрипта.
По сути, вся перечисленная выше скриптовая логика, как бы, пытается заменить системного администратора, который бы установил и настроил все вручную под конкретную систему от начала до конца. Но вообще любые требования самых разнообразных систем учесть, в принципе, невозможно. Поэтому получается, наоборот, головная боль, как для разработчиков, которым нужно поддерживать эти скрипты, так и для системных инженеров, которым в этих скриптах потом нужно разбираться. С моей точки зрения, системному инженеру гораздо проще один раз разобраться в параметрах JVM и настроить ее как надо, чем каждый раз при установке новой системы разбираться в тонкостях ее startup-скриптов.
Что же делать?
У — про — щать! KISS и YAGNI нам в руки. Тем более, что на дворе 2018-й год, а это значит, что:
- за очень редким исключением, UNIX == Linux
- задача управления процессами решена как для отдельного сервера (Systemd, Docker), так и для кластеров (Kubernetes и т.п.)
- появилась куча удобных инструментов управления конфигурациями (Ansible и др.)
- в администрирование пришла и уже основательно закрепилась тотальная автоматизация: вместо ручной настройки хрупких неповторимых «серверов-снежинок» теперь можно автоматически собирать унифицированные репродуцируемые виртуальные машины и контейнеры с помощью целого ряда удобных инструментов, включая упомянутые выше Ansible и Docker
- повсеместно используются инструменты сбора runtime-статистики, как для самой JVM (пример), так и для Java-приложения (пример)
- и, самое главное, появились специалисты: системные и DevOps-инженеры, которые умеют использовать перечисленные выше технологии и понимают, как правильно установить JVM на конкрентной системе и впоследствии подстроить ее с учетом собранной runtime-статистики
Так что давайте снова пройдемся по функционалу startup-скриптов еще раз с учетом перечисленных пунктов, не пытаясь при этом делать работу за системного инженера, и уберем оттуда все «лишнее».
- синтаксис POSIX shell ⇒ /bin/bash
- определение версии ОС ⇒ UNIX == Linux, если есть ОС-специфичные параметры, можно описать их в документации
- поиск JRE/JDK ⇒ у нас единственная версия, и это OpenJDK (ну или Oracle JDK, если уж очень нужно), java и компания есть в стандартном системном пути
- расчет числовых параметров JVM, тюнинг JVM ⇒ это можно описать в документации по скалированию приложения
- поиск своих компонентов и библиотек ⇒ описать структуру приложения и способы ее настройки в документации
- настройка окружения ⇒ описать в документации требования и особенности
- генерация CLASSPATH ⇒ -cp path/to/my/jars/* или даже, вообще, Uber-JAR
- парсинг аргументов командной строки ⇒ аргументов не будет, т.к. обо всем, кроме запуска, позаботится менеджер процессов
- сборка Java-команды
- выполнение Java-команды
В итоге, нам нужно просто собрать и выполнить Java-команду вида java -jar
с помощью выбранного менеджера процессов (Systemd, Docker и т.п.). Все параметры и опции ( ) мы оставляем на усмотрение системного инженера, который подстроит их под конкретную среду. Если список опций довольно длинный, можно вновь вернуться к идее startup-скрипта, но, в этом случае, максимально компактного и декларативного, т.е. не содержащего никакой программной логики.
Пример
В качестве примера давайте посмотрим, как можно упростить скрипт запуска Nexus 3.
Самый простой вариант, чтобы не залезать в дебри этого скрипта — просто запустить его в реальных условиях ( ./nexus start ) и посмотреть на результат. Например, можно найти полный список аргументов запущенного приложения в таблице процессов (через ps -ef ), или запустить скрипт в режиме отладки ( bash -x ./nexus start ), чтобы наблюдать весь процесс его выполнения и в самом конце — команду запуска.
Вначале применим к ней пару простых приемов:
- поменяем /the/long/and/winding/road/to/my/java на java , ведь она есть в системном пути
- поместим список Java-параметров в отдельный массив, отсортируем его и уберем дубликаты
Теперь можно идти в глубину.
Install4j — это такой графический Java-инсталлятор. Похоже, что он используется для начальной установки системы. На сервере он нам не нужен, убираем.
Договоримся о размещении компоненты и данные Nexus на файловой системе:
- поместим само приложение в /opt/nexus-
- для удобства создадим символическую ссылку /opt/nexus -> /opt/nexus-
- сам скрипт разместим вместо исходного как /opt/nexus/bin/nexus
- все данные нашего Nexus будут лежать на отдельной файловой системе, смонтированной как /data/nexus
Само создание каталогов и ссылок — удел систем управления конфигурациями (на все про все 5-10 строчек в Ansible), поэтому оставим эту задачу системным инженерам.
Пусть наш скрипт при запуске меняет рабочий каталог на /opt/nexus — тогда мы сможем поменять пути к компонентам Nexus на относительные.
Опции вида -Dkaraf.* — это настройки Apache Karaf, OSGi-контейнера, в который, очевидно, «запакован» наш Nexus. Поменяем karaf.home , karaf.base , karaf.etc и karaf.data соответственно размещению компонентов, по возможности используя относительные пути.
Видя, что CLASSPATH состоит из списка jar-файлов, которые лежат в одном каталоге lib/ , заменим весь этот список на lib/* (придется также выключить wildcard expansion с помощью set -o noglob ).
Поменяем java на exec java , чтобы наш скрипт на запускал java как дочерний процесс (менеджер процессов этот дочерний процесс просто не увидит), а «заменял» себя на java (описание exec).
Посмотрим, что нас получилось:
Итого всего 27 строчек вместо >400, прозрачно, понятно, декларативно, никакой лишней логики. При необходимости этот скрипт легко превратить в темплейт для Ansible/Puppet/Chef и добавить туда только ту логику, которая нужна для конкретной ситуации.
Этот скрипт можно использовать в качестве ENTRYPOINT в Dockerfile или вызывать в unit-файле Systemd, заодно подстроив там ulimits и другие системные параметры, например:
Заключение
Какие выводы можно сделать из этой статьи? В принципе, все сводится к паре пунктов:
- У каждой системы свое предназначение, т.е., не нужно забивать гвозди микроскопом.
- Простота (KISS, YAGNI) рулит — реализовывать только то, что нужно для данной конкретной ситуации.
- И самое главное: круто, что есть IT-специалисты разного профиля. Давайте будем взаимодействовать и делать наши IT-системы проще, понятнее и лучше! 🙂
Спасибо за внимание! Буду рад обратной связи и конструктивной дискуссии в комментариях.
Источник
Разворачиваем окружение для Java-приложения с помощью Ansible
За мной, за мной, читатель, и я проведу тебя в чарующий мир автоматизации разворачивания окружения на серверах под управлением Linux семейства RHEL.
Один из наших java-проектов вырос, стал совсем взрослым и сейчас занимает 4 контура:
Dev — контур для команды разработки,
Qa — контур для команды тестирования,
Stage — контур для демонстрации новых фич заказчику,
Production — боевой контур.
Каждый контур содержит два одинаковых сервера с идентичным набором компонентов окружения для нашего приложения:
linux Oracle — операционная система,
jdk — комплект приложений Java,
haproxy — proxy сервер,
nginx — веб-сервер для отдачи статики,
mysql — субд.
Перед командой эксплуатации встал резонный вопрос: как настроить управление окружением на восьми серверах и сохранить оптимистичное отношение к жизни.
После краткого сравнения систем управления конфигурациями был выбран Ansible. В его пользу сыграли простота, гибкость и отсутствие агентов на управляемых серверах.
Теперь немного расскажем об архитектуре ролей Ansible для проекта.
Для установки и первоначальной настройки каждого компонента окружения написана отдельная роль. Последующая кастомизация настроек каждого компонента окружения также выделена в дополнительную роль. Это позволило нам избежать дублирования общих стандартных ролей для остальных проектов.
Playbook c последовательностью выполняемых ролей для подключения новой ноды в контур выглядит так:
А теперь расскажем о каждой роли поподробнее.
Роль rhel_install_new_server
Выполняет общую настройку операционной системы и установку системных утилит.
Источник
Guide Установка и настройка Java версии сервера на Linux (на примере Ubuntu) Thread starter mr.Slink Start date Sep 20, 2020 Tags bdoguideinstallunixустановка
mr.Slink
EmuDevs
Лучше проделайте данное действие после первого запуска сервера, потому-что количество баз данных может быть разными и их придётся прописывать вручную для каждого пользователя.
Подключитесь к MongoDB, с помощью mongo shell:
Замените passw0rd на свой пароль
Перейдите в базу данных, в которой вы хотите создать обычного пользователя:
К сожалению мы будем видеть логи только GameServerа, но их в любом случае можно будет найти по пути /var/dboserver/loginserver/loginserver/bin/log .
Поздравляю, Вы запустили сервер на Ubuntu в первый раз!
Теперь можно переходить к настройке.
И да, в сборках как правило лежит уже MongoDB (в директории database), его можно спокойно удалить, чтобы у нас осталось только gameserver, java, loginserver и source. Ведь MongoDB у нас теперь является отдельной службой.
На этом всё, у меня таким способом получилось запустить сервер и даже подключится к нему.
Karmas: pau
Evgen
з дравствуйте, не подскажите возможно ли запустить GS с большим объёмом памяти? я вижу что он берет автоматот 8гб. а у меня машина с 32гб.
за ранее благодарю.
Ты только посмотри на него
з дравствуйте, не подскажите возможно ли запустить GS с большим объёмом памяти? я вижу что он берет автоматот 8гб. а у меня машина с 32гб.
за ранее благодарю.
Karmas: mr.Slink
mr.Slink
EmuDevs
Я удивлен, что ты знаешь. Ты прав
Флаг Xmx указывает максимальный пул распределения памяти для виртуальной машины Java (JVM), а Xms указывает начальный пул распределения памяти.
Это означает, что ваша JVM будет запущена с Xms объемом памяти и сможет использовать максимальный Xmxобъем памяти. Например, запуск JVM, как показано ниже, запустит ее с 256 МБ памяти и позволит процессу использовать до 25 ГБ памяти:
Xms Флаг не имеет значения по умолчанию, а Xmx имеет значение по умолчанию равной 256 мегабайт.
Обычно эти флаги используются, когда вы сталкиваетесь с ошибкой java.lang.OutOfMemoryError .
При использовании этих настроек имейте в виду, что эти настройки предназначены для кучи JVM и что JVM может / будет использовать больше памяти, чем просто размер, выделенный для кучи. Из документации Oracle:
Источник