/
/
Отказоустойчивый портал Битрикс24

Построение отказоустойчивой инфраструктуры для корпоративного портала на Битрикс24

О клиенте

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

Инфраструктуру нужно было подготовить к росту, повысить ее доступность и сделать управление серверами воспроизводимым.

Исходная ситуация

К началу проекта production-система работала на одной крупной виртуальной машине под управлением старой версии BitrixVM. На этом же сервере находились веб-приложение, фоновые задачи, кеш, сессии и несколько вспомогательных компонентов.

Развертывание кода выполнялось через подключение CI-системы к серверу по SSH и команду git pull. При этом на production были обнаружены локально измененные файлы, которых не было в репозитории. Конфигурация рабочего сервера не могла считаться полностью воспроизводимой: при его пересоздании существовал риск потерять незадокументированные изменения.

База данных уже была вынесена в управляемый облачный сервис, но в ней накопились проблемы с запросами, индексами и размером отдельных таблиц. Часть данных и пользовательских сессий продолжала храниться локально, поэтому простое добавление веб-серверов не обеспечивало отказоустойчивость.

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

Какие проблемы мы обнаружили

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

Целевой уровень доступности нельзя было обеспечить только увеличением ресурсов этой машины. Требовалось разделить роли и разместить однотипные компоненты в нескольких зонах доступности.
Состояние приложения было привязано к локальному серверу
Кеши, пользовательские сессии и данные отдельных модулей хранились на локальных сервисах или в файлах. При запуске нескольких веб-серверов запросы одного пользователя могли попадать на разные узлы. Без общего хранилища это приводило бы к потере сессий и непоследовательному поведению приложения.

При этом механический перенос всех данных в управляемый Redis оказался неэффективным. Проверка на препродуктивном стенде показала, что использование сетевого Redis для файлового кеша увеличивало время генерации страниц в несколько раз. Архитектуру хранения пришлось разделить: общее состояние вынести наружу, а чувствительный к задержкам кеш оставить локальным.
Production нельзя было гарантированно восстановить из кода
На рабочем сервере находились изменения, не зафиксированные в Git. Конфигурация создавалась и дорабатывалась непосредственно на машине, а процесс развертывания сводился к обновлению рабочей копии репозитория.

Из-за этого исходный сервер фактически становился уникальным экземпляром. Даже при наличии резервных копий восстановление требовало бы ручного воспроизведения его настроек и поиска отличий от репозитория.
База данных содержала системные ограничения производительности
Диагностика управляемой MySQL показала длительные запросы, выборки десятков тысяч и миллионов строк и обращения к таблицам без необходимых индексов.

В одной из таблиц за два месяца накопилось около 38 ГБ отладочных записей. Индексы отсутствовали, а причиной роста оказался включенный режим расширенного логирования. Его отключили еще во время обследования, после чего таблицу можно было очистить.

Были обнаружены и запросы, которые нельзя ускорить переносом приложения на новые серверы. Например, подсчет записей в таблице примерно на 10 млн строк занимал несколько секунд как на старом, так и на новом стенде. Причина находилась в объеме данных и логике приложения, а не в конфигурации веб-сервера.
Существовал риск раскрытия данных через объектное хранилище
Один из облачных бакетов допускал публичное чтение. По структуре каталогов и названиям файлов содержимое не выглядело предназначенным для свободного доступа.

Публичный бакет не участвовал в работе основного сайта, однако сам факт доступности создавал риск выгрузки его содержимого любым человеком, знающим имя хранилища. Вопрос потребовал проверки владельца данных и назначения бакета.
Архитектура фоновых процессов не была рассчитана на несколько серверов
В системе работали стандартные задания Битрикс, пользовательские cron-скрипты и Kafka-консьюмеры. Часть обработчиков использовала Redis не только как кеш, но и как хранилище состояния обрабатываемого сообщения.

Поэтому фоновые процессы нельзя было просто запустить на каждом новом сервере. Для разных типов задач требовались разные модели: Kafka-консьюмеры могли работать параллельно с учетом числа партиций, а cron-задания должны были выполняться контролируемо, без дублирования.

Дополнительно выяснилось, что некоторые пользовательские обработчики некорректно завершались по системному сигналу. Это мешало безопасно управлять ими через оркестрацию процессов.
Переход на PostgreSQL нельзя было объединить с миграцией инфраструктуры
Первоначальный план включал перенос базы с MySQL на PostgreSQL. Проверка установленных модулей показала, что несколько используемых компонентов Битрикс не имели штатных сценариев миграции или заявленной поддержки PostgreSQL.

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

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

Что было сделано

1
Спроектировали целевую архитектуру
Production разделили на несколько функциональных слоев:
  • веб-серверы за облачным балансировщиком;
  • отдельные серверы для cron-заданий и Kafka-консьюмеров;
  • управляемую MySQL;
  • общие хранилища сессий и состояния сервисов;
  • существующий управляемый Kafka;
  • отдельный сервер доступа;
  • отдельный GitLab Runner.

Однотипные серверы предусмотрели в разных зонах доступности. Прямой доступ пользователей к виртуальным машинам исключили: HTTP- и HTTPS-трафик проходит через балансировщик, административное подключение — через выделенную точку входа.

Препродуктивный контур построили как уменьшенную копию production. Это позволило проверять балансировку, распределенное хранение состояния и работу фоновых процессов до переключения пользователей.
2
Перевели инфраструктуру в код
Для создания облачных ресурсов подготовили Terraform-сценарии, запускаемые из GitLab CI. Для настройки операционной системы и компонентов приложения разработали Ansible-роли.

Сценарии охватили:
  • создание виртуальных машин и балансировщиков;
  • сетевые настройки;
  • базовую настройку серверов;
  • установку Nginx, PHP-FPM и Node. js;
  • настройку веб- и фоновых узлов;
  • подключение внешних хранилищ;
  • установку экспортеров и сбор метрик;
  • настройку ротации логов;
  • управление пользователями и SSH-ключами;
  • развертывание приложения из репозитория.

Новый сервер стал создаваемым объектом, а не уникальной машиной, которую необходимо вручную восстанавливать по памяти.
3
Построили новый процесс развертывания
Развертывание приложения связали с GitLab CI. При первичной настройке сервер получает код из репозитория, после чего создаются необходимые каталоги и конфигурационные файлы. Последующие обновления выполняются через управляемый pipeline.

Это позволило отделить код приложения от состояния конкретной виртуальной машины и подготовило систему к работе с несколькими однотипными узлами.
4
Разделили хранение кеша и пользовательского состояния
На препродуктивном контуре проверили несколько вариантов хранения.

Пользовательские сессии и данные, которые должны быть общими для всех узлов, вынесли из файловой системы веб-серверов. Для них настроили отдельное общее хранилище.

Файловый кеш оставили в локальном Memcached на каждом веб-сервере. Тестирование показало, что сетевое хранилище добавляло задержку и ухудшало генерацию страниц. Разделение позволило совместить работу нескольких веб-узлов с приемлемой производительностью.

Для пользовательского модуля очередей добавили возможность задавать внешний Redis через конфигурацию. Push-сервер доработали для подключения к Redis с аутентификацией.
5
Разделили веб-нагрузку и фоновые процессы
Веб-запросы распределили между несколькими backend-серверами через облачный балансировщик.

Cron-задания и Kafka-консьюмеры перенесли на отдельные узлы. Для них уточнили правила параллельного запуска и исключили неконтролируемое дублирование задач. Kafka-обработчики запускались с учетом структуры топиков и партиций, а выполнение cron-заданий оставалось управляемым.

Перед переключением production фоновые процессы сначала отключили на старом сервере, затем запустили на новых узлах и проверили их работу с реальными интеграциями.
6
Подготовили переход без длительной остановки
Миграцию разбили на отдельные переключения:
  1. перенос сессий и состояния сервисов во внешние хранилища;
  2. запуск новых веб- и фоновых серверов;
  3. проверка приложения на новых backend-узлах;
  4. отключение фоновых задач на старом сервере;
  5. включение задач на новых серверах;
  6. переключение DNS на адрес облачного балансировщика.

Для DNS заранее уменьшили TTL. Это позволяло быстрее вернуть трафик на старую инфраструктуру, если после переключения обнаруживалась проблема.
7
Провели нагрузочную диагностику нового стенда
После первого тестового переключения страницы на новой инфраструктуре открывались медленнее. Увеличение размера и производительности дисков не изменило результат, поэтому проблему продолжили исследовать на уровне приложения.

Для одной из тяжелых страниц подготовили PHP-профайлер. Он измерял авторизацию, генерацию страницы, число SQL-запросов, длительность отдельных запросов и потребление памяти.
Профилирование показало две разные причины задержек.

Первая была связана с непрогретым кешем. На новом стенде приложение выполняло дополнительные запросы за метаданными, настройками модулей и пользовательскими полями. После заполнения Memcached число SQL-запросов сократилось с нескольких десятков до двух.

Вторая причина находилась в запросе подсчета данных по таблице примерно на 10 млн строк. Он занимал около 2,3−2,6 секунды на обеих инфраструктурах. Это подтвердило, что проблема не связана с новыми серверами и требует изменения логики приложения, например кеширования результата.

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

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

Результат

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

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

инфраструктура описана с помощью Terraform и Ansible
серверы могут создаваться и настраиваться из кода
веб-нагрузка распределяется через облачный балансировщик
веб-узлы отделены от cron-заданий и Kafka-консьюмеров
однотипные компоненты могут размещаться в разных зонах доступности
состояние, необходимое нескольким узлам, вынесено из локальной файловой системы
локальный кеш сохранен там, где сетевое хранилище ухудшало производительность
подготовлен управляемый процесс развертывания через GitLab CI
устранена причина неконтролируемого роста крупной таблицы с отладочными данными
выявлены медленные запросы и таблицы без индексов
производительность нового стенда проверена профилированием, а инфраструктурные причины отделены от ограничений приложения
подготовлена последовательность переключения с возможностью быстрого возврата
миграция на PostgreSQL отделена от инфраструктурного переезда из-за несовместимости используемых модулей

Вывод

Исходную задачу нельзя было решить созданием нескольких копий существующего сервера. Приложение сохраняло состояние локально, фоновые процессы имели разные требования к параллельному запуску, production содержал незадокументированные изменения, а часть задержек возникала внутри запросов к многомиллионным таблицам.
Проект перевел инфраструктуру от одной вручную настроенной машины к воспроизводимой многосерверной архитектуре. Одновременно аудит и тестовые переключения показали границы инфраструктурных изменений: высокая доступность требует не только резервных серверов, но и управления состоянием приложения, проверки внешних интеграций и доработки самой системы там, где ограничения заложены в ее коде и модели данных.

Смотрите другие
кейсы по Bitrix

    Error get alias

    Хотите передать
    свою инфраструктуру в надежные руки?

    Будем рады проконсультировать вас и обсудить проект.