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

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

Обновлено: 06.10.26

Инструкция актуальна для версий продукта: 2025.4.6.16449 – 2026.3.6.17475

Схема

В данной статье рассматривается схема развертывания отказоустойчивого комплекса Корпоративный сервер 2024 версии 2026.1.12.16832 и выше на Astra Linux.

В состав решения входят:

  • 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;
  • nginx2 — 192.168.26.139;
  • внешний виртуальный 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 версии 15 в режиме master/replica.

Технические требования

  • 4 Виртуальные машины;
  • Для хранения данных GlusterFS;
  • ТХ Машин для тестирования приложений возможно использовать:
    • От 4 CPU;
    • От 8Гб RAM;
    • От 50Гб свободного пространства на диске;
  • ТХ Машин для тестирования reverse proxy возможно использовать:
    • От 2 CPU;
    • От 4 Гб RAM;
    • От 50Гб свободного пространства на диске;
  • Более конкретные данные рассчитываются по обращению в ТП;
  • Отключение или перевод SELinux в режим permissive для корректной работы сервисов.
Важно

В состав дистрибутива Корпоративный сервер 2024 не включён модуль Р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. На всех нодах установить, запустить и добавить в автозагрузку

apt update
apt install -y glusterfs-server
systemctl start glusterd
systemctl enable glusterd

Также рекомендуется, чтобы gluster корректно работал, соединялся друг с другом и не падал, то нужно посмотреть порты:

cat /etc/glusterfs/glusterd.vol

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

Нам нужно добавить исключения на firewall, чтобы соединения проходили:

ufw allow 49152:49251/tcp
ufw allow 24007:24008/tcp
ufw 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. Установка и настройка БД

Установите следующие пакеты на оба сервера:

apt install postgresql postgresql-contrib

3.1. БД

  • На всех серверах

Создайте первичный кластер базы данных:

sudo systemctl enable --now postgresql

Отредактируйте /etc/postgresql/15/main/postgresql.conf

listen_addresses = '*'          # Слушать на адресах
port = 5432                     # Задать порт БД
wal_level = replica             # Включить репликацию
archive_mode = on               # Включить архивирование WAL
archive_command = 'cp %p /var/lib/postgresql/15/main/archive/%f'   # Команда для архивирования WAL и при необходимости можно настроить архивацию в нужный каталог и монтировать на отдельный диск для резервирования
max_wal_senders = 10            # Максимальное количество одновременных подключений репликации
max_replication_slots = 10
hot_standby = on                # Разрешить подключения в режиме hot standby
wal_keep_size = 4096MB
  • На всех серверах

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

/etc/postgresql/15/main/pg_hba.conf

Привести к виду:
# local   all             postgres                                peer
# Разрешить локальные подключения для всех пользователей
# "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 адрес двух серверов, а вместо vip_internal/32 укажите внутренний VIP. Режим trust необходим для установки Корпоративного сервера

И добавьте строки в конце (для возможности репликации БД):

# Разрешить репликацию с обоих серверов для пользователя replication_user
host    replication     replication_user        ip_srv1/32    md5
host    replication     replication_user        ip_srv2/32    md5

где,  ip_srv1/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/postgresql/15/archive
sudo chown -R postgres: /var/lib/postgresql/15/archive
sudo chmod -R 750 /var/lib/postgresql/15/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/postgresql/.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/postgresql/.pgpass
chmod 600 /var/lib/postgresql/.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/postgresql/15/main
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/postgresql/.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
  systemctl restart nginx
  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="$(local_scalar "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/postgresql-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/postgresql/archive
  
  chown postgres:postgres "$PGDATA"
  chmod 700 "$PGDATA"
  
  chown postgres:postgres /var/lib/postgresql/archive
  chmod 750 /var/lib/postgresql/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/postgresql-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/postgresql/archive
  
chown postgres:postgres "$PGDATA" /var/lib/postgresql/archive
chmod 700 "$PGDATA"
chmod 750 /var/lib/postgresql/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-узлах

Выполните установку

apt install 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 CDinstall_*.zip

Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert

Предоставьте права на скрипт установки:

chmod +x ./CDDiskPack/CDinstall/online_installer.sh

Создайте DNS запись для Корпоративного сервера, например:

5.2. Запуск установки первого сервера

Выполните команды:

cd /mnt/CDDiskPack/CDinstall/
sudo ./online_installer.sh

Выберите Нет, иначе произойдет удаление postgresql

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

Выберите Да:

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

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

Выберите Да:

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

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

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

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

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

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

Укажите пароль к БД 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 CDinstall_*.zip

Для корректной работы Корпоративного сервера обязательно требуется настройка HTTPS. Перед установкой скопируйте crt и key файлы в папку sslcert

Предоставьте права на скрипт установки:

chmod +x ./CDDiskPack/CDinstall/online_installer.sh

5.4. Запуск установки

Выполните команды:

cd /mnt/CDDiskPack/CDinstall/
sudo bash ./online_installer.sh

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

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

Выберите Да:

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

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

Выберите Да:

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

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

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

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

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

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

Укажите пароль к БД 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:

grep -rl 'set \$secure_link_secret' /etc/r7-office/documentserver/ | xargs -I {} sudo sed -i 's/set \$secure_link_secret [^;]*/set \$secure_link_secret НОВОЕ_ЗНАЧЕНИЕ/g' {}
 
sudo grep -rl '"secretString"' /etc/r7-office/documentserver/ | xargs -I {} sudo sed -i 's/"secretString"[[:space:]]*:[[:space:]]*"[^"]*"/"secretString": "НОВОЕ_ЗНАЧЕНИЕ"/g' {}

Где замените НОВОЕ_ЗНАЧЕНИЕ на необходимый параметр c вывода предыдущей команды (пример, kCj7RMAAYCNtOh6KiGin).

Выполните команды по перезапуску nginx и сервера документов:

systemctl restart nginx ds-converter.service ds-docservice.service ds-metrics.service

Выполните настройку указания корректного адреса сервера документов:

sudo -u postgres psql -h 192.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

Действия выполняются для обеих нод

Выполните установку и включение в автозагрузку

apt update
apt install -y nginx
systemctl enable --now nginx

Проверьте корректность размещения сертификата и ключа в соответствующем каталоге:

ls /etc/nginx/ssl

Создайте следующие конфигурации nginx для модулей:

измените основной conf в /etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
 
include /etc/nginx/modules-enabled/*.conf;
 
events {
    worker_connections 768;
    # multi_accept on;
}
 
http {
 
    ##
    # Basic Settings
    ##
 
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;
 
    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;
 
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
 
    ##
    # SSL Settings
    ##
 
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
 
    ##
    # Logging Settings
    ##
 
    access_log /var/log/nginx/access.log;
 
    ##
    # Gzip Settings
    ##
 
    gzip on;
 
    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
 
    ##
    # Virtual Host Configs
    ##
 
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

/etc/nginx/conf.d/0-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_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Module Disk;
 
        proxy_pass https://cs_doc_backend/web-apps;
    }
 
    location /docserver {
        rewrite ^/docserver/(.*)$ /$1 break;
        proxy_pass https://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 https://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 https://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

Установите пакет:

apt 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

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

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

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