Продукты Р7
Корпоративный сервер 2024
Корпоративный сервер 2024
Сервер документов
Сервер документов
Редакторы
Редакторы
Корпоративный сервер 2019
Корпоративный сервер 2019
Графика
Графика
Команда
Команда
Мобильные редакторы
Мобильные редакторы
Облачный офис
Облачный офис
Почта
Почта
Органайзер
Органайзер
Сервер лицензирования
Сервер лицензирования
Файл-Экспресс
Файл-Экспресс
Дополнительно
Часто задаваемые вопросы
Разработчикам
Интеграции
Новые возможности

Комплекс 2 приложения и 2 веб сервера Корпоративный сервер 2024 на РЕД ОС 8 с внешними reverse proxy и переключением БД

Обновлено: 13.07.26

Схема

В данной статье рассматривается схема развертывания отказоустойчивого комплекса Корпоративный сервер 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

Условия эксплуатации

  1. Для доступа к серверу будет использоваться адрес cddisk.test1.s7-office.site. и потребуется создать публичную А запись для доступа к сервису.
  2. При недоступности текущей master-ноды standby может быть автоматически promoted в master.
    Возврат прежней master-ноды выполняется только вручную.
  3. Проверка доступности сервиса для изменения мастера выполняется по внутреннему 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.sh

4.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

Была ли эта статья полезной?

Поделитесь пожалуйста своим мнением

      В статье есть ошибкиРекомендации не помоглиТекст трудно понятьСодержание статьи не соответствует заголовкуДругое