Верил в мечту и в счастливые грёзы. День за день я жил - улетели те годы.

В мире корпоративных коммуникаций надёжность — это крайне важное преимущество, особенно в условиях, когда компании активно переходят на отечественное ПО. Если топ‑менеджер не может проверить почту перед важным совещанием или выездной инженер теряет доступ к критической документации из‑за отказа инфраструктуры, бизнес несёт прямые финансовые потери.

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

В этой статье я расскажу, как этот проект вписывается в программу импортозамещения, почему для нас критически важен был принцип High Availability (высокая доступность), какие Open Source технологии мы выбрали для обеспечения независимости и как нам удалось заставить все компоненты работать как единый, бесперебойный механизм.

По официальной документации архитектура представлена в таком варианте:

Пример кластера WorksPad из двух узлов
Пример кластера WorksPad из двух узлов

Но мы пойдем иным путём, более сложным и более надежным.

В нашей инсталляции задействовано 11 виртуальных машин (ВМ) на платформе виртуализации ISPsystem VMmanager (https://www.ispsystem.ru/vmmanager), разделенных на логические слои. Такая сегментация позволяет гибко управлять нагрузкой и обеспечивает высокую доступность сервиса.

1. Слой балансировки и доступа (Frontend)

Это "ворота" в нашу систему. Все запросы от мобильных клиентов приходят сюда.

ВМ: nginx (192.168.1.127)

Роль: Обратный прокси (Reverse Proxy).

Задача: Принимает HTTPS-трафик извне и распределяет его между тремя серверами WorksPad. Также может выполнять терминацию SSL.

2. Слой приложений (Application Layer)

Здесь работает логика WorksPad. Мы используем кластер из трех узлов для отказоустойчивости.

ВМ: node1 (192.168.1.122) — WorksPad Server (Gateway, Assistant)

ВМ: node2 (192.168.1.123) — WorksPad Server

ВМ: node3 (192.168.1.124) — WorksPad Server

Задача: Обработка запросов пользователей, взаимодействие с Exchange и Active Directory.

Особенность: Эти серверы не хранят данные локально. Для работы им нужна связь с БД через слой Middleware.

3. Слой Middleware (Отказоустойчивая маршрутизация БД)

Прослойка, которая делает работу с базой данных надежной.

ВМ: proxy1 (192.168.1.140) — PgBouncer + Keepalived (Master)

ВМ: proxy2 (192.168.1.141) — PgBouncer + Keepalived (Backup)

Задача:

VIP (192.168.1.200): Keepalived держит этот «плавающий» IP. Все ноды WorksPad стучатся именно на 192.168.1.200:6432.

Пулинг: PgBouncer принимает соединения и перенаправляет их на текущего лидера кластера Patroni.

Результат: При падении proxy1 адрес.200 мгновенно переезжает на proxy2. Сервис не останавливается.

4. Слой данных (Data Layer)

Кластер хранения данных под управлением Patroni.

ВМ: psql1 (192.168.1.130) — PostgreSQL + Patroni

ВМ: psql2 (192.168.1.131) — PostgreSQL + Patroni (Текущий Лидер в нашем примере)

ВМ: psql3 (192.168.1.132) — PostgreSQL + Patroni

Задача: Надежное хранение данных WorksPad.

Механика: Patroni следит за состоянием PostgreSQL. Если Лидер (.131) упадет, Patroni выберет нового лидера (например,.130), и PgBouncer начнет слать запросы туда.

5. Инфраструктурные сервисы (Infrastructure)

Внешние системы, необходимые для работы корпоративной среды.

ВМ: msad (192.168.1.128) — Microsoft Active Directory 2016

Задача: Аутентификация пользователей WorksPad.

ВМ: exchange (192.168.1.205) — Microsoft Exchange 2016

Задача: Источник корпоративной почты, календарей и контактов, с которыми работает WorksPad.

Как проходит запрос пользователя (Flow):

  1. Пользователь открывает приложение WorksPad.

  2. Запрос летит на Nginx (.127).

  3. Nginx пересылает его на одну из нод, например, node2 (.123).

  4. node2 понимает, что нужно проверить данные в базе. Он делает запрос на VIP Middleware (.200).

  5. Текущий активный прокси (proxy1, .140) принимает запрос через PgBouncer.

  6. PgBouncer пересылает запрос на реального лидера БД (psql2, .131).

  7. psql2 отдает данные, и цепочка возвращается обратно пользователю.

Эта схема гарантирует, что падение любого одного сервера (nginx, node, proxy, psql) не приведет к остановке сервиса.

И так, а теперь перейдем к этапам установки и настройки:

Предполагаем, что ОС (мы использовали Astra Linux) уже установлена.

Этап 1. Создаем "Сердце" системы — Кластер PostgreSQL + Patroni

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

Зачем нужен Patroni?

Представьте: у вас есть база данных PostgreSQL с репликацией Master → Slave. Всё работает отлично, пока Мастер не упадет. Что дальше?

Без Patroni: Вы (администратор) должны в 3 часа ночи зайти на Slave-сервер и вручную выполнить команды, чтобы "повысить" его до Мастера. Это часы простоя.

С Patroni: Система сама обнаруживает смерть Мастера за 5 секунд, проводит "выборы" среди живых реплик и автоматически назначает нового лидера. Человек может спокойно спать.

В нашем проекте мы используем 3 сервера PostgreSQL под управлением Patroni:

psql1 (192.168.1.130)

psql2 (192.168.1.131) — будет Мастером изначально

psql3 (192.168.1.132)

Шаг 1.1. Устанавливаем PostgreSQL

На всех трех серверах (psql1, psql2, psql3) выполняем:

sudo apt update
sudo apt install postgresql postgresql-contrib

Проверяем, что PostgreSQL запустился:

systemctl status postgresql

Важно: Мы не будем настраивать репликацию вручную. Patroni сделает это сам.

Шаг 1.2. Устанавливаем Patroni и etcd

Patroni нужен "координатор" — сервис, который хранит информацию о том, кто сейчас Мастер. Мы используем etcd (легковесная распределенная база данных для конфигов).

На всех трех серверах БД:

sudo apt install patroni etcd python3-etcd

Шаг 1.3. Настраиваем etcd (Coordinator)

Для простоты мы развернем etcd на тех же серверах, где PostgreSQL.

Редактируем /etc/default/etcd на каждом узле.

На psql1:

ETCD_NAME="psql1"
ETCD_INITIAL_CLUSTER="psql1=
http://192.168.1.130:2380,psql2=http://192.168.1.131:2380,psql3=http://192.168.1.132:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="patroni-cluster"
ETCD_INITIAL_ADVERTISE_PEER_URLS="
http://192.168.1.130:2380"

ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.130:2379"
ETCD_LISTEN_PEER_URLS="http://192.168.1.130:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.130:2379,http://127.0.0.1:2379"

На psql2 и psql3 меняем только IP и имя узла.

Запускаем etcd на всех узлах:

systemctl start etcd
systemctl enable etcd

Проверяем кластер:

etcdctl member list

Должны увидеть три узла.

etcdctl member list
etcdctl member list

Шаг 1.4. Настраиваем Patroni

Теперь учим Patroni управлять PostgreSQL.

Создаем конфиг /etc/patroni/patroni.yml на каждом сервере.

Пример для psql2 (будущий Мастер):

scope: workspad-cluster
namespace: /db/
name: psql2

restapi:
listen: 192.168.1.131:8008
connect_address: 192.168.1.131:8008

etcd:
hosts: 192.168.1.130:2379,192.168.1.131:2379,192.168.1.132:2379

bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
max_connections: 500
shared_buffers: 256MB

initdb:
- encoding: UTF8
- data-checksums

pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 192.168.1.0/24 md5

postgresql:
listen: 192.168.1.131:5432
connect_address: 192.168.1.131:5432
data_dir: /var/lib/postgresql/15/main
bin_dir: /usr/lib/postgresql/15/bin
authentication:
replication:
username: replicator
password: RepliPass123
superuser:
username: postgres
password: StrongPass

На psql1 и psql3 конфиг аналогичный, но меняем:

  • name: psql1 / psql3

  • Все упоминания IP на соответствующий (.130 и .132).

Шаг 1.5. Запускаем Patroni

На всех трех серверах:

systemctl start patroni
systemctl enable patroni

Проверяем статус кластера:

patronictl -c /etc/patroni/patroni.yml list

patronictl -c /etc/patroni/patroni.yml list
patronictl -c /etc/patroni/patroni.yml list

Шаг 1.6. Создаем базу данных WorksPad

Заходим на текущего лидера (в нашем случае psql2):

sudo -u postgres psql

Создаем базу и пользователя:

CREATE DATABASE workspad; CREATE USER workspad WITH PASSWORD 'jhYg/=\F'; GRANT ALL PRIVILEGES ON DATABASE workspad TO workspad; \q

Patroni будет автоматически реплицировать эту базу на остальные узлы.

Часть 2. Установка и настройка WorksPad

Теперь переходим к установке самого приложения WorksPad. Этот процесс нужно повторить на всех трех узлах: node1, node2 и node3.

Этап 2.1. Подготовка зависимостей

WorksPad работает на платформе .NET Core. Прежде чем ставить сервер, нужно подготовить почву в ОС (мы используем Astra Linux или Debian).

Устанавливаем ASP.NET Core Runtime 6.0
Это "движок", на котором работает приложение.

# Скачиваем ключи Microsoft (для доступа к репозиториям) wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb sudo apt update # Устанавливаем сам рантайм sudo apt install -y aspnetcore-runtime-6.0

Устанавливаем системные библиотеки и GSS-NTLMSSP WorksPad активно работает с графикой (для предпросмотра документов) и аутентификацией Windows.

sudo apt install -y libgdiplus libc6-dev libicu-dev gss-ntlmssp

Этап 2.2 Установка пакета WorksPad Server

В отличие от многих Linux-сервисов, WorksPad имеет удобный инсталлятор, который сам создает службы и папки.

Запускаем установку Копируем .deb пакет на сервер и запускаем:

sudo dpkg -i workspad_amd64.deb

Мастер настройки (прямо в консоли) Инсталлятор задаст вам 4 вопроса. Важно отвечать правильно, с учетом нашей архитектуры!

Вопрос 1: Database Server host (Адрес сервера БД)

Здесь мы вводим наш VIP-адрес PgBouncer: 192.168.1.200:6432

Почему: Мы не указываем адреса реальных PostgreSQL серверов. WorksPad должен ходить только через прокси (в нашем проекте).

Вопрос 2: Database name (Имя базы)

Пишем: workspad

Вопрос 3: Database User (Пользователь)

Пишем: workspad

Вопрос 4: Password (Пароль)

Вводим пароль пользователя workspad (который мы прописываем в userlist.txt на pgbouncer).

После этого инсталлятор распакует файлы в /opt/workspad и автоматически создаст systemd-службы (Gateway, Assistant, AdminAPI и др.).

Этап 3. Настройка кластеризации WorksPad

Инсталлятор настроил всё для одиночного сервера. Но у нас кластер из трех нод.

Этап 3.1. Синхронизация сертификатов шифрования

При первой установке на node1 WorksPad сгенерировал уникальный сертификат защиты данных . Если его не скопировать на node2 и node3, то пользователь, переключившийся между нодами, "вылетит" из сессии.

Зайдите на node1.

И на этом этапе я продемонстрирую как это описано в фоициальной документации

Официальная документация - создание кластера
Официальная документация - создание кластера

Этап 4. Готовим "Регулировщика" (PgBouncer + Keepalived)

Мы работаем на серверах proxy1 и proxy2.

Этап 4.1. Устанавливаем софт:

sudo apt update
sudo apt install pgbouncer keepalived

Этап 4.2. Настраиваем Keepalived (Виртуальный IP):

Нам нужно, чтобы адрес 192.168.1.200 жил на этих серверах. Открываем конфиг

nano /etc/keepalived/keepalived.conf.

На proxy1 (Master):

vrrp_instance VIP_GPROXY {
state MASTER
interface eth0
virtual_router_id 70
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass SecretPass
}
virtual_ipaddress {
192.168.1.200/24
}
}

На proxy2 (Backup): конфиг тот же, но state BACKUP и priority 90.

Запускаем: systemctl start keepalived. Проверяем ip a — адрес 192.168.1.200 должен появиться на мастере.

Этап 4.3. Настраиваем PgBouncer

Редактируем /etc/pgbouncer/pgbouncer.ini.

[databases]

workspad = host=192.168.1.131 port=5432 dbname=workspad

[pgbouncer]
listen_addr = *
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

Самый хитрый момент: Пароли PgBouncer не хранит пароли в открытом виде. Ему нужен MD5-хэш. Генерируем его в консоли (формат: пароль + логин):

echo -n "МойПарольworkspad" | md5sum

Результат вставляем в /etc/pgbouncer/userlist.txt:
"workspad" "md5<ваш_полученный_хэш>"

Перезапускаем: systemctl restart pgbouncer.

Этап 5. Настраиваем WorksPad

Теперь идем на сервера приложений: node1, node2, node3.
WorksPad уже установлен в /opt/workspad. Нам нужно научить его ходить через наш новый отказоустойчивый шлюз.

Открываем файл настроек:
/opt/workspad/GatewayService/appsettings.json

Нас интересует секция ConnectionStrings.

Было (напрямую к базе):
"Host=psql2;Database=workspad;..."

Стало (через наш кластер):

"ConnectionStrings": {
"WorksPadDatabase": "Host=192.168.1.200:6432;Database=workspad;User Id=workspad;Password='jhYg/=\\F';"
}

Перезапускаем сервис:
systemctl restart workspad-gateway

Часть 5. Входные ворота: Настройка Nginx Load Balancer

Это последний недостающий пазл вашей статьи. Nginx здесь выполняет роль входных ворот (Load Balancer). Именно он принимает пользователей из интернета и распределяет их между тремя нодами WorksPad (node1, node2, node3).

У нас есть три мощных сервера WorksPad, но пользователи о них не знают. Они стучатся по одному адресу (например, workspad.company.com). Нам нужен "швейцар", который встретит гостя и проводит к свободному серверу. Эту роль выполняет Nginx.

Мы работаем на сервере nginx1 (IP: 192.168.1.127).

5.1. Установка

Всё стандартно:

sudo apt update
sudo apt install nginx

5.2. Настройка балансировки (Upstream)

Nginx должен знать обо всех наших трех узлах. Открываем конфиг (обычно создаем новый /etc/nginx/conf.d/workspad.conf или редактируем default):

# Группа серверов WorksPad (Backends) upstream workspad_cluster { # Метод балансировки: ip_hash важен! # Он "привязывает" пользователя к одному серверу на время сессии. # Без этого пользователя будет выкидывать при каждом запросе. ip_hash; server 192.168.1.122:4430; # Node1 server 192.168.1.123:4430; # Node2 server 192.168.1.124:4430; # Node3 }

5.3. Настройка проксирования (Server Block)

Теперь настраиваем сам сервер, который слушает порт 443 (HTTPS) и пересылает запросы в workspad_cluster.

server { listen 443 ssl; server_name workspad.company.com; # Ваше доменное имя # SSL сертификаты (те же, что и на самих нодах WorksPad) ssl_certificate /etc/nginx/ssl/workspad.crt; ssl_certificate_key /etc/nginx/ssl/workspad.key; # Основные настройки SSL ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Локация для перенаправления location / { proxy_pass https://workspad\_cluster; # Шлем трафик в наш кластер # Важные заголовки, чтобы WorksPad понимал, кто реальный клиент proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Поддержка WebSocket (критично для WorksPad!) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Увеличиваем таймауты для тяжелых файлов proxy_read_timeout 300s; } }

5.4. Проверка и запуск

Проверяем, что мы не наделали ошибок в конфиге:

sudo nginx -t

Если видите syntax is ok — вы молодец.

Перезагружаем Nginx:

sudo systemctl reload nginx

Если node1 упадет, Nginx (благодаря пассивным проверкам) поймет это и перестанет слать туда трафик. Пользователи автоматически пойдут на node2 и node3. Если нагрузка вырастет, мы просто поднимем node4, добавим одну строку в upstream и перезагрузим Nginx.

Итог: Что мы построили?

Потратив время на настройку этой архитектуры, мы ушли от "ремесленного" подхода ("лишь бы работало") к промышленному стандарту надежности.

Окно входа
Окно входа
Работа отказоустойчивости решения
Работа отказоустойчивости решения

Мы собрали систему из 11 серверов, которая работает как единое целое:

  1. Nginx встречает пользователя и отправляет его к свободному серверу.

  2. Кластер WorksPad (3 ноды) обрабатывает запросы. Если даже один сервер остался, работа продолжится.

  3. PgBouncer + Keepalived работа с СУБД продолжится, даже если будет происходить аварийное переключение.

  4. Patroni + PostgreSQL гарантируют, что данные (самое ценное, что у нас есть) не пропадут и будут доступны 24/7.

Комментарии (1)


  1. d3d11
    24.11.2025 17:53

    Как вы решили проблему доступности в условии белых списков?

    Самый актуальный сегодня вопрос.