Pull to refresh

Кластер JBoss 7 — балансировка нагрузки с помощью Apache

Reading time 9 min
Views 14K
В прошлой статье мы настроили репликацию сессий пользователей на все узлы кластера JBoss. Само по себе такое действие никак не улучшает отказоустойчивость, и для того, чтобы использовать полученную функциональность, требуется балансировщик нагрузки, который будет распределять внешние обращения между узлами кластера. Распределять нагрузку между узлами будет веб-сервер Apache, в соответствии с рекомендациями в документации по JBoss. Вся информация в статье доступна в различных источниках, но она разрознена, и мне не попалось ни одного ресурса, где все было бы собрано в одном месте, с описанием решения проблем, которые могут возникнуть на практике (лично у меня возникли, поэтому делюсь рецептами). Статья не претендует на полноту информации, скорее наоборот — описана минимальная конфигурация. Предназначена как для специалистов, которых интересуют конкретные конфигурации и настройки, так и для людей далеких от данной темы, но которым интересно, что такое кластеризация.

Общие принципы работы

Специалисты, знакомые с принципами кластеризации, могут пропустить этот раздел. Для наглядности буду рассматривать конкретную ситуацию, с которой встречаются многие: существующая система, состоящая из одного сервера приложений, переходит на кластерное решение. Самый лучший вариант в таких случаях — не пытаться сразу объять необъятное, а построить минимальную работающую конфигурацию: два сервера приложений в кластере, а перед ними веб-сервер в качестве интерфейса (frontend) к клиентам.

Схема работы следующая: все запросы, ранее отправляемые непосредственно на http/https коннектор JBoss-а, теперь должны обращаться к веб-серверу (Apache). Apache настраивается таким образом, чтобы «знать» о существовании «за его спиной» двух серверов приложений. При первом обращении клиента к Системе, веб-сервер выбирает один из серверов приложений (Узел-1) и перенаправляет запрос к нему. Создается сессия, в нее добавляется Cookie, которая в дальнейшем используется веб-сервером для того, чтобы «приклеить» все последующие запросы того же клиента к выбранному серверу приложений. Узел-1, обрабатывающий запросы клиента, при создании/изменении сессии реплицирует её состояние на остальные узлы кластера, где они и висят мёртвым грузом, пока что не принося никакой пользы.
Подробнее о Сессии
Упрощенно. Сессия — объект, создаваемый на стороне JBoss, имеющий различные атрибуты. Сессия имеет идентификатор, который передается в браузер при создании сессии, и при каждом обращении клиента к серверу передается браузером обратно серверу (обычно, в виде Cookie). Сервер по идентификатору находит сессию и обрабатывает запрос в контексте данных найденной сессии. Задача репликации в кластере — передача данных о сессии на остальные узлы таким образом, чтобы при обращении клиента к ним, любой другой узел смог бы так же, как и Узел-1 найти сессию (и данные в ней сохраненные) по идентификатору и обработать запрос клиента в её контексте.
В случае «падения» Узла-1, при очередном обращении клиента, Apache обнаруживает, что сервер недоступен, и перенаправляет запрос на другой узел (Узел-2). Вот тут и начинает использоваться тот самый «мертвый груз» — сессия, состояние которой уже есть на всех узлах кластера. Запрос обрабатывается Узлом-2, и Apache «приклеивает» сессию к нему, т.е. все последующие запросы пользователя теперь сразу направляются на Узел-2.

Базовая кофигурация

Если на стороне JBoss уже настроена репликация сессий, то дополнительных настроек не требуется, всё, что нужно (AJP коннектор), уже настроено в конфигурации по умолчанию. Требуется скачать и установить Apache HTTP Server (процесс установки выходит за рамки данной статьи). Вся настройка осуществляется путем редактирования файла conf/httpd.conf.
  1. Подключение модулей. Раскоментируйте либо добавьте следующие строки:
    LoadModule proxy_module modules/mod_proxy.so
    LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
    LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
    LoadModule headers_module modules/mod_headers.so

    Модули mod_proxy.so, mod_proxy_ajp.so и mod_proxy_balancer.so требуются для балансировщика нагрузки. Если в Системе нет понятия «сессия», то их достаточно. Я, например, работал с системой, которая обрабатывала запросы посредством веб-сервисов, это единичные обращения, не связанные между собой, поэтому сессии не создавались. В нашем случае есть пользователи и их сессии, поэтому требуется добавить модуль mod_headers.so, который позволит отслеживать данные о сессии, передаваемые браузером.
  2. Настройка балансировки. Добавьте следующие строки:
    NameVirtualHost 192.168.1.0:80
    <VirtualHost 192.168.1.0:80>
    	Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
    	<Proxy balancer://ajp-cluster>
    		BalancerMember ajp://192.168.1.1:8009 route=node1
    		BalancerMember ajp://192.168.1.2:8009 route=node2
    		ProxySet stickysession=ROUTEID
    	</Proxy>
    	<Location />
    		ProxyPass balancer://ajp-cluster/
    	</Location>
    </VirtualHost>

    Вместо 192.168.1.0 указываете адрес Apache.
    В разделе Proxy указываете список адресов AJP-коннекторов узлов кластера, по одному вхождению BalancerMember для каждого узла. В конфигурации JBoss 7 по умолчанию AJP-коннектор прослушивает порт 8009. Параметр route присваивает уникальное имя узла, используемое только самим Apache, т.е. совсем необязательно совпадение с именами, указанными при старте JBoss (параметр jboss.node.name). Больше ничего менять не требуется, кластер готов к запуску и тестированию.
    Подробнее о конфигурации
    Директива "Header add Set-Cookie" действует совместно с директивой "ProxySet stickysession" следующим образом. При первом обращении клиента к Apache, после того, как балансировщик выбрал на какой узел он будет перенаправлен, в сессию пользователя добавляется Cookie с именем "ROUTEID", значением которой является идентификатор выбранного узла (node1/node2 в конфигурации выше). При последующих обращениях клиента, Apache уже не надо «думать» о том, какой был ранее выбран узел, Cookie явно сообщает веб-серверу на какой узел следует перенаправить запрос. Директива «Header» ответственна за существование Cookie в сессии, а директива «ProxySet stickysession» за использование ее при маршрутизации для «приклеивания» сессии к конкретному узлу. Если ваш сервер не работает с сессиями (например, как в моем примере выше, только веб-сервисы), то директивы «Header» и ProxySet не нужны.
    JSESSIONID
    Во многих примерах конфигурации предлагается использовать в качестве маркера узла идентификатор сессии JSESSIONID. Это кажется логичным, т.к. такой идентификатор используется в большинстве веб-приложений на Java. Пример с apache.org:
    ProxyPass /test balancer://mycluster stickysession=JSESSIONID|jsessionid scolonpathdelim=On
    <Proxy balancer://mycluster>
    BalancerMember http://192.168.1.50:80 route=node1
    BalancerMember http://192.168.1.51:80 route=node2
    </Proxy>
    Сразу скажу: для JBoss это не работает, по крайней мере, в конфигурации по-умолчанию. Данный метод предполагает, что значение Cookie с именем JSESSIONID, сформированное на стороне сервера приложений, состоит из двух частей: идентификатор сессии дополняется идентификатором сервера, они разделяются точкой. JBoss 7 формирует простую Cookie JSESSIONID, которая содержит только идентификатор сессии, значение которого сгенерировано случайным образом и не несет никакой смысловой нагрузки.
    Таким образом, основные отличия от описанной мной конфигурации следующие:
    • В описанной в статье конфигурации идентификатор узла и идентификатор сессии находятся в разных Cookie, здесь два значения объединены в одну Cookie.
    • В описанной в статье конфигурации идентификатор узла формируется на стороне балансировщика (логично: кто использует, тот и формирует), а идентификатор сессии на стороне сервера приложений (тот же принцип: идентификатор сессии нужен JBoss-у, а не Apache). Здесь же оба значения генерируются на стороне сервера приложений (либо веб-сервера на бэкенде).



Настройка SSL (HTTPS)

Если до кластеризации вы использовали шифрование при обмене данными с клиентом, общаясь с ним по протоколу HTTPS, то настройки сертификатов осуществлялись на стороне JBoss, в конфигурации HTTPS-коннектора. При переходе на кластерное решение сертификатами должен управлять Apache.

Общие принципы
Процесс следующий: HTTPS-запросы от клиента поступают на веб-сервер Apache, расшифровываются и передаются серверу приложений в открытом виде посредством протокола AJP. Ответ от сервера приложений передается на веб-сервер также в открытом виде, шифруется там и передается клиенту по протоколу HTTPS. Таким образом, между Apache и JBoss существует так называемая «демилитаризованная зона», т.е. закрытое от внешнего неавторизованного доступа окружение, в котором между серверами настроены доверительные отношения, не требующие дополнительной защиты от несанкционированного доступа на прикладном уровне. Доступным извне должен быть только Apache, по HTTPS порту (обычно, 443), все остальное должно быть наглухо закрыто.

Настройки веб-сервера в conf/httpd.conf:
  1. Прослушка порта 443. Веб-сервер Apache в конфигурации по умолчанию «слушает» только порт 80. Для того, чтобы он стал прослушивать порт 443, следует добавить строчку:
    Listen 443
  2. Подключение модулей. Раскоментируйте либо добавьте следующую строчку:
    LoadModule ssl_module modules/mod_ssl.so

  3. Настройка балансировки. Добавьте следующие строки:
    NameVirtualHost 192.168.1.0:443
    
    <VirtualHost 192.168.1.0:443>
    	Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
    	<Proxy balancer://ajp-cluster>
    		BalancerMember ajp://192.168.1.1:8009 route=node1
    		BalancerMember ajp://192.168.1.2:8009 route=node2
    		ProxySet stickysession=ROUTEID
    	</Proxy>
    	<Location />
    		ProxyPass balancer://ajp-cluster/
    	</Location>
    
    	ProxyRequests off
    	SSLEngine on
    	SSLCertificateFile c:/Apache2/conf/my.cer
    	SSLCertificateKeyFile c:/Apache2/conf/my.key
    </VirtualHost>
    


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

Выше описана конфигурация Apache 2.2, начиная с версии 2.4 следует добавить модули mod_lbmethod_byrequests.so и mod_slotmem_shm.so, и удалить объявления NameVirtualHost, они теперь не нужны.
Проблемы с сертификатами и их решение
При настройке шифрования на стороне JBoss, требовался один файл, содержащий ключ и сертификат, например, в формате PKCS#12. Apache требует разделения на два файла, в одном файле сертификат, в другом — закрытый ключ. Кроме этого, Apache имеет два ограничения:
  1. Распознается только формат PEM. Узнать, соответствует ли ваш ключ этому формату, можно открыв файл ключа текстовым редактором. В содержании должны присутствовать строки "-----BEGIN PRIVATE KEY-----" и "-----END PRIVATE KEY-----", длина строк между ними не должна превышать 64 символа. То же касается сертификата, со строками, соответственно "-----BEGIN CERTIFICATE-----" и "-----END CERTIFICATE-----".
  2. Сборка Apache под ОС Windows не умеет открывать хранилища ключей, защищенные паролем.

Все вышеперечисленные проблемы решаются при помощи утилиты Openssl. Из вашего файла хранилища ключа и сертификата, используемого в JBoss, следует извлечь ключ и сертификат в отдельные файлы, осуществив при этом конвертацию в нужный формат и сняв защиту паролем. Вот пример конвертации ключа из формата PKCS#12 (поддерживаются и другие форматы, Google вам в помощь):
openssl pkcs12 -nocerts -nodes -in C:\key.p12 -out C:\Apache2\conf\my.key
openssl pkcs12 -clcerts -nokeys -nodes -in C:\key.p12 -out C:\Apache2\conf\my.cer

ВНИМАНИЕ! Не забудьте удалить со своего компьютера полученный файл my.key после переноса его на промышленный сервер, т.к. он ничем не защищен и может быть скомпрометирован.

Заключение

После применения описанного в статье решения, в принципе, можно вообще удалить из настроек JBoss описание коннекторов HTTP/HTTPS за ненадобностью, т.к. теперь все внешние обращения будут поступать через AJP коннектор. Это заметно повысит безопасность сервера, особенно для сканирующих утилит, которые особое внимание уделяют этим протоколам.

PS

Предлагаемый вариант кластеризации частично решает проблемы балансировки нагрузки и отказоустойчивости. Для полноценной конфигурации здесь не хватает кластеризации СУБД и дублирования единой точки входа — Apache. Также очень важна конфигурация аппаратной инфраструктуры: дублирование сетей и маршрутизаторов, серверных компонентов, бесперебойное питание и прочее. Тем не менее, данная статья поможет развить систему по принципу 80/20, т.е. путем небольших затрат получить значительное повышение производительности и отказоустойчивости. В своей практике мне не довелось встретить решений, в которых пошли дальше, разве что встречалась кластеризация СУБД, но если доведется столкнуться, обязательно напишу об этом. Если же ваши сервисы высококритичны, то предложенное мной решение — лишь первый шаг.

Есть похожая статья на Хабре, рекомендую ознакомиться также и с ней. Несмотря на тематическую схожесть, моя статья заметно отличается, и, на мой взгляд, имеет полное право на существование. Я постарался доходчиво рассказать о том, как и за счет чего все работает, там же просто выложено содержание конфигурационных и командных файлов. К тому же, в статье по ссылке, в Apache используется mod_jk для балансировки, у меня же mod_proxy. Stackoverflow пишет, что основным минусом mod_jk является "Need to build and maintain a separate module". Именно с этим столкнулись на практике наши системные администраторы, которые так и не смогли установить этот модуль на Apache под AIX, так что если у вас схожая проблема, можете попробовать mod_proxy, который поставляется в комплекте с Apache и, на мой взгляд, проще в настройке.
Tags:
Hubs:
+5
Comments 12
Comments Comments 12

Articles