Хабрахабр

Внедряем OSGI на платформе Karaf

OSGI это не сложно

Я много раз встречал мнение, что OSGI это сложно. И более того, у самого когда-то такое мнение было. Году в 2009, если быть точным. На тот момент мы собирали проекты при помощи Maven Tycho, и деплоили их в Equinox. И это действительно было сложнее, чем разрабатывать и собирать проекты под JavaEE (в тот момент как раз появилась версия EJB 3, на которую мы и переходили). Equinox был намного менее удобен по сравнению с Weblogic, например, а преимущества OSGI тогда мне были не очевидны.

Это была не моя идея, я давно знал к тому моменту про Camel, и решил почитать про Karaf, даже еще не имея оффера. Зато потом, через много лет, мне пришлось на новой работе взяться за проект, который был задуман на основе Apache Camel и Apache Karaf. Почитал один вечер, и понял — вот же оно, простое и готовое, практически то же самое решение некоторых проблем типового JavaEE, аналогичное которому я когда-то делал на коленке при помощи Weblogic WLST, Jython, и Maven Aether.

С чего начнем?
Итак, допустим вы решили попробовать OSGI на платформе Karaf.

Если хочется более глубокого понимания

Можно конечно начать с чтения документации. А можно и с хабра — тут были весьма неплохие статьи, скажем вот совсем уже давно такая. Но в целом karaf получил пока незаслуженно мало внимания. Была еще пара обзоров этот или этот. Вот это упоминание karaf лучше пропустить. Как говорится, не читайте на ночь советских газет… ибо вам там скажут, что kafar это OSGI фреймворк — так вы не верьте. OSGI фреймворки — это Apache Felix или Eclipse Equinox, на базе которых karaf как раз и работает. Можно при этом выбрать любой из них.

по сути — тоже самое, только собранное вендором. Надо заметить, что когда упоминается Jboss Fuse, или Apache ServiceMix, то следует это читать как «Karaf, с предустановленными компонентами», т.е. Я бы не советовал начинать с этого на практике, но почитать вполне можно и обзорные статьи про ServiceMix, например.

Для начала, я попробую тут определить совсем кратко, что из себя представляет OSGI, и для чего можно это применять.

Близким аналогом можно считать, например JavaEE, и в какой-то степени OSGI контейнеры могут выполнять JavaEE модули (скажем, web-приложения в виде War), а с другой стороны, многие JavaEE контейнеры содержат OSGI внутри, как средство реализации модульности «для себя». По большому счету OSGI это средство для создания Java-приложений из модулей. То есть, JavaEE и OSGI — это вещи похожие до совместимости, и удачно взаимодополняющие.

В случае OSGI модуль называется бандлом (bundle), и является хорошо известным всем разработчикам jar архивом с некоторыми дополнениями (то есть, и тут очень похож например на war или ear). Важная часть любой модульной системы — это определение самого модуля. По аналогии с JavaEE бандлы могут экспортировать и импортировать сервисы, являющиеся, по сути, методами классов (то есть, сервис — это интерфейс, или все публичные методы класса).

MF. Метаданные бандла — это знакомый всем META-INF/MANIFEST. Существенно, что среди метаданных обязательно есть: Заголовки манифеста OSGI не пересекаются с заголовками для JRE, соответственно, вне OSGI контейнера бандл — это обычный jar.

Bundle-SymbolicName: com.example.myosgi
Bundle-Version: 1.0.0

Это «координаты» бандла, и тут важен тот факт, что мы можете иметь в одном контейнере две и более одновременно установленных и работающих версии одного бандла.

По аналогии с JavaEE бандлы имеют жизненный цикл, который выглядит так: image.

Экспортируемые пакеты определены внутри бандла, и делаются доступными другим компонентам, когда бандл устанавливается в систему. Кроме сервисов бандлы могут импортировать и экспортировать также пакеты (packages, в обычном для java смысле этого термина). Импортируемые определены где-то извне, должны быть кем-то экспортированы, и предоставлены бандлу контейнером, прежде чем он сможет начать работать.

И еще довольно существенно, что импорт и экспорт содержат указание на версию (или диапазон версий). Импорты пакетов могут быть объявлены необязательными, также как и импорты сервисов.

Отличия от JavaEE

Ну хорошо, что они похожи — мы поняли. А чем они отличаются?

Как только бандл перешел в состояние STARTED, возможности ограничены только вашей фантазией. На мой взгляд, основное отличие состоит в том, что OSGI дает нам намного большую гибкость. Контейнер не берет на себя управление всеми ресурсами в той же мере, что JavaEE. Скажем, вы можете спокойно создавать потоки (да, да, я знаю про ManagedExecutorService), пулы коннектов к базам, и т.п.

Попробуйте скажем в JavaEE динамически создать новый сервлет? Вы можете в процессе работы экспортировать новые сервисы. А тут это вполне возможно, более того, сервлетный контейнер karaf, созданный на базе jetty, ваш созданный сервлет тут же обнаружит, и он будет доступен клиентам по определенному URL.

Хотя это и является небольшим упрощением, но если JavaEE приложение в его классическом виде состоит в основном из компонентов

  • пассивных, ожидающих вызова со стороны клиента
  • определенных статически, то есть на момент деплоя приложения

С другой стороны, приложение на базе OSGI может содержать

  • активные и пассивные компоненты, работающие по расписанию, выполняющие опрос, ну и слушающие сокет, и т.п.
  • сервисы можно определять и публиковать динамически
  • можно подписаться на события фреймворка, например слушать регистрацию сервисов, бандлов и т.п., получать ссылки на другие бандлы и сервисы, и делать много чего еще

Хотя и рисков тут, наверное, несколько больше. Да, на JavaEE многое из этого тоже частично возможно (например, через JNDI), но в случае OSGI на практике делается проще.

Отличия karaf от чистого OSGI

Помимо фреймворка karaf включает много чего полезного. В сущности, karaf есть средство для удобного управления OSGI фреймворком — установки туда бандлов (в том числе группами), их конфигурирования, мониторинга, описания ролевой модели и обеспечения безопасности, и тому подобного.

А давайте уже практиковаться?

Ну чтож, начнем сразу с установки. Тут писать особо нечего — идем на сайт karaf.apache.org, скачиваем дистрибутив, распаковываем. Версии karaf отличаются между собой поддержкой разных спецификаций OSGI (4, 5 или 6), и версий Java. Семейство 2.x я не советую, а вот и 3 (если у вас Java 8, как у меня), и 4 вполне можно пользоваться, хотя развивается на сегодня только семейство 4.x (текущая версия 4.2.2, она поддерживает OSGI 6 и Java вплоть до 10).

Поддержка MacOS и многих других видов Unix тоже декларируется. Karaf вполне нормально работает под Windows и Linux, все что нужно для создания сервиса и автозапуска, имеется.

Если нет, то как правило стоит подправить файл конфигурации, указав, где у вас репозиторий(-ии) maven. Обычно можно запустить karaf сразу, если вы в интернете. Конфигурация karaf располагается в папке etc дистрибутива. Обычно это будет корпоративный Nexus, или скажем Artifactory, кому что нравится. Формат этого файла — java properties. Названия файлов конфигурации не слишком очевидны, но в этом случае вам нужен файл org.ops4j.pax.url.mvn.cfg.

Там же караф возьмет расположение вашего proxy, что в интранете как правило знать обязательно. Задать репозиторий(и) можно как в самом файле конфигурации, перечислив список URL в настройках, так и просто показав, где лежит ваш settings.xml.

Если они у вас заняты, или вы хотите запустить на одном хосте несколько копий, то придется изменить и их. Kafar нужны несколько портов, это порты HTTP, HTTPS (если web настроен, по умолчанию нет), SSH, RMI, JMX. Всего этих портов примерно пять.

Порты типа jmx и rmi — вот тут: org.apache.karaf.management.cfg, ssh — org.apache.karaf.shell.cfg, чтобы поменять порты http/https, нужно будет создать (его скорее всего нет) файл etc/org.ops4j.pax.web.cfg, и записать в него значение org.osgi.service.http.port=нужный-вам-порт.

Для промышленного использования очевидно придется внести изменения в файл bin/setenv, или bin/setenv.bat, чтобы например выделить нужный объем памяти, но для начала, чтобы посмотреть, это не нужно. Дальше точно можно запускать, и как правило все заведется.

Это вполне стандартный SSH, с поддержкой SCP, и SFTP. Можно запустить Karaf сразу с консолью, командой karaf, а можно в фоновом режиме, командой start server, и потом подключиться к нему по SSH. Вполне можно подключиться любым клиентом, например моим любимым инструментом является Far NetBox. Вы можете выполнять команды, и копировать туда-сюда файлы. В потрохах jsch, со всеми вытекающими последствиями. Доступен вход по логину и паролю, а также и по ключам.

Логи вам пригодятся, из кратких сообщений в консоли не все бывает понятно. Рекомендую иметь сразу дополнительное окно консоли, для просмотра логов, которые размещаются в data/log/karaf.log (и другие файлы обычно там же, хотя это настраивается).

Эти две вещи позволят вам намного проще ориентироваться в том, что происходит в контейнере, и в значительной степени рулить процессом оттуда (как бонус, вы получите jolokia и возможность мониторинга по http). Я бы посоветовал сразу установить web, и hawtio web-консоль. Установка hawtio выполняется двумя командами из консоли karaf (как описано тут), и увы, на сегодня версия karaf 3.x уже не поддерживается (вам придется поискать более старые версии hawtio).

Реализация основана на jetty, поэтому все эти усилия по большей части делаются так же. Из коробки https сразу не будет, для этого нужно предпринять некоторые усилия типа генерации сертификатов и пр.

Хорошо, оно запустилось, что дальше?

Я же говорил, будет ssh. Собственно, а вы чего ожидали? Tab работает, если что.

Приложение для OSGI либо является бандлом (bundle), или состоит из нескольких бандлов. Самое время установить какое-нибудь приложение. Караф умеет деплоить приложения в нескольких форматах:

  • Бандл в виде jar, как с манифестом OSGI, так и без него
  • xml, содержащий Spring DM или Blueprint
  • xml, содержащий так называемую feature, которая представляет собой набор из бандлов, других features, и ресурсов (файлов конфигурации)
  • архив .kar, содержащий несколько features и репозиторий maven с зависимостями
  • JavaEE приложения (при некоторых дополнительный условиях), например .war

Это можно сделать несколькими способами:

  • положить приложение в папку deploy
  • установить из консоли командой install
  • установить feature командой из консоли feature:install
  • kar:install

Ну в общем, это вполне похоже на то, что умеет типовой JavaEE контейнер, но несколько удобнее (я бы сказал — сильно удобнее).

Простой jar

Самый простой вариант — это установить обычный jar. Если он у вас лежит в maven репозитории, то для установки достаточно команды:

install mvn:groupId/artifactId/version

wrapper, генерируя манифест по умолчанию, с импортами и экспортами пакетов. При этом Karaf понимает, что перед ним обычный jar, и обрабатывает его, создавая на лету бандл-обертку, т.н.

Смысла от установки просто jar как правило немного, так как этот бандл будет пассивным — из него только экспортируются классы, которые будут доступны другим бандлам.

Этот способ применяется для установки компонентов типа Apache Commons Lang, для примера:

install mvn:org.apache.commons.lang3/commons-lang/3.8.1

А вот и не получилось 🙂 Вот верные координаты:

install mvn:org.apache.commons/commons-lang3/3.8.1

Посмотрим, что вышло: list -u покажет нам бандлы и их источники:

karaf@root()> list -u
START LEVEL 100 , List Threshold: 50
ID | State | Lvl | Version | Name | Update location
-------------------------------------------------------------------------------------------------
87 | Installed | 80 | 3.8.1 | Apache Commons Lang | mvn:org.apache.commons/commons-lang3/3.8.1
88 | Installed | 80 | 3.6.0 | Apache Commons Lang | mvn:org.apache.commons/commons-lang3/3.6

Update location — это то место, где мы взяли бандл, и откуда его можно при необходимости обновить. Как видите, вполне можно установить две версии одного компонента.

Jar и Spring context

Если внутри вашего jar имеется Spring Context, все становится интереснее. Karaf Deployer автоматически ищет xml-контексты в папке META-INF/spring, и создает их, если успешно нашлись все нужные бандлу внешние пакеты.

Если у вас там были например Camel Spring, то Camel routes запустятся тоже. Таким образом, все сервисы, которые были внутри контекстов, уже запустятся. Разумеется, запустить несколько сервисов, слушающих один порт, так просто не выйдет. Это означает, что скажем REST сервис, или сервис, слушающий TCP-порт, вы уже можете запустить.

Просто Spring XML context

Если у вас внутри Spring Context были например определения JDBC DataSources, то вы вполне можете установить их в Karaf отдельно. Т.е. взять xml файл, содержащий только DataSource в виде , или любой другой набор компонентов, вы можете положить его в папку deploy. Context будет запущен стандартным способом. Единственная проблема в том, что созданные таким образом DataSources не будут видны другим бандлам. Их нужно экспортировать в OSGI в виде сервисов. Об этом — чуть позже.

Spring DM

Чем в сущности отличается Spring DM (версия с поддержкой OSGI) от классического Spring? Тем что в классическом случае у вас все бины в контексте создаются на этапе инициализации контекста. Новых появиться не может, старые никуда не денутся. В случае OSGI новые бандлы могут быть установлены, а старые удалены. Среда становится более динамичной, на это нужно как-то реагировать.

Сервис — это как правило некий интерфейс, со своими методами, который опубликован каким-либо бандлом. Способ реагирования называется сервисами. Метаданные — это простой набор свойств key-value. Сервис имеет метаданные, позволяющие его искать и отличать от другого сервиса, реализующего аналогичный интерфейс (от другого DataSource, очевидно).

На уровне Spring DM, в XML, это реализовано как два элемента, service и reference, базовое назначение которых достаточно простое: опубликовать имеющийся бин из контекста как сервис, и подписаться на внешний сервис, опубликовав его в текущий spring-контекст. Так как сервисы могут появляться и пропадать, те, кому они нужны, могут либо подписаться на сервисы при старте, либо слушать события, чтобы узнать об их появлении или пропадании.

Бандл стартует только после того, как ссылки на сервисы будут разрешены. Соответственно, при инициализации такого бандла, контейнер найдет для него нужные ему внешние сервисы, и опубликует реализуемые внутри бандла, сделав их доступными извне.

Т.е. На самом деле, все немножко сложнее, потому что бандл может пользоваться списком похожих сервисов, и подписаться сразу на список. N. у сервиса, в общем случае, есть такое свойство, как cardinality, принимающее значение 0.. 1, описывает необязательный сервис, и в этом случае бандл успешно стартует, даже если такого сервиса в системе нет (а вместо ссылки на него получит заглушку). При этом подписка, где указано 0..

Map с данными. Замечу, что сервис — это именно любой интерфейс (а можно публиковать и просто классы), поэтому вы вполне можете в качестве сервиса опубликовать java.util.

Кроме всего прочего, service позволяет указать метаданные, а reference — искать сервис по этим метаданным.

Blueprint

Blueprint — это более новая инкарнация Spring DM, которая немного попроще. А именно, если в Spring у вас есть custom XML элементы, то тут их нет, за ненадобностью. Иногда это все же доставляет неудобства, но прямо скажем — нечасто. Если вы не мигрируете проект из Spring, то можно начать сразу с Blueprint.

Для знающих Spring тут нет вообще ничего незнакомого. Суть тут таже самая — это XML, где описаны компоненты, из которых собирается контекст бандла.

Вот пример, как описать DataSource, и экспортировать в виде сервиса:

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource"> <property name="URL" value="URL"/> <property name="user" value="USER"/> <property name="password" value="PASSWORD"/> </bean> <service interface="javax.sql.DataSource" ref="dataSource" id="ds"> <service-properties> <entry key="osgi.jndi.service.name" value="jdbc/ds"/> </service-properties> </service>
</blueprint>

Увидели, что бандл не запустился — в статусе Indtalled. Ну вот, мы задеплоили этот файл в папку deployment, и посмотрели результаты команды list. Пробуем start , и получаем сообщение об ошибке.

В чем дело? Теперь в списке бандл в статусе Failed. Наш DataSource запустился. Очевидно, в том, что ему тоже нужны зависимости, в данном случае — Jar с классами Oracle JDBC, а еще точнее — пакет oracle.jdbc.pool.
Находим нужный jar в репозитории, или скачиваем с сайта Oracle, и устанавливаем, как было описано раньше.

Ссылка на сервис называется в Blueprint reference (где-то, в контексте другого бандла): Как этим всем воспользоваться?

<reference id="dataSource" interface="javax.sql.DataSource"/>

Затем данный бин становится, как обычно, зависимостью для других бинов (в примере camel-sql):

<bean id="sql" class="org.apache.camel.component.sql.SqlComponent"> <property name="dataSource" ref="dataSource"/>
</bean>

Jar и Activator

Канонический способ инициализации бандлов — это класс, реализующий интерфейс Activator. Это типичный интерфейс жизненого цикла, содержащий методы start и stop, которым передается контекст. Внутри них бандл как правило запускает свои потоки, если нужно, начинает слушать порты, подписывается на внешние сервисы при помощи OSGI API, и так далее. Это самый пожалуй сложный, самый базовый, и самый гибкий способ. Мне он за три года не понадобился ни разу.

Настройки и конфигурирование

Понятно, что такая конфигурация DataSource, как приведена в примере, мало кому нужна. Логин, пароль, и прочее, все хардкодится внутри XML. Нужно вынести эти параметры наружу.

<property name="url" value="$"/>
<property name="user" value="${oracle.ds.user}"/>
<property name="password" value="${oracle.ds.password}"/>

Решение достаточно простое, и похожее на то, что применяется в классическом Spring: в определенный момент жизненного цикла контекста происходит подстановка значений свойств из разного рода источников.

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть