Содержание
- Схема
- Условия эксплуатации
- Технические требования
- Установка двухузловой архитектуры master-slave на РедОС
- 1. Установка и настройка сервиса хранения GlusterFS для приложений
- 2. Записи в DNS
- 3. Установка и настройка БД
- 4. Настройка внутреннего Keepalived на backend-узлах
- 4.1. Подготовка служебных каталогов
- 4.2. Настройка беспарольного SSH между backend-узлами
- 4.3. Скрипт работы переключения режимов
- 4.4. Оформление watchdog как systemd-сервиса
- 4.5. Установка и настройка Keepalived на backend-узлах
- 4.6. Ручное восстановление cs24-1 как standby от cs24-2
- 4.7. Ручное переключение master-роли с cs24-2 на cs24-1
- 5. Установка Корпоративного сервера
- 6. Выполните доп настройку Nginx, Сервер документов и БД
- 7. Reverse proxy nginx ноды
- 8. Настройка cs24-1 и cs24-2
Схема

В данной статье рассматривается схема развертывания отказоустойчивого комплекса Корпоративный сервер 2024 версии 2025.4.6.16449 и выше на РЕД ОС 8.
В состав решения входят:
- 2 внешних reverse proxy сервера с Nginx и Keepalived;
- 2 внутренних сервера приложений Корпоративный сервер 2024 с установленными модулями портала и Сервером документов;
- GlusterFS для синхронизации общих данных между двумя узлами приложения;
- PostgreSQL в режиме master/standby;
- Автоматически выполняется только failover standby → master при недоступности текущего master.
- Обратный failback на cs24-1 автоматически не выполняется.
- Возврат cs24-1 в standby и последующий switchover cs24-1 → master выполняются вручную отдельными скриптами администратора.
- внутренний виртуальный IP-адрес для backend-части;
- внешний виртуальный IP-адрес для пользовательского доступа.
Используемая адресация в качестве примера:
- nginx1 — 192.168.26.64, внешний IP 45.12.228.66;
- nginx2 — 192.168.26.139, внешний IP 45.12.228.67;
- внешний виртуальный IP — 45.12.228.68;
- cs24-1 — 192.168.26.203;
- cs24-2 — 192.168.26.126;
- внутренний виртуальный IP — 192.168.26.8
Условия эксплуатации
- Для доступа к серверу будет использоваться адрес cddisk.test1.s7-office.site. и потребуется создать публичную А запись для доступа к сервису.
- При недоступности текущей master-ноды standby может быть автоматически promoted в master.
Возврат прежней master-ноды выполняется только вручную. - Проверка доступности сервиса для изменения мастера выполняется по внутреннему VIP
Файловое хранилище:
- Корпоративный Сервер
/var/r7-office/filestorage /var/r7-office/searchindex /opt/r7-office/java-maker/uploaded-files - Сервер документов
/var/www/r7-office/Data /var/lib/r7-office/documentserver/App_Data/cache
Нет необходимости синхронизировать временных каталогов:
- filestorage_temp
- filestorage_temp_proc
База данных (БД): Используется PostgreSQL версии 16 в режиме master/replica.
Технические требования
- 4 Виртуальные машины;
- Для хранения данных GlusterFS;
- ТХ Машин для тестирования приложений возможно использовать:
- От 4 CPU;
- От 8Гб RAM;
- От 50Гб свободного пространства на диске;
- ТХ Машин для тестирования reverse proxy возможно использовать:
- От 2 CPU;
- От 4 Гб RAM;
- От 50Гб свободного пространства на диске;
- Более конкретные данные рассчитываются по обращению в ТП;
- Отключение или перевод SELinux в режим permissive для корректной работы сервисов.
Установка двухузловой архитектуры master-slave на РедОС
В состав дистрибутива Корпоративный сервер 2024 не включён модуль Р7 Графика. Для установки модуля Р7 Графика необходимо воспользоваться инструкциями, опубликованными в разделе: Р7 Графика > Установка ↗.
1. Установка и настройка сервиса хранения GlusterFS для приложений
Проводиться на cs24-1 и cs24-2
1.1. Установка и настройка glusterfs
Добавьте запись в /etc/hosts:
192.168.26.203 gluster1 192.168.26.126 gluster2
Укажите соответствующее имя (поправить на свой домен и IP-адрес сервера), вместо mx1 можно использовать любое необходимое имя:
Укажите соответствующее имя на сервере №1
hostnamectl set-hostname cs24-1
Укажите соответствующее имя на сервере №2:
hostnamectl set-hostname cs24-2
Для корректной установки корпоративного сервера требуется наличие перевода строки в конце файла /etc/hosts.
Для применения параметров выполните перезапуск серверов.
1.2. На всех нодах установить, запустить и добавить в автозагрузку
dnf install -y glusterfs-server systemctl start glusterd.service systemctl enable glusterd.service
Также рекомендуется, чтобы gluster корректно работал и соединялся друг с другом и не падал, то нужно посмотреть порты:
cat /etc/glusterfs/glusterd.vol
Пример вывода:

Нам нужно добавить исключения на firewall, чтобы соединения проходили:
sudo firewall-cmd --permanent --add-port=49152-49251/tcp sudo firewall-cmd --permanent --add-port=24007-24008/tcp sudo firewall-cmd --reload
Теперь все должно корректно проходить и gluster будет устанавливать соединения.
Не имеет значения, какой из узлов вы будете использовать, но в следующем примере команда запускается на gluster1:
gluster peer probe gluster2
Фактически эта команда сообщает gluster1 доверять gluster2 и регистрирует его как часть пула хранения данных.
Если зондирование пройдет успешно, вы получите следующий вывод
peer probe: success
Вы можете проверить связь узлов в любое время путем запуска команды:
gluster peer status
Если вы запустите эту команду из gluster2, вы увидите следующий вывод:
Number of Peers: 1 Hostname: gluster1 Uuid: 7ecfa2d1-3394-4f15-a1f5-bba484f2bbef State: Peer in Cluster (Connected)
Рекомендуется на время установки/настройки отключить firewalld
На этом этапе два ваших сервера взаимодействуют и готовы к созданию томов хранения друг с другом.
Создание томов
- Выполняется на первом сервере
Для создания тома вы будете использовать команду gluster volume create с таким общим синтаксисом:
sudo gluster volume create cddisk-filestorage replica 2 gluster{1,2}:/cddisk-filestorage force
sudo gluster volume create cddisk-searchindex replica 2 gluster{1,2}:/cddisk-searchindex force
sudo gluster volume create ds-data replica 2 gluster{1,2}:/ds-data force
sudo gluster volume create ds-cache replica 2 gluster{1,2}:/ds-cache force
sudo gluster volume create cddisk-javamaker replica 2 gluster{1,2}:/cddisk-javamaker force
sudo gluster volume create ds-plugins replica 2 gluster{1,2}:/ds-plugins forceЕсли том был создан успешно, вы увидите следующий вывод:
volume create: cddisk-filestorage: success: please start the volume to access data
На этом этапе ваш том создан, но еще не активирован. Вы можете запустить том и сделать его доступным для использования путем выполнения следующей команды с любого сервера Gluster:
sudo gluster volume start cddisk-filestorage sudo gluster volume start cddisk-searchindex sudo gluster volume start ds-data sudo gluster volume start ds-cache sudo gluster volume start cddisk-javamaker sudo gluster volume start ds-plugins
Вы получите следующий вывод, если том запущен корректно:
volume start: cddisk-filestorage: success volume start: cddisk-searchindex: success volume start: ds-data: success volume start: ds-cache: success volume start: cddisk-javamaker:success volume start: ds-plugins:success
Затем проверьте, находится ли том в сети. Запустите следующую команду с любого из ваших узлов:
sudo gluster volume status
В результате вы увидите вывод, аналогичный данному:
Status of volume: cddisk-filestorage Gluster process TCP Port RDMA Port Online Pid ------------------------------------------------------------------------------ Brick gluster1:/cddisk-filestorage 49152 0 Y 4495 Brick gluster2:/cddisk-filestorage 49152 0 Y 20192 Self-heal Daemon on localhost N/A N/A Y 4518 Self-heal Daemon on gluster2 N/A N/A Y 20215
Создайте каталог и смонтируйте
- На всех серверах
mkdir -p /var/r7-office/filestorage mkdir -p /var/r7-office/searchindex mkdir -p /var/www/r7-office/Data mkdir -p /var/lib/r7-office/documentserver/App_Data/cache mkdir -p /opt/r7-office/java-maker/uploaded-files mkdir -p /var/www/r7-office/documentserver/sdkjs-plugins/ mount.glusterfs localhost:/cddisk-filestorage /var/r7-office/filestorage mount.glusterfs localhost:/cddisk-searchindex /var/r7-office/searchindex mount.glusterfs localhost:/ds-data /var/www/r7-office/Data mount.glusterfs localhost:/ds-cache /var/lib/r7-office/documentserver/App_Data/cache mount.glusterfs localhost:/cddisk-javamaker /opt/r7-office/java-maker/uploaded-files mount.glusterfs localhost:/ds-plugins /var/www/r7-office/documentserver/sdkjs-plugins
Добавьте в автозагрузку:
{
echo 'localhost:/cddisk-filestorage /var/r7-office/filestorage glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/cddisk-searchindex /var/r7-office/searchindex glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/ds-data /var/www/r7-office/Data glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/ds-cache /var/lib/r7-office/documentserver/App_Data/cache glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/cddisk-javamaker /opt/r7-office/java-maker/uploaded-files glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
echo 'localhost:/ds-plugins /var/www/r7-office/documentserver/sdkjs-plugins glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'
} | sudo tee -a /etc/fstabДобавьте задание для перезапуска glusterfs, в случае перезагрузки или выключения питания
- На всех серверах
После перезагрузки сервера, могут наблюдаться проблемы с синхронизацией glusterfs. Поэтому данный метод решает эту проблему.
echo "@reboot sleep 30 && systemctl restart glusterd.service" | crontab -
Проверка монтирования:
df -h mount -a
Проверьте корректность синхронизации каталогов между серверами, создавая тестовые файлы или каталоги в монтированных каталогах.
При настройке разрешенных портов рекомендуется проверить все задействованные порты на двух серверах:
ss -tulnp | grep gluster
2. Записи в DNS
Для доступа в публичной сети создайте на внешний адрес VIP А запись, например

или на каждый поддомен модулей Корпоративного сервера
3. Установка и настройка БД
Установите следующие пакеты на оба сервера:
dnf install postgresql-server-16.*
3.1. БД
- На всех серверах
Создайте первичный кластер базы данных:
sudo postgresql-setup --initdb sudo systemctl enable --now postgresql
Отредактируйте /var/lib/pgsql/data/postgresql.conf
listen_addresses = '*' # Слушать на адресах port = 5432 # Задать порт БД wal_level = replica # Включить репликацию archive_mode = on # Включить архивирование WAL archive_command = 'cp %p /var/lib/pgsql/archive/%f' # Команда для архивирования WAL и при необходимости можно настроить архивацию в нужный каталог и монтировать на отдельный диск для резервирования max_wal_senders = 10 # Максимальное количество одновременных подключений репликации max_replication_slots = 10 hot_standby = on # Разрешить подключения в режиме hot standby wal_keep_size = 4096MB
где,
- ip_srv1,2 — адреса внутренней сети первого и второго почтового сервера.
- На всех серверах
В файле postgresql.conf разрешите логическую репликацию, добавляем строку для прослушивания локального интерфейса:
/var/lib/pgsql/data/pg_hba.conf
Привести к виду:
# Разрешить локальные подключения для всех пользователей # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust host all all ip_srv1/32 trust host all all ip_srv2/32 trust host all all vip_internal/32 trust
Вместо ip_srv1/32 и ip_srv2/32 укажите ip адрес двух серверов. Режим trust необходим для установки Корпоративного сервера
И добавьте строки в конце (для возможности репликации БД):
# Разрешить репликацию с обоих серверов для пользователя replication_user host replication replication_user ip_srv1/32 md5 host replication replication_user ip_srv2/32 md5
где,
- ip_another_srv/32 — адрес первого или второго почтового сервера, в зависимости от сервера где производиться настройка.
Выполните перезапуск сервиса БД:
sudo systemctl restart postgresql
- На сервере №1
После базовой настройки базы данных, создаем необходимую структуру в БД, создаем пользователей и настраиваем репликацию:
sudo -u postgres psql
В консоли psql> выполните следующие команды (вместо паролей password и replication_password, cddisk и ds задайте свои пароли), подтверждая каждую нажатием Enter:
Не рекомендуется использовать другие имена для баз данных cddisk и ds
CREATE USER cddisk WITH password 'cddisk'; CREATE USER ds WITH password 'ds'; CREATE DATABASE cddisk OWNER cddisk; CREATE DATABASE ds OWNER ds; CREATE DATABASE pagesdb OWNER cddisk; GRANT ALL privileges ON DATABASE cddisk TO cddisk; GRANT ALL privileges ON DATABASE ds TO ds; GRANT ALL privileges ON DATABASE pagesdb TO cddisk; ALTER DATABASE cddisk OWNER TO cddisk; ALTER DATABASE ds OWNER TO ds; ALTER DATABASE pagesdb OWNER TO cddisk; CREATE USER replication_user WITH REPLICATION LOGIN PASSWORD 'replication_password'; GRANT CONNECT ON DATABASE cddisk TO replication_user; GRANT CONNECT ON DATABASE ds TO replication_user; \c cddisk GRANT USAGE ON SCHEMA public TO replication_user; GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user; GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO replication_user; \c ds GRANT USAGE ON SCHEMA public TO replication_user; GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user; GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO replication_user; Для выхода из БД \q
- На сервере №1
Создайте директорию для архива:
sudo mkdir /var/lib/pgsql/archive sudo chown -R postgres: /var/lib/pgsql/archive sudo chmod -R 750 /var/lib/pgsql/archive
- На сервере №1
Перезагрузите службу PostgreSQL для применения изменений:
systemctl restart postgresql
4. Настройка внутреннего Keepalived на backend-узлах
4.1. Подготовка служебных каталогов
Выполните на cs24-1 и cs24-2.
mkdir -p /etc/r7-ha mkdir -p /run/r7-ha touch /var/log/r7-ha.log chown root:adm /var/log/r7-ha.log chmod 640 /var/log/r7-ha.log
Создайте файл для доступа от пользователя replication_user
cat > /var/lib/pgsql/.pgpass <<'EOF' 192.168.26.203:5432:*:replication_user:replication_password 192.168.26.126:5432:*:replication_user:replication_password EOF chown postgres:postgres /var/lib/pgsql/.pgpass chmod 600 /var/lib/pgsql/.pgpass
Создайте файл /etc/r7-ha/r7-ha.env:
cat > /etc/r7-ha/r7-ha.env <<'EOF_ENV' CS24_1_IP=192.168.26.203 CS24_2_IP=192.168.26.126 INT_VIP=192.168.26.8 PGDATA=/var/lib/pgsql/data REPLICATION_USER=replication_user SSH_USER=root SSH_PORT=22 EOF_ENV chmod 600 /etc/r7-ha/r7-ha.env
4.2. Настройка беспарольного SSH между backend-узлами
На каждой ноде cs создайте свой SSH-ключ. Закрытый ключ не копируется между серверами. На соседнюю ноду переносится только открытый ключ.
ssh-keygen -t rsa -b 4096 ssh-copy-id root@192.168.26.203 ssh-copy-id root@192.168.26.126
Для проверки с обеих нод:
ssh root@192.168.26.203 hostname ssh root@192.168.26.126 hostname
4.3. Скрипт работы переключения режимов
Создайте файл на cs24-1 и cs24-2 /usr/local/sbin/r7-ha.sh:
cat > /usr/local/sbin/r7-ha.sh <<'EOF_SCRIPT'
#!/bin/bash
set -euo pipefail
# Единый HA-скрипт для cs24-1 / cs24-2.
# Действия: check-local, watchdog, rebuild-standby, switchover-to-cs24-1, status.
ENV_FILE="/etc/r7-ha/r7-ha.env"
LOG_FILE="/var/log/r7-ha.log"
RUN_DIR="/run/r7-ha"
BACKUP_DIR="/var/backups/r7-ha"
PGPASSFILE_PATH="/var/lib/pgsql/.pgpass"
PGDATABASE="${PGDATABASE:-cddisk}"
WATCH_INTERVAL="${WATCH_INTERVAL:-5}"
PROMOTE_TIMEOUT="${PROMOTE_TIMEOUT:-60}"
CATCHUP_TIMEOUT="${CATCHUP_TIMEOUT:-120}"
[ -r "$ENV_FILE" ] && . "$ENV_FILE" || { echo "No $ENV_FILE" >&2; exit 2; }
: "${CS24_1_IP:?}" "${CS24_2_IP:?}" "${INT_VIP:?}" "${PGDATA:?}" "${REPLICATION_USER:?}" "${SSH_USER:?}" "${SSH_PORT:?}"
mkdir -p "$RUN_DIR" "$BACKUP_DIR" 2>/dev/null || true
log() {
local msg="[$(date '+%F %T')] $*"
echo "$msg"
echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
log_file_only() {
local msg="[$(date '+%F %T')] $*"
echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
# Пишет в лог только при смене состояния.
log_state_once() {
local name="$1"
local state="$2"
local message="$3"
local state_file="$RUN_DIR/${name}.state"
local old_state=""
mkdir -p "$RUN_DIR" 2>/dev/null || true
[ -r "$state_file" ] && old_state="$(cat "$state_file" 2>/dev/null || true)"
if [ "$old_state" != "$state" ]; then
echo "$state" > "$state_file" 2>/dev/null || true
log_file_only "$message"
fi
}
die() {
log "ОШИБКА: $*"
exit 1
}
as_postgres() {
runuser -u postgres -- "$@"
}
local_scalar() {
local sql="$1"
as_postgres psql -d "$PGDATABASE" -Atqc "$sql"
}
remote_scalar() {
local host="$1"
local sql="$2"
as_postgres env PGPASSFILE="$PGPASSFILE_PATH" \
psql -h "$host" -U "$REPLICATION_USER" -d "$PGDATABASE" -Atqc "$sql"
}
is_this_ip() {
local ip="$1"
ip -o -4 addr show | awk '{print $4}' | cut -d/ -f1 | grep -Fxq "$ip"
}
require_cs24_1() {
is_this_ip "$CS24_1_IP" || die "действие нужно запускать на cs24-1 ($CS24_1_IP)"
}
require_cs24_2() {
is_this_ip "$CS24_2_IP" || die "действие нужно запускать на cs24-2 ($CS24_2_IP)"
}
local_role() {
local_scalar "select pg_is_in_recovery();" 2>/dev/null || true
}
local_is_master() {
[ "$(local_role)" = "f" ]
}
local_is_standby() {
[ "$(local_role)" = "t" ]
}
remote_role() {
remote_scalar "$1" "select pg_is_in_recovery();" 2>/dev/null || true
}
remote_is_master() {
[ "$(remote_role "$1")" = "f" ]
}
vip_pg_ready() {
pg_isready -h "$INT_VIP" -p 5432 >/dev/null 2>&1
}
remote_pg_ready() {
pg_isready -h "$1" -p 5432 >/dev/null 2>&1
}
ssh_cmd() {
local host="$1"
shift
ssh -p "$SSH_PORT" \
-o BatchMode=yes \
-o ConnectTimeout=5 \
"${SSH_USER}@${host}" "$@"
}
# Поиск systemd-сервисов Document Server.
stack_units() {
systemctl list-unit-files --type=service --no-legend 2>/dev/null | \
awk '$1 ~ /^ds.*\.service$/ {print $1}'
}
# Управление локальным стеком приложения.
stack_cmd() {
local action="$1"
log "Локальный стек: выполняю '$action'"
if command -v supervisorctl >/dev/null 2>&1; then
supervisorctl "$action" all || true
else
log "supervisorctl не найден, пропускаю"
fi
local units
units="$(stack_units | tr '\n' ' ')"
if [ -n "$units" ]; then
# shellcheck disable=SC2086
systemctl "$action" $units || true
else
log "ds*.service не найдены, пропускаю"
fi
log "Локальный стек: '$action' завершён"
}
# Управление стеком на соседней ноде.
remote_stack_cmd() {
local host="$1"
local action="$2"
log "Удалённый стек $host: выполняю '$action'"
ssh -p "$SSH_PORT" \
-o BatchMode=yes \
-o ConnectTimeout=5 \
"${SSH_USER}@${host}" bash -s -- "$action" <<'EOS'
set -euo pipefail
action="$1"
if command -v supervisorctl >/dev/null 2>&1; then
supervisorctl "$action" all || true
fi
units="$(systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '$1 ~ /^ds.*\.service$/ {print $1}' | tr '\n' ' ')"
if [ -n "$units" ]; then
# shellcheck disable=SC2086
systemctl "$action" $units || true
fi
EOS
log "Удалённый стек $host: '$action' завершён"
}
# Promote локального PostgreSQL.
promote_local() {
log "PostgreSQL: выполняю promote локальной ноды"
as_postgres pg_ctl -D "$PGDATA" promote -w -t "$PROMOTE_TIMEOUT"
local_is_master || die "PostgreSQL не стал master после promote"
log "PostgreSQL: локальная нода стала master"
}
# Проверка для keepalived.
check_local() {
local pgport="${PGPORT:-5432}"
local pgdb="${PGDATABASE:-cddisk}"
if ! pg_isready -h 127.0.0.1 -p "$pgport" >/dev/null 2>&1; then
log_state_once "check-local" "pg_not_ready" \
"check-local: локальный PostgreSQL не отвечает, VIP запрещён"
exit 1
fi
local local_role
local_role="$(runuser -u postgres -- psql -XAtq \
-h 127.0.0.1 \
-p "$pgport" \
-d "$pgdb" \
-c "select pg_is_in_recovery();" 2>/dev/null || true)"
if [ "$local_role" != "f" ]; then
log_state_once "check-local" "local_not_master_${local_role:-unknown}" \
"check-local: локальная нода не master, pg_is_in_recovery=${local_role:-unknown}, VIP запрещён"
exit 1
fi
local local_ip
local_ip="$(ip -4 -o addr show | awk '{print $4}' | cut -d/ -f1 | grep -E "^(${CS24_1_IP}|${CS24_2_IP})$" | head -n1)"
if [ -z "$local_ip" ]; then
log_state_once "check-local" "local_ip_unknown" \
"check-local: не удалось определить локальную backend-ноду, VIP запрещён"
exit 1
fi
# Защита от автоматического возврата VIP на cs24-1.
if [ "$local_ip" = "$CS24_1_IP" ]; then
local peer_role
peer_role="$(runuser -u postgres -- env \
PGCONNECT_TIMEOUT=2 \
PGPASSFILE="$PGPASSFILE_PATH" \
psql -XAtq \
-h "$CS24_2_IP" \
-p "$pgport" \
-U "$REPLICATION_USER" \
-d "$pgdb" \
-c "select pg_is_in_recovery();" 2>/dev/null || true)"
if [ "$peer_role" = "f" ]; then
log_state_once "check-local" "cs24_1_blocked_peer_master" \
"check-local: cs24-2 уже master, VIP на cs24-1 запрещён"
exit 1
fi
fi
log_state_once "check-local" "ok_${local_ip}" \
"check-local: проверка успешна на $local_ip, VIP разрешён"
exit 0
}
# Автоматический failover на cs24-2.
watchdog() {
require_cs24_2
log "watchdog: запущен на cs24-2"
while true; do
if local_is_master; then
log_state_once "watchdog" "cs24_2_master" \
"watchdog: cs24-2 уже master, действий не требуется"
sleep "$WATCH_INTERVAL"
continue
fi
if local_is_standby; then
if ! vip_pg_ready && ! remote_pg_ready "$CS24_1_IP"; then
log "watchdog: cs24-1 и VIP недоступны, начинаю failover"
promote_local
log "watchdog: перезапускаю прикладной стек"
stack_cmd restart
log "watchdog: перезапускаю keepalived для получения VIP"
systemctl restart keepalived || true
log "watchdog: failover завершён, cs24-2 master"
else
log_state_once "watchdog" "cs24_2_standby_waiting" \
"watchdog: cs24-2 standby, cs24-1 или VIP доступны, promote не требуется"
fi
else
log_state_once "watchdog" "local_role_unknown" \
"watchdog: роль локального PostgreSQL не определена"
fi
sleep "$WATCH_INTERVAL"
done
}
# Сохранение старого PGDATA и подготовка нового.
backup_and_clear_pgdata_local() {
mkdir -p "$BACKUP_DIR"
local ts backup_path
ts="$(date '+%Y%m%d-%H%M%S')"
backup_path="$BACKUP_DIR/pgsql-data-$ts"
log "PostgreSQL: останавливаю локальный сервис"
systemctl stop postgresql || true
if [ -d "$PGDATA" ] && [ -n "$(ls -A "$PGDATA" 2>/dev/null || true)" ]; then
log "PGDATA: перемещаю $PGDATA в $backup_path"
mv "$PGDATA" "$backup_path"
else
log "PGDATA: старый каталог пустой или отсутствует"
fi
log "PGDATA: создаю новый каталог и archive"
mkdir -p "$PGDATA" /var/lib/pgsql/archive
chown postgres:postgres "$PGDATA"
chmod 700 "$PGDATA"
chown postgres:postgres /var/lib/pgsql/archive
chmod 750 /var/lib/pgsql/archive
log "PGDATA: каталоги подготовлены"
}
# Базовая копия с master-ноды.
basebackup_local_from() {
local master_ip="$1"
log "pg_basebackup: старт с master-ноды $master_ip"
as_postgres env PGPASSFILE="$PGPASSFILE_PATH" pg_basebackup \
--host="$master_ip" \
--username="$REPLICATION_USER" \
--pgdata="$PGDATA" \
--wal-method=stream \
--write-recovery-conf \
--checkpoint=fast \
--progress
log "pg_basebackup: завершён с $master_ip"
}
# Пересборка cs24-1 как standby от cs24-2.
rebuild_standby() {
require_cs24_1
remote_is_master "$CS24_2_IP" || die "cs24-2 должен быть master перед rebuild-standby"
log "rebuild-standby: начинаю пересборку cs24-1 от cs24-2"
log "keepalived: останавливаю на cs24-1"
systemctl stop keepalived || true
log "Приложение: останавливаю на cs24-1"
stack_cmd stop
backup_and_clear_pgdata_local
basebackup_local_from "$CS24_2_IP"
log "PostgreSQL: запускаю на cs24-1"
systemctl start postgresql
local_is_standby || die "cs24-1 не стал standby после пересборки"
log "PostgreSQL: cs24-1 стал standby"
log "keepalived: запускаю на cs24-1, VIP должен остаться на cs24-2"
systemctl start keepalived || true
log "rebuild-standby: завершён, cs24-1 standby"
}
# Ожидание догонки WAL.
wait_local_caught_up_to() {
local master_ip="$1"
local timeout="${2:-$CATCHUP_TIMEOUT}"
local target_lsn diff end_ts
target_lsn="$(remote_scalar "$master_ip" "select pg_current_wal_lsn();")"
end_ts=$(( $(date +%s) + timeout ))
log "WAL: ожидаю догон локального standby до LSN $target_lsn"
while [ "$(date +%s)" -le "$end_ts" ]; do
diff="$(local_scalar "select pg_wal_lsn_diff('$target_lsn', pg_last_wal_replay_lsn())::bigint;" 2>/dev/null || echo 999999999)"
if [ "${diff:-999999999}" -le 0 ]; then
log "WAL: standby догнал master"
return 0
fi
sleep 1
done
die "standby не догнал master за ${timeout} секунд"
}
# Удалённая пересборка standby.
remote_rebuild_as_standby() {
local target_ip="$1"
local master_ip="$2"
log "remote-rebuild: $target_ip будет пересобран как standby от $master_ip"
ssh -p "$SSH_PORT" \
-o BatchMode=yes \
-o ConnectTimeout=5 \
"${SSH_USER}@${target_ip}" bash -s -- \
"$PGDATA" "$REPLICATION_USER" "$master_ip" "$PGPASSFILE_PATH" "$BACKUP_DIR" <<'EOS'
set -euo pipefail
PGDATA="$1"
REPLICATION_USER="$2"
MASTER_IP="$3"
PGPASSFILE_PATH="$4"
BACKUP_DIR="$5"
ts="$(date '+%Y%m%d-%H%M%S')"
backup_path="$BACKUP_DIR/pgsql-data-$ts"
echo "remote-rebuild: останавливаю прикладные сервисы"
if command -v supervisorctl >/dev/null 2>&1; then
supervisorctl stop all || true
fi
units="$(systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '$1 ~ /^ds.*\.service$/ {print $1}' | tr '\n' ' ')"
if [ -n "$units" ]; then
# shellcheck disable=SC2086
systemctl stop $units || true
fi
echo "remote-rebuild: останавливаю keepalived и PostgreSQL"
systemctl stop keepalived || true
systemctl stop postgresql || true
mkdir -p "$BACKUP_DIR"
if [ -d "$PGDATA" ] && [ -n "$(ls -A "$PGDATA" 2>/dev/null || true)" ]; then
echo "remote-rebuild: перемещаю PGDATA в $backup_path"
mv "$PGDATA" "$backup_path"
else
echo "remote-rebuild: старый PGDATA пустой или отсутствует"
fi
echo "remote-rebuild: создаю новый PGDATA и archive"
mkdir -p "$PGDATA" /var/lib/pgsql/archive
chown postgres:postgres "$PGDATA" /var/lib/pgsql/archive
chmod 700 "$PGDATA"
chmod 750 /var/lib/pgsql/archive
echo "remote-rebuild: запускаю pg_basebackup от $MASTER_IP"
runuser -u postgres -- env PGPASSFILE="$PGPASSFILE_PATH" pg_basebackup \
--host="$MASTER_IP" \
--username="$REPLICATION_USER" \
--pgdata="$PGDATA" \
--wal-method=stream \
--write-recovery-conf \
--checkpoint=fast \
--progress
echo "remote-rebuild: запускаю PostgreSQL и keepalived"
systemctl start postgresql
systemctl start keepalived || true
echo "remote-rebuild: standby пересобран"
EOS
log "remote-rebuild: $target_ip пересобран как standby от $master_ip"
}
# Ручной возврат master-роли на cs24-1.
switchover_to_cs24_1() {
require_cs24_1
local_is_standby || die "cs24-1 должен быть standby перед switchover"
remote_is_master "$CS24_2_IP" || die "cs24-2 должен быть master перед switchover"
log "switchover: начинаю возврат master-роли на cs24-1"
log "cs24-2: останавливаю приложение"
remote_stack_cmd "$CS24_2_IP" stop
wait_local_caught_up_to "$CS24_2_IP" "$CATCHUP_TIMEOUT"
log "cs24-2: останавливаю keepalived и PostgreSQL"
ssh_cmd "$CS24_2_IP" "systemctl stop keepalived || true; systemctl stop postgresql"
promote_local
log "cs24-1: перезапускаю keepalived для получения VIP"
systemctl restart keepalived || true
log "cs24-1: запускаю приложение"
stack_cmd restart
remote_rebuild_as_standby "$CS24_2_IP" "$CS24_1_IP"
log "switchover: завершён, cs24-1 master, cs24-2 standby"
}
# Имя текущей backend-ноды.
node_name() {
if is_this_ip "$CS24_1_IP"; then
echo "cs24-1"
elif is_this_ip "$CS24_2_IP"; then
echo "cs24-2"
else
echo "unknown"
fi
}
# IP соседней backend-ноды.
peer_ip() {
if is_this_ip "$CS24_1_IP"; then
echo "$CS24_2_IP"
elif is_this_ip "$CS24_2_IP"; then
echo "$CS24_1_IP"
else
echo ""
fi
}
section() {
echo
echo "============================================================"
echo "$*"
echo "============================================================"
}
status_kv() {
printf "%-34s %s\n" "$1:" "$2"
}
service_status_line() {
local svc="$1"
local active enabled
active="$(systemctl is-active "$svc" 2>/dev/null || true)"
enabled="$(systemctl is-enabled "$svc" 2>/dev/null || true)"
status_kv "$svc" "${active:-unknown} / ${enabled:-unknown}"
}
run_status_cmd() {
local title="$1"
shift
echo
echo "-- $title"
"$@" 2>&1 || echo "WARN: команда завершилась с ошибкой: $*"
}
pg_role_ru() {
local role
role="$(local_role)"
case "$role" in
f) echo "master" ;;
t) echo "standby" ;;
*) echo "unknown" ;;
esac
}
status_common() {
local node peer vip_state role
node="$(node_name)"
peer="$(peer_ip)"
role="$(pg_role_ru)"
if ip -4 -o addr show | grep -q "${INT_VIP}/"; then
vip_state="есть на этой ноде"
else
vip_state="нет на этой ноде"
fi
section "Общее состояние ноды"
status_kv "hostname" "$(hostname -f 2>/dev/null || hostname)"
status_kv "node" "$node"
status_kv "peer_ip" "${peer:-unknown}"
status_kv "time" "$(date '+%F %T %Z')"
status_kv "backend VIP $INT_VIP" "$vip_state"
status_kv "PostgreSQL role" "$role"
echo
echo "-- IP backend-интерфейса и VIP"
ip -4 -o addr show | grep -E "${CS24_1_IP}|${CS24_2_IP}|${INT_VIP}" || true
section "Сервисы"
service_status_line postgresql
service_status_line keepalived
if [ "$node" = "cs24-2" ]; then
service_status_line r7-db-watchdog
else
status_kv "r7-db-watchdog" "на cs24-1 не требуется"
fi
service_status_line glusterd
}
status_pg() {
local role peer
role="$(pg_role_ru)"
peer="$(peer_ip)"
section "PostgreSQL"
run_status_cmd "pg_isready локально" \
pg_isready -h 127.0.0.1 -p 5432
status_kv "local role" "$role"
if [ -n "$peer" ]; then
run_status_cmd "pg_isready соседней ноды $peer" \
pg_isready -h "$peer" -p 5432
fi
echo
echo "-- Базовая информация PostgreSQL"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select
now() as now,
pg_is_in_recovery() as is_in_recovery,
current_setting('server_version') as server_version,
current_setting('data_directory') as data_directory;
" 2>&1 || echo "WARN: не удалось получить базовую информацию PostgreSQL"
if [ "$role" = "master" ]; then
echo
echo "-- Репликация на master: pg_stat_replication"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select
client_addr,
application_name,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
write_lag,
flush_lag,
replay_lag
from pg_stat_replication
order by client_addr;
" 2>&1 || echo "WARN: не удалось прочитать pg_stat_replication"
echo
echo "-- Количество replication-клиентов"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select count(*) as replication_clients
from pg_stat_replication;
" 2>&1 || true
fi
if [ "$role" = "standby" ]; then
echo
echo "-- Репликация на standby: pg_stat_wal_receiver"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select
status,
sender_host,
sender_port,
receive_start_lsn,
latest_end_lsn,
latest_end_time
from pg_stat_wal_receiver;
" 2>&1 || echo "WARN: не удалось прочитать pg_stat_wal_receiver"
echo
echo "-- WAL lag на standby"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select
pg_last_wal_receive_lsn() as receive_lsn,
pg_last_wal_replay_lsn() as replay_lsn,
pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) as replay_lag_bytes;
" 2>&1 || true
fi
echo
echo "-- Активность подключений"
as_postgres psql -P pager=off -d "$PGDATABASE" -c "
select
datname,
state,
count(*) as connections
from pg_stat_activity
group by datname, state
order by datname, state;
" 2>&1 || true
echo
echo "-- Последние предупреждения PostgreSQL"
journalctl -u postgresql --since "${STATUS_SINCE:-30 min ago}" -p warning..alert --no-pager -n 80 2>/dev/null || true
}
status_keepalived() {
section "Keepalived"
service_status_line keepalived
echo
echo "-- Проверка keepalived.conf"
keepalived -t -f /etc/keepalived/keepalived.conf 2>&1 || true
echo
echo "-- VRRP/VIP адреса"
ip -4 -o addr show | grep -E "${CS24_1_IP}|${CS24_2_IP}|${INT_VIP}" || true
echo
echo "-- Последние предупреждения keepalived"
journalctl -u keepalived --since "${STATUS_SINCE:-30 min ago}" -p warning..alert --no-pager -n 80 2>/dev/null || true
}
status_gluster() {
section "GlusterFS"
if ! command -v gluster >/dev/null 2>&1; then
echo "WARN: команда gluster не найдена"
return 0
fi
service_status_line glusterd
run_status_cmd "gluster peer status" \
gluster peer status
run_status_cmd "gluster volume status" \
gluster volume status
echo
echo "-- Gluster volumes"
local volumes
volumes="$(gluster volume list 2>/dev/null || true)"
if [ -z "$volumes" ]; then
echo "WARN: список Gluster-томов пустой или недоступен"
else
echo "$volumes"
for volume in $volumes; do
echo
echo "-- Heal summary: $volume"
gluster volume heal "$volume" info summary 2>&1 || true
echo
echo "-- Split-brain check: $volume"
gluster volume heal "$volume" info split-brain 2>&1 || true
done
fi
section "GlusterFS mounts"
findmnt -t fuse.glusterfs,fuse.glusterfs-fuse,glusterfs -o TARGET,SOURCE,FSTYPE,OPTIONS 2>/dev/null || \
mount | grep -i gluster || \
echo "GlusterFS mount-точки не найдены"
echo
echo "-- df по GlusterFS mount-точкам"
df -hT 2>/dev/null | grep -i gluster || true
section "Ошибки GlusterFS"
echo "-- journald glusterd за период: ${STATUS_SINCE:-30 min ago}"
journalctl -u glusterd --since "${STATUS_SINCE:-30 min ago}" -p warning..alert --no-pager -n 120 2>/dev/null || true
echo
echo "-- Поиск ошибок в /var/log/glusterfs за последние сутки"
local gluster_logsecho "-- Последние предупреждения/ошибки из /var/log/glusterfs"
local gluster_logs
gluster_logs="$(find /var/log/glusterfs -type f -name '*.log' 2>/dev/null | tr '\n' ' ')"
if [ -n "$gluster_logs" ]; then
# shellcheck disable=SC2086
grep -Eih "error|failed|failure|disconnect|split-brain|heal.*fail| E | W " $gluster_logs 2>/dev/null | tail -n 120 || true
else
echo "Логи /var/log/glusterfs не найдены"
fi
gluster_logs="$(find /var/log/glusterfs -type f -name '*.log' -mtime -1 2>/dev/null | tr '\n' ' ')"
if [ -n "$gluster_logs" ]; then
# shellcheck disable=SC2086
grep -Eih "error|failed|failure|disconnect|split-brain|heal.*fail| E | W " $gluster_logs 2>/dev/null | tail -n 120 || true
else
echo "Логи /var/log/glusterfs за последние сутки не найдены"
fi
}
status_app() {
section "Прикладные сервисы"
if command -v supervisorctl >/dev/null 2>&1; then
echo "-- supervisorctl status"
supervisorctl status 2>&1 || true
else
echo "supervisorctl не найден"
fi
echo
echo "-- ds*.service"
local units
units="$(stack_units | tr '\n' ' ')"
if [ -n "$units" ]; then
# shellcheck disable=SC2086
systemctl status --no-pager -l $units 2>&1 || true
else
echo "ds*.service не найдены"
fi
}
status_logs() {
section "Последние события r7-ha"
if [ -f "$LOG_FILE" ]; then
tail -n "${STATUS_LOG_LINES:-80}" "$LOG_FILE"
else
echo "$LOG_FILE не найден"
fi
echo
echo "-- watchdog"
journalctl -u r7-db-watchdog --since "${STATUS_SINCE:-30 min ago}" --no-pager -n 80 2>/dev/null || true
}
status_all() {
status_common
status_pg
status_keepalived
status_gluster
status_app
status_logs
}
usage() {
cat <<USAGE
Использование:
$0 <action>
Действия:
check-local
Проверка для keepalived.
watchdog
Автоматический failover на cs24-2.
rebuild-standby
Пересборка cs24-1 как standby от cs24-2.
switchover-to-cs24-1
Ручной возврат master-роли на cs24-1.
status
Полный статус: PostgreSQL, репликация, VIP, keepalived, GlusterFS, приложение.
status-pg
Только PostgreSQL и репликация.
status-gluster
Только GlusterFS, heal, split-brain и ошибки.
status-keepalived
Только keepalived и VIP.
status-app
Только прикладные сервисы.
Переменные:
STATUS_SINCE="2 hours ago" период для journalctl, по умолчанию "30 min ago"
STATUS_LOG_LINES=150 количество строк /var/log/r7-ha.log
USAGE
}
case "${1:-}" in
check-local)
check_local
;;
watchdog)
watchdog
;;
rebuild-standby)
rebuild_standby
;;
switchover-to-cs24-1)
switchover_to_cs24_1
;;
status)
status_all
;;
status-pg)
status_pg
;;
status-gluster)
status_gluster
;;
status-keepalived)
status_keepalived
;;
status-app)
status_app
;;
*)
usage
exit 2
;;
esac
EOF_SCRIPT
chmod +x /usr/local/sbin/r7-ha.sh4.4. Оформление watchdog как systemd-сервиса
На cs24-2 создайте файл /etc/systemd/system/r7-db-watchdog.service:
cat > /etc/systemd/system/r7-db-watchdog.service <<'EOF_SERVICE' [Unit] Description=R7 cs24-2 auto promote when internal VIP is unavailable After=network-online.target postgresql.service keepalived.service Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/sbin/r7-ha.sh watchdog Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF_SERVICE
4.5. Установка и настройка Keepalived на backend-узлах
Выполните установку
dnf install -y keepalived
Файл /etc/keepalived/keepalived.conf на cs24-1:
cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
router_id backend_1
enable_script_security
script_user root
}
vrrp_script check_local_backend {
script "/usr/local/sbin/r7-ha.sh check-local"
interval 2
timeout 2
fall 2
rise 2
}
vrrp_instance VI_INT {
state BACKUP
interface ens3
virtual_router_id 61
priority 200
advert_int 1
nopreempt
authentication {
auth_type PASS
auth_pass Pass1234
}
unicast_src_ip 192.168.26.203
unicast_peer {
192.168.26.126
}
virtual_ipaddress {
192.168.26.8/24 dev ens3 label ens3:intvip
}
track_script {
check_local_backend
}
}
EOFИзмените соответствующие параметры ens3 (название сетевого интерфейса) и auth_pass Pass1234
Файл /etc/keepalived/keepalived.conf на cs24-2:
cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
router_id backend_2
enable_script_security
script_user root
}
vrrp_script check_local_backend {
script "/usr/local/sbin/r7-ha.sh check-local"
interval 2
timeout 2
fall 2
rise 2
}
vrrp_instance VI_INT {
state BACKUP
interface ens3
virtual_router_id 61
priority 100
advert_int 1
nopreempt
authentication {
auth_type PASS
auth_pass Pass1234
}
unicast_src_ip 192.168.26.126
unicast_peer {
192.168.26.203
}
virtual_ipaddress {
192.168.26.8/24 dev ens3 label ens3:intvip
}
track_script {
check_local_backend
}
}
EOFИзмените соответствующие параметры ens3 (название сетевого интерфейса) и auth_pass Pass1234
Выполните команды на cs24-1:
systemctl enable --now keepalived
Выполните команды на cs24-2:
systemctl daemon-reload systemctl enable --now keepalived systemctl enable --now r7-db-watchdog.service
Проверьте конфигурацию и наличие VIP на обоих нодах:
keepalived -t -f /etc/keepalived/keepalived.conf ip a
4.6. Ручное восстановление cs24-1 как standby от cs24-2
Запускать на cs24-1 после автоматического failover, когда cs24-2 уже master. Скрипт останавливает локальные сервисы, перемещает старый PGDATA в /var/backups/r7-ha, выполняет pg_basebackup с cs24-2 и стартует cs24-1 как standby.
Для запуска команда:
/usr/local/sbin/r7-ha.sh rebuild-standby
4.7. Ручное переключение master-роли с cs24-2 на cs24-1
Запускать на cs24-1 только когда cs24-1 является standby от cs24-2. Скрипт останавливает приложение на cs24-2, ждёт догон WAL на cs24-1, останавливает PostgreSQL/Keepalived на cs24-2, promotes cs24-1, перезапускает сервисы на cs24-1 и пересобирает cs24-2 как standby от cs24-1.
Рекомендуется выполнять при отсутствии активных пользователей на портале
Для запуска команда:
/usr/local/sbin/r7-ha.sh switchover-to-cs24-1
5. Установка Корпоративного сервера
Выполнение установки по инструкции ↗.
- На сервере №1
5.1. Предварительная подготовка
Скачайте дистрибутив и рекомендуем, для корректной установки, архив разместите в директории, отличной от /root, например в /mnt или /tmp.
Перейдите в каталог:
cd /mnt
Распакуйте архив:
unzip RedOS_*.zip
Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert
Предоставьте права на скрипт установки:
chmod +x online_installer.sh
Создайте DNS запись для Корпоративного сервера, например:

5.2. Запуск установки первого сервера
Выполните команду:
sudo bash ./online_installer.sh
Выберите Нет, иначе произойдет удаление postgresql

Выберите Нет:

Выберите Да:

Задайте secret. Необходимо ввести секрет (Набор цифр, букв и спецсимволов. Длина от 8 символов) для защищённого доступа Р7-Диска и Сервера Документов:

Укажите пароль к БД ds, ранее созданного в пункте 3.1. (из примера: ds)

Выберите Да:

Выберите postgresql:

Выберите Нет:

Укажите БД master и ip адрес текущего первого сервера:

Укажите по умолчанию порт 5432:

Укажите БД cddisk из пункта 3.1. (из примера: cddisk)

Укажите пароль к БД cddisk из пункта 3.1. (из примера: cddisk):

Измените на актуальный, если есть Р7 Офис Корпоративный сервер 2019 и нажмите ОК

Выберите Да:

Необходимо указать домен, в котором у вас созданы записи из пункта подготовки:

Далее укажите необходимые префиксы для всех модулей.
Установка Почтового сервера в данном комплексе не рассматривается
Дальнейшие действия нужно выполнять после перезагрузки сервера
5.3. Установка второго сервера
Перед установкой корпоративного сервера №2 необходимо пересоздать базу данных, чтобы избежать дублирования информации.
На сервере №1 (Мастере)
supervisorctl stop all sudo -u postgres psql -h 192.168.26.8 -p 5432 DROP DATABASE IF EXISTS cddisk; \q
Далее мы создадим базу данных заново на мастере из пункта 3.1.
- На сервере №2
Скачайте дистрибутив и рекомендуем, для корректной установки, архив разместите в директории, отличной от /root, например в /mnt или /tmp
Перейдите в каталог:
cd /mnt
Распакуйте архив:
unzip RedOS_*.zip
Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и keyфайлы в папку sslcert
Предоставьте права на скрипт установки:
chmod +x online_installer.sh
5.4. Запуск установки
Выполните команду:
sudo bash ./online_installer.sh
Выберите Нет:

Выберите Нет:

Выберите Да:

Укажите такой же secret как для первого сервера:

Укажите пароль к БД ds из пункта 2.2.1. (из примера ds):

Выберите Да:

Выберите postgresql:

Выберите Нет:

Укажите VIP адрес первого сервера master:

Укажите порт 5432:

Укажите БД cddisk из пункта 3.1. (из примера: cddisk):

Укажите пароль к БД cddisk из пункта 3.1. (из примера: cddisk):

Измените на актуальный, если есть Р7-Офис Корпоративный сервер 2019 и нажмите ОК

Выберите Да:

Необходимо указать домен, в котором у Вас созданы записи из пункта подготовки:

Далее укажите необходимые префиксы для всех модулей.
Установка Почтового сервера в данном комплексе не рассматривается.
Дальнейшие действия нужно выполнять после перезагрузки сервера.
6. Выполните доп настройку Nginx, Сервер документов и БД
Выведите значение параметра secure_link_secret на cs24-1:
grep -rn "set \$secure_link_secret" /etc/r7-office/documentserver/nginx/ds.conf
Пример вывода:
13: set $secure_link_secret kCj7RMAAYCNtOh6KiGin;
Для cs24-2:
sudo sed -i "s/set \$secure_link_secret [^;]*/set \$secure_link_secret НОВОЕ_ЗНАЧЕНИЕ/" $(grep -rl "set \$secure_link_secret" /etc/r7-office/documentserver/) sudo grep -RIl '"secretString"' /etc/r7-office/documentserver/ | sudo xargs sed -i 's/"secretString": *"[^"]*"/"secretString": "НОВОЕ_ЗНАЧЕНИЕ"/g'
Где замените НОВОЕ_ЗНАЧЕНИЕ на необходимый параметр c вывода предыдущей команды (пример, kCj7RMAAYCNtOh6KiGin).
Выполните команды по перезапуску nginx и сервера документов:
systemctl restart nginx ds-converter.service ds-docservice.service ds-metrics.service
Выполните настройку указания корректного адреса сервера документов:
sudo -u postgres psql -h192.168.26.8 -d cddisk UPDATE public."MessageSettings" SET "Value"='https://cddisk.test1.s7-office.site' WHERE "Key" = 'apiUrlInternal';
где вместо https://cddisk.test1.s7-office.site ↗ укажите адрес модуля cddisk
7. Reverse proxy nginx ноды
7.1. Статика
Запакуйте необходимые каталоги статики на одной из нод backend:
tar -cjvf /root/web.tar.bz2 /var/www/r7-office/cddisk /var/www/r7-office/cdmail /var/www/r7-office/admin /var/www/r7-office/calendar /var/www/r7-office/contacts /var/www/r7-office/projects /var/www/r7-office/forms /var/www/r7-office/pages /etc/nginx/ssl
Перенесите архивы на ноды proxy и распакуйте:
tar -xjvf /root/web.tar.bz2 -C /
7.2. Установка и настройки nginx
Действия выполняются для обеих нод
Выполните установку и включение в автозагрузку
dnf install -y nginx systemctl enable --now nginx
Проверьте корректность размещения сертификата и ключа в соответствующем каталоге:
ls /etc/nginx/ssl
Создайте следующие конфигурации nginx для модулей:
измените основной conf в /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}/etc/nginx/conf.d/r7-common.conf
map $http_upgrade $proxy_connection {
default upgrade;
'' close;
}
map $http_x_forwarded_proto $the_scheme {
default $http_x_forwarded_proto;
'' $scheme;
}
upstream cs_api_backend {
server 192.168.26.8:38033;
keepalive 32;
}
upstream cs_saml_backend {
server 192.168.26.8:38034;
keepalive 16;
}
upstream cs_app_backend {
server 192.168.26.8:8080;
keepalive 16;
}
upstream cs_doc_backend {
server 192.168.26.8:443;
keepalive 32;
}admin.conf
server {
listen 80;
listen [::]:80;
server_name admin.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
root /var/www/r7-office/admin;
index index.html;
server_name admin.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /.well-known/carddav {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/.well-known/carddav;
}
location /.well-known/caldav {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/caldav;
}
location /carddav {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/carddav;
}
location /saml2 {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_saml_backend/saml2;
}
location /api {
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-Module Admin;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /web-apps {
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;
proxy_pass http://cs_doc_backend/web-apps;
}
location /docserver {
rewrite ^/docserver/(.*)$ /$1 break;
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location /cache {
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location / {
try_files $uri $uri/ /index.html;
}
}calendar.conf
server {
listen 80;
listen [::]:80;
server_name calendar.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
root /var/www/r7-office/calendar;
index index.html;
server_name calendar.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /.well-known/carddav {
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-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/.well-known/carddav;
}
location /.well-known/caldav {
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-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/caldav;
}
location /carddav {
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-Module Calendar;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/carddav;
}
location / {
try_files $uri $uri/ /index.html;
}
}cddisk.conf
server {
listen 80;
listen [::]:80;
server_name cddisk.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
client_max_body_size 4600M;
root /var/www/r7-office/cddisk;
index index.html;
server_name cddisk.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /.well-known/carddav {
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-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/.well-known/carddav;
}
location /.well-known/caldav {
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-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/caldav;
}
location /carddav {
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-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/carddav;
}
location /saml2 {
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;
proxy_pass http://cs_saml_backend/saml2;
}
location /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Disk;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/ws;
}
location /web-apps {
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;
proxy_pass http://cs_doc_backend/web-apps;
}
location /docserver {
rewrite ^/docserver/(.*)$ /$1 break;
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location /cache {
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location / {
try_files $uri $uri/ /index.html;
}
}cdmail.conf
server {
listen 80;
listen [::]:80;
server_name cdmail.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
client_max_body_size 26M;
root /var/www/r7-office/cdmail;
index index.html;
server_name cdmail.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Mail;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Mail;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Mail;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Mail;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/ws;
}
location /web-apps {
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;
proxy_pass http://cs_doc_backend/web-apps;
}
location /docserver {
rewrite ^/docserver/(.*)$ /$1 break;
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location /cache {
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
location / {
try_files $uri $uri/ /index.html;
}
}contacts.conf
server {
listen 80;
listen [::]:80;
server_name contacts.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
root /var/www/r7-office/contacts;
index index.html;
server_name contacts.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /api {
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-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /.well-known/carddav {
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-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/.well-known/carddav;
}
location /.well-known/caldav {
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-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/caldav;
}
location /carddav {
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-Module Contacts;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/carddav;
}
location / {
try_files $uri $uri/ /index.html;
}
}forms.conf
server {
listen 80;
listen [::]:80;
server_name forms.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
client_max_body_size 26M;
root /var/www/r7-office/forms;
index index.html;
server_name forms.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Forms;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Forms;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /app/ {
proxy_pass http://cs_app_backend/;
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;
}
location /api {
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-Module Forms;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location / {
try_files $uri $uri/ /index.html;
}
}pages.conf
server {
listen 80;
listen [::]:80;
server_name pages.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
client_max_body_size 26M;
root /var/www/r7-office/pages;
index index.html;
server_name pages.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Pages;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Pages;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /app/ {
proxy_pass http://cs_app_backend/;
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;
}
location /api {
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-Module Pages;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location / {
try_files $uri $uri/ /index.html;
}
}projects.conf
server {
listen 80;
listen [::]:80;
server_name projects.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
client_max_body_size 26M;
root /var/www/r7-office/projects;
index index.html;
server_name projects.test1.s7-office.site;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location /api/v1/Documents/UploadCallback {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Documents/UploadCallback;
}
location /api/v1/Link/ds {
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-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/Link/ds;
}
location /api {
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-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api;
}
location /.well-known/carddav {
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-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/.well-known/carddav;
}
location /.well-known/caldav {
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-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/api/v1/caldav;
}
location /carddav {
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-Module Projects;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://cs_api_backend/carddav;
}
location / {
try_files $uri $uri/ /index.html;
}
}ds.conf
server {
listen 80;
listen [::]:80;
server_name ds.test1.s7-office.site;
server_tokens off;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ds.test1.s7-office.site;
server_tokens off;
ssl_certificate /etc/nginx/ssl/test1.s7-office.site.crt;
ssl_certificate_key /etc/nginx/ssl/test1.s7-office.site.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_prefer_server_ciphers on;
ssl_verify_client off;
add_header Strict-Transport-Security max-age=31536000;
add_header X-Content-Type-Options nosniff;
client_max_body_size 1024M;
location / {
proxy_pass http://cs_doc_backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_ssl_verify off;
}
}7.3. Установка и настройка keepalived
На обеих нодах nginx
Установите пакет:
dnf install -y keepalived
Создайте скрипт проверки доступности /usr/local/bin/check_internal_vip.sh:
cat > /usr/local/bin/check_internal_vip.sh <<'EOF' #!/bin/bash systemctl is-active --quiet nginx || exit 1 ping -c 2 -W 1 192.168.26.8 >/dev/null 2>&1 || exit 1 exit 0 EOF chmod +x /usr/local/bin/check_internal_vip.sh
Конфигурация для nginx-1
cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
router_id front_1
enable_script_security
script_user root
}
vrrp_script chk_internal_vip {
script "/usr/local/bin/check_internal_vip.sh"
interval 2
timeout 2
fall 2
rise 2
weight -101
}
vrrp_instance VI_EXT {
state MASTER
interface ens4
virtual_router_id 51
priority 200
advert_int 1
authentication {
auth_type PASS
auth_pass Pass1234
}
unicast_src_ip 45.12.228.66
unicast_peer {
45.12.228.67
}
virtual_ipaddress {
45.12.228.68/29 dev ens4 label ens4:extvip
}
track_script {
chk_internal_vip
}
}
EOFКонфигурация для nginx-2
cat > /etc/keepalived/keepalived.conf <<'EOF'
global_defs {
router_id front_2
enable_script_security
script_user root
}
vrrp_script chk_internal_vip {
script "/usr/local/bin/check_internal_vip.sh"
interval 2
timeout 2
fall 2
rise 2
weight -101
}
vrrp_instance VI_EXT {
state BACKUP
interface ens4
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass Pass1234
}
unicast_src_ip 45.12.228.67
unicast_peer {
45.12.228.66
}
virtual_ipaddress {
45.12.228.68/29 dev ens4 label ens4:extvip
}
track_script {
chk_internal_vip
}
}
EOFПроверьте конфигурации на валидность:
keepalived -t -f /etc/keepalived/keepalived.conf
Запустите и добавьте в автозагрузку:
systemctl enable --now keepalived
8. Настройка cs24-1 и cs24-2
8.1. Внесите правки в /etc/hosts
/etc/hosts — уберите привязки на обеих нодах cs
8.2. Изменение параметров для Страницы и Формы
в файле /opt/r7-office/java-maker/external.properties на обоих cs необходимо использовать идентичные параметры, например:
spring.security.oauth2.client.registration.custom-client.client-id=282efeb5-412a-43f3-8769-eea223bcf3e1 spring.security.oauth2.client.registration.custom-client.client-secret=1a83ecd7-1a29-4fdf-b96f-27d7cd721f0a
8.3. Проверка текущего состояния машин:
на cs24-1
ip a | grep 192.168.26.8 sudo -u postgres psql -tAc "select pg_is_in_recovery();" sudo -u postgres psql -Atqc "select client_addr, state, sync_state from pg_stat_replication;" systemctl is-active postgresql keepalived
Ожидаемый результат
inet 192.168.26.8/24 scope global secondary ens3:intvip f 192.168.26.126|streaming|async active active
на cs24-2
ip a | grep 192.168.26.8 sudo -u postgres psql -tAc "select pg_is_in_recovery();" sudo -u postgres psql -Atqc "select pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();" systemctl is-active postgresql keepalived r7-db-watchdog
Ожидаемый результат
t t|0/1902E2C0|0/1902E2C0 active active active
Проверка общего статуса
/usr/local/sbin/r7-ha.sh status
Проверка статуса БД
/usr/local/sbin/r7-ha.sh status-pg
Проверка статуса gluster
/usr/local/sbin/r7-ha.sh status-gluster
Проверка статуса keepalived
/usr/local/sbin/r7-ha.sh status-keepalived
Проверка статуса приложений Р7
/usr/local/sbin/r7-ha.sh status-app













