Боевые заметки системного инженера: оптимизация Portal от СТАРКОВ Групп

9 0

Доброго дня друзья, коллеги и все неравнодушные к продуктам компании Directum лица). Хочу поговорить немного про Portal, его запуск и сопровождение после внедрения. Учитывая, что продукт довольно новый и еще не оброс мясом из «питона» как все продукты Directum, у него есть ряд нюансов, с которыми придется столкнуться при внедрении и сопровождении.

Немного напомню: Portal очень удобная и гибкая структура, позволяющая создать онлайн-портал или, если угодно, сайт компании, соединить его с какими угодно сервисами (в частности виджеты Directum RX) для удобства и наполнить любым контентом. Все это счастье сделано на основе свободно распространяемого программного обеспечения Liferay с добавлением сервисов идентификации, используемых в HR Pro, приправлено менеджером сообщений и Elasticsearch, завернуто в docker и иногда посыпано каким-либо прокси (nginx, traefik, haproxy).

Подробнее о истории продукта и его подноготной можно почитать в моей прошлой статье Directum Portal – единое пространство на базе Liferay

Основа

Итак установка и настройка портала описана в прилагаемой вендором инструкции, повторяться не будем. Мы в компании "СТАРКОВ Групп" после внедрения продукта у заказчиков выявили несколько моментов, которые дополняют настройку и делают более расширенной. В качестве первого пункта нашего экскурса хочу остановиться именно на них.

1. Время жизни сессии

На сервисе идентификации есть параметры, отвечающие за время сессии, в частности параметр Authentication__SessionCookieLifetime. Данная информация есть в справке по сервису идентификации на любой версии HR Pro, но есть одно «НО»: этот параметр отвечает за время жизни cookies на самом сервисе идентификации, а не в портале. Чтобы увеличить время жизни сессии на портале, необходимо сделать следующее: выдернуть из контейнера с порталом файлик web.xml, он лежит по пути /opt/liferay/tomcat/webapps/ROOT/WEB-INF/web.xml. В нем необходимо поправить параметр session-timeout в блоке session-config по примеру:

<session-config>
    <session-timeout>10080</session-timeout>
        <cookie-config>
            <http-only>true</http-only>
        </cookie-config>
</session-config>

Далее - сохранить исправленный web.xml в директории portal и добавить его в docker-compose следующей строкой:

./web.xml:/opt/liferay/tomcat/webapps/ROOT/WEB-INF/web.xml:rwx

После загрузки/перезагрузки портала время сессии внутри самого портала изменится.

2. Доступные порталу ресурсы

При начальном конфигурировании и запуске портала он не использует все ядра и оперативную память машины, на которой запущен - у портала есть строго заданные ограничения. Если даже вы добавите оперативной памяти на машину и увеличите количество ядер, плюсом внесете кучу виджетов и тяжелого контента, портал будет работать все медленнее и медленнее с каждым разом, потому что выделенные ему ресурсы не менялись с самого старта системы. Вся эта история с выделенными ресурсами тянется из Liferay. Чтобы портал работал с определенным количеством ресурсов, либо не выедал все и сразу, были созданы настройки и ограничения с помощью компонента сервера приложений Apache Tomcat – Catalina. За настройку Catalina отвечает небольшой скрипт настройки переменных сред (setenv.sh), который находится по пути в контейнере portal: /opt/liferay/tomcat/bin/setenv.sh.

В этом скрипте настраиваются не только выделенные для портала ресурсы, но и очень много других немаловажных параметров. Нас интересует только строка «CATALINA_OPTS=», именно в ней мы будем перечислять все необходимые настройки:

  • -Dfile.encoding=UTF-8 – кодировка UTF8, думаю, не надо объяснять, зачем эта настройка;
  • -Duser.timezone=GMT – тоже знакомо наверняка, настройка временной зоны;
  • -Djava.locale.providers=JRE,COMPAT,CLDR – немаловажный аргумент для отображения «годов» в четырехзначном виде, небольшой «косяк» локали UNICODE из репозитория JDK9;
  • -Djava.net.preferIPv4Stack=true – из названия понятно, что это работа со стеком протокола TCPIPv4;

Это были четыре обязательные опции, которые есть по умолчанию, и их менять не обязательно. Едем дальше:

  • -Xms8g – начальный минимальный размер оперативной памяти, минимум с этим размером запускается portal (можно указывать в мегабайтах “m” или в гигабайтах “g”);
  • -Xmx16g – конечный максимальный размер оперативной памяти, до этого значения можно заполнять память portal;
  • -XX:MaxNewSize=1536m – максимальный размер нового пространства, весь стек памяти состоит из пространств, в которых живет “portal”;
  • -XX:NewSize=1536m – почти тоже самое, что и предыдущее, размер нового пространства по умолчанию;
  • -XX:MaxMetaspaceSize=1g – это максимальный размер области памяти для хранения метаданных классов в JVM (Java Virtual Machine), по умолчанию память классов не ограничена, и если этот параметр не настроен, то вы рискуете столкнуться с ошибкой “OutOfMemory”;
  • -XX:SurvivorRatio=12 – отношение «старого» и «молодого» пространства друг к другу, как уже упоминал ранее JVM состоит из пространств, где хранятся данные, при поступлении новых данных часть остается в новом пространстве, часть переходит в старое, отношение показывает сколько хранить в новом, сколько передавать в старое, значение по умолчанию 12-14;
  • -XX:MaxTenuringThreshold=15 – кол-во циклов на объект в новом пространстве до перехода в старое, по умолчанию 13, 15 – это максимальное, распределение данных по пространствам регулирует текущую нагрузку на портал;
  • -XX:+UseLargePages – использовать память больших страниц с данными, очень аккуратно ставьте эту настройку, ее использование рекомендуется только на сильных «машинах», при использовании на малом количестве памяти может привести к зависанию;
  • -XX:LargePageSizeInBytes=256m – ограничение потребления памяти для больших страниц с данными;
  • -XX:ParallelGCThreads=8 – количество параллельных потоков сборщиков «мусора», т.е. различный служебный кэш, метрики и т.д. собираются и очищаются из системы, для более стабильной работы желательно ставить количество сборщиков равное количеству ядер;
  • -XX:+UseConcMarkSweepGC – включение сборщика, используется вместе с предыдущим параметром;
  • -XX:+CMSParallelRemarkEnabled – включение параллельных потоков сборки, также используется с предыдущими двумя параметрами;
  • -XX:CMSInitiatingOccupancyFraction=85 – этот параметр отвечает за порог включения сборщиков мусора при проценте использования памяти, соответственно, в данном случае, сборщик включается при достижении использования памяти в 85%, чем больше памяти на «машине» чем больше можно ставить «процент»;
  • -XX:+UseCompressedOops – использовать сжатие данных;
  • -XX:+DisableExplicitGC – отключение «явной» постоянной сборки мусора Java, т.е. сборка включается только при срабатывании «триггеров» в других ранее описанных параметрах;
  • -XX:-UseBiasedLocking – опция, которая отключает использование оптимизации Java, оптимизация очень затратна по ресурсам и практически бесполезна с точки зрения Liferay, в JVM 15 версии отключена по умолчанию;
  • -XX:InitialCodeCacheSize=32m – размер начального кэша кода для виртуальной машины Java, оптимальный размер 32 мегабайта;
  • -XX:ReservedCodeCacheSize=96m – это максимальный размер кэша для скомпилированного кода, он позволяет оптимизировать работу с большими плагинами и ресурсоемкими базами данных;
  • -XX:ActiveProcessorCount=8 – из названия читается назначение этого параметра, количество выделенных активных процессоров для Java машины.

В итоге конечная запись в скрипте setenv.sh будет иметь вид:

CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8 -Djava.locale.providers=JRE,COMPAT,CLDR -Djava.net.preferIPv4Stack=true -Duser.timezone=GMT -Xms8g -Xmx16g -XX:MaxNewSize=1536m -XX:MaxMetaspaceSize=1g -XX:MetaspaceSize=1g -XX:NewSize=1536m -XX:SurvivorRatio=12 -XX:MaxTenuringThreshold=15 -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:ParallelGCThreads=8 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCompressedOops -XX:+DisableExplicitGC -XX:-UseBiasedLocking -XX:InitialCodeCacheSize=32m -XX:ReservedCodeCacheSize=96m -XX:ActiveProcessorCount=8"

После внесения изменений в setenv.sh сохраняем его в директории с portal, вносим строчку монтирования в контейнер, монтируется данный скрипт, и стартуем/перегружаем портал для применения:

- ./setenv.sh:/opt/liferay/tomcat/bin/setenv.sh:rwx

Также данные настройки можно указывать в файле настроек портала - portal-ext.properties. Но практика показала, что не все параметры оттуда считываются, и лучше непосредственно указывать в самом скрипте.

Мы с вами прошлись по дополнительным настройкам portal, которые помогут оптимизировать запуск и работу приложения в рамках плавного распределения нагрузки на имеющиеся ресурсы, также после внесения данных изменений вы можете наблюдать за ресурсами портала на странице «Администрирование сервера»:

 

Настройка перезагрузки портала

Далее вторым и заключительным пунктом нашего экскурса будет настройка перезагрузки портала. При стандартной перезагрузке методом “docker-compose up -d” у нас поднимается контейнер портала, загружается liferay и показывается стандартная страница приветствия Portal без наших доработок, виджетов, плагинов и так далее. Это происходит из-за того, что не применяется разработка, которая хранится в каталоге /opt/liferay/deploy, хотя у нас изначально в файле docker-compose.yml есть точка монтирования согласно инструкции к развертыванию ./deploy:/opt/liferay/deploy. Данная проблема решается копированием текущей разработки из хранилища (в контейнере /mnt/liferay/deploy): нужно из папки ./mnt/liferay/deploy скопировать в папку ./opt/liferay/deploy все содержимое (делается в контейнере). После этих нехитрых действий в логе Liferay отражается тема sungero и вся наша текущая разработка, а web-страница портала загружается уже с привычным нам видом.

Едем дальше… Полный комплект сервисов портала представляет собой несколько контейнеров, а именно:

  • контейнер portal (собственно, сам портал);
  • контейнер ids (сервис идентификации);
  • контейнер message-broker (брокер сообщений);
  • контейнер message-scheduler (запуск брокера сообщений);
  • контейнер nginx (или другого прокси, в случае развертывания IDS из комплекта HRpro - traefik);
  • контейнер Rabbit (стандартный брокер);
  • контейнер Postgres (куда ж без БД);
  • контейнер Elasticsearch (у портала есть встроенный функционал поиска, но его можно вывести в отдельный контейнер для разгрузки).

Потихоньку мы подобрались к вопросу перезагрузки. Все это добро приходится перезапускать по отдельности, делать заранее копию базы данных на всякий случай, копию разработки, метаданных портала, потом по каждому docker-compose.yml запускать “UP -d”, потом еще проверять лог, а поднялось ли, и перезапускать отдельно какой-либо сервис.

В прошлой статье, уже описывался скрипт, который перегружает портал и делает бэкап, однако я его немного изменил, добавил «мяса». Для оптимизации процесса я изменил скрипт, который теперь работает как служба, запускает или перезапускает все сервисы portal, проверяет запустились ли они, предварительно делает бэкап базы данных и метаданных портала, после загрузки применяет разработку и, плюсом, логирует все свои действия:

#!/bin/bash

# Логирование

LOG_FILE="/srv/logs/portal-restart.log"
exec > >(tee -a "$LOG_FILE") 2>&1

echo "========================================="
echo "Запуск скрипта перезапуска Portal"
echo "Время запуска: $(date)"
echo "========================================="

# Проверка, что Docker запущен
if ! systemctl is-active --quiet docker; then
  echo "Ошибка: Docker не запущен. Запускаю Docker..."
  systemctl start docker
  sleep 10
fi

# Проверка доступности Docker
if ! docker info >/dev/null 2>&1; then
  echo "Ошибка: Docker недоступен"
  exit 1
fi

c_name="portal-directum"

yml="/srv/portal/portal/docker-compose.yml"
ymlMB="/srv/portal/MB/docker-compose.yml"
ymlBD="/srv/portal/postgres/docker-compose.yml"
ymlRab="/srv/portal/rabbit/docker-compose.yml"
ymlIds="/srv/portal/Ids/docker-compose.yml"
ymlNg="/srv/portal/nginx/docker-compose-nginx.yml"
ymlELK="/srv/portal/elastic/docker-compose.yml"
c_name_bd="postgres"
bd_name="portal_01"
DATE=$(date +"%Y%m%d_%H%M")

# Функция для ожидания готовности контейнера
wait_for_container() {

  local container_name=$1
  local max_attempts=$2
  local attempt=1
  echo "Ожидание запуска контейнера $container_name..."
  while [ $attempt -le $max_attempts ]; do
    if docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then
      if docker exec "$container_name" sh -c "echo 'Container is ready'" >/dev/null 2>&1; then
        echo "Контейнер $container_name готов"
        return 0
     fi
   fi
   echo "Попытка $attempt/$max_attempts: контейнер $container_name не готов"
  sleep 5
  ((attempt++))
  done
  echo "Ошибка: контейнер $container_name не запустился за отведенное время"
  return 1
}

# Бэкап базы данных
echo "=== Бэкап базы данных ==="
if wait_for_container "$c_name_bd" 12; then
  echo "Выполняю бэкап БД в контейнере $c_name_bd"
  docker exec "$c_name_bd" sh -c "pg_dump -U user -d $bd_name -f /srv/backup/${bd_name}_${DATE}.dump"
  echo "Бэкап создан: /srv/backup/${bd_name}_${DATE}.dump"
else
  echo "Пропускаем бэкап БД, контейнер не доступен"
fi

echo ""
echo "=== Перезагрузка сервисов на сервере ==="

# Перезагрузка прокси
echo "> Перезагрузка Прокси"
docker-compose -f "$ymlNg" down
docker-compose -f "$ymlNg" up -d

# Перезагрузка ELK
echo "> Перезагрузка ELK Стека"
docker-compose -f "$ymlELK" down
docker-compose -f "$ymlELK" up -d

# Бэкап Portal
echo "> Бэкап Portal"
rm -rf /srv/backup_portal/portal
cp -r /srv/portal/portal /srv/backup_portal
cp /srv/backup_portal/portal/portal-ext.properties /srv/portal/portal

# Остановка portal и сервиса идентификации
echo "> Остановка portal и сервиса идентификации"
docker-compose -f "$yml" down
docker-compose -f "$ymlIds" down

# Перезагрузка Базы данных
echo "> Перезагрузка Базы данных"
docker-compose -f "$ymlBD" down
docker-compose -f "$ymlBD" up -d

# Ждем запуска БД
wait_for_container "$c_name_bd" 30

# Запуск Сервиса идентификации
echo "> Запуск Сервиса идентификации"
docker-compose -f "$ymlIds" up -d

# Перезагрузка брокеров сообщений
echo "> Перезагрузка основного и дополнительного Брокеров сообщений"
docker-compose -f "$ymlRab" down
docker-compose -f "$ymlRab" up -d
docker-compose -f "$ymlMB" down
docker-compose -f "$ymlMB" up -d

# Запуск Portal
echo "> Запуск Portal"
docker-compose -f "$yml" up -d

# Ожидание загрузки Portal
echo "Ожидание загрузки Portal..."
if wait_for_container "$c_name" 180; then
  echo "Portal загружен, копируем файлы разработки"
  # Даем дополнительное время на полный запуск Liferay
  sleep 30

  # Копирование разработки
  echo "> Копирование разработки в контейнер"
  docker exec "$c_name" sh -c "cp /mnt/liferay/deploy/* /opt/liferay/deploy/ 2>/dev/null || echo 'Нет файлов для копирования или директория не существует'"
else
  echo "Ошибка: Portal не запустился за отведенное время"
  exit 1
fi

echo "=== Скрипт успешно завершен ==="

Пример вывода лога:

Как описано выше, скрипт работает в связке со службой. Чтобы ее сделать, создаем в systemd юнит со следующем содержимым:

[Unit]
Description=Portal Restart Service
After=docker.service network.target
Requires=docker.service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
RemainAfterExit=yes
User=root
Group=root
WorkingDirectory=/srv/portal
ExecStart=/srv/portal/restart-portal.sh
Restart=on-failure
#RestartSec=30s

# Настройки журналирования
StandardOutput=journal
StandardError=journal
SyslogIdentifier=restart-portal

# Таймауты (увеличьте при необходимости)
#TimeoutStartSec=1800
#TimeoutStopSec=300

# Переменные окружения (добавить при необходимости)
#Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
#Environment="DOCKER_HOST=unix:///var/run/docker.sock"

[Install]
WantedBy=multi-user.target
Alias=portal.service

Соответственно, исходя из настроек службы нам достаточно выполнить systemctl restart/start portal.service - и все . Выполнится бэкап метаданных, базы данных, перезапустятся все контейнеры, накатится разработка и все залогируется.

В заключение

Мы рассмотрели дополнительные технические нюансы портала. Надеюсь, вам они помогут при настройке и развертывании. После того как вы развернете портал, возможно, потребуется установить плагины, виджеты, настроить CSS, об этом есть отдельная статья нашего разработчика Никиты Вершинина (Опыт внедрения Portal) и его же статья на Directum Awards 2026 про внедрение портала в нашей организации (Цифровое сердце СТАРКОВ Групп на Directum Portal).

Пишите в комментариях вопросы, буду рад обсудить!

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий