Выберите продукт

PostgreSQL HA и DCS-подход на repmgr: streaming replication + VIP через keepalived/VRRP

Построим отказоустойчивый кластер PostgreSQL на repmgr: физическая streaming replication, автоматический failover и единый VIP через keepalived/VRRP. Разберём конфиги, роли, порядок запуска, меры против split‑brain и безопасное тестирование.
PostgreSQL HA и DCS-подход на repmgr: streaming replication + VIP через keepalived/VRRP

Если нужен надёжный PostgreSQL с минимальным временем простоя, но без внешнего ключевого хранилища (etcd/Consul/ZooKeeper), связка repmgr + streaming replication + keepalived (VRRP) — проверенное решение. В этой статье разбираем архитектуру, настройку и сценарии отказов, показываем конфиги и даём чек-лист в прод.

Идея и архитектура

Цель: обеспечить high availability (HA) PostgreSQL с автоматическим failover и единой точкой входа через VIP. Классическая схема:

  • Три ноды: pg01 (primary), pg02 (standby), pg03 (standby).
  • Физическая streaming replication (asynchronous или synchronous, по требованиям RPO).
  • repmgrd отслеживает состояние кластера и инициирует standby promote.
  • keepalived (VRRP) держит плавающий IP (VIP) на активном primary.

Где тут DCS? В классическом понимании (как в Patroni) DCS — внешнее консенсус-хранилище для координации лидера. В repmgr координация проще: метаданные узлов — в системной БД (свои таблицы), события и автоматизация — через repmgrd и его event hooks. Это менее «распределённый консенсус», но при корректно настроенном VIP и антисплит-брайн логике — жизнеспособное и предсказуемое решение.

Когда выбрать repmgr+keepalived? Когда нужен понятный стек без внешнего DCS, небольшая команда сопровождения, и критичны простота и прозрачность. Когда лучше Patroni? Когда принципиален консенсус, расширенная оркестрация и гибкая политика кворума.

Сетевая модель и роли

Рекомендуемые допущения:

  • Сеть L2/L3 позволяет VRRP (или настроен unicast VRRP).
  • VIP один, клиенты подключаются к нему как к «адресу PostgreSQL».
  • На каждом узле запущены: postgresql, repmgrd, keepalived.

Ключевой момент — синхронизация «кто сейчас primary» между repmgr и keepalived. Мы добьёмся этого через health-check скрипт, который проверяет локальный статус PostgreSQL (не в recovery) и, при необходимости, клонфиг репликации/задержки. Keepalived перемещает VIP только на узел, который действительно primary. Кластер удобно разворачивать на изолированных облачных инстансах — подойдут управляющие узлы на VDS с гарантированными ресурсами и собственными сетями.

Потоковое переключение VIP: keepalived выбирает primary на основе роли PostgreSQL

Подготовка PostgreSQL

Пакеты

apt update
apt install -y postgresql-16 repmgr keepalived jq

Версию PostgreSQL корректируйте под свою ОС. Убедитесь, что версии PostgreSQL совпадают на всех узлах.

Основные параметры postgresql.conf

# /etc/postgresql/16/main/postgresql.conf
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
wal_keep_size = 1024MB
archive_mode = on
archive_command = 'test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'
synchronous_commit = on  # для синхронного режима, если RPO=0 критично
# synchronous_standby_names настраивается ниже, когда появятся standby

Директорию архива WAL создайте и дайте права postgres. Если не используете архивирование — уберите блок, но помните о резервных копиях и PITR. В проде включайте TLS для подключения клиентов (ssl=on, серверный сертификат и ключ). Для выпуска проверенных сертификатов используйте SSL-сертификаты.

pg_hba.conf

# /etc/postgresql/16/main/pg_hba.conf
# Доступ для репликации от узлов кластера
host    replication     repmgr          10.0.0.0/24        md5
host    repmgr          repmgr          10.0.0.0/24        md5
host    all             appuser         10.0.0.0/24        md5

Пользователь repmgr — это роль суперпользователя или роль с нужными правами для управления репликацией. В простом варианте — суперпользователь.

Инициализация primary

  1. Создайте БД и пользователя repmgr на первичном узле:
sudo -u postgres psql -c "CREATE USER repmgr WITH SUPERUSER LOGIN ENCRYPTED PASSWORD 'StrongPass';"
sudo -u postgres psql -c "CREATE DATABASE repmgr OWNER repmgr;"
  1. Перезапустите PostgreSQL после правок конфигурации:
systemctl restart postgresql

Настройка repmgr

На каждом узле создадим /etc/repmgr/16/repmgr.conf с уникальным node_id и общим cluster:

# /etc/repmgr/16/repmgr.conf
node_id=1
node_name=pg01
data_directory='/var/lib/postgresql/16/main'
conninfo='host=pg01 dbname=repmgr user=repmgr password=StrongPass'
pg_bindir='/usr/lib/postgresql/16/bin'
use_primary_conninfo_password=true
failover=automatic
promote_command='repmgr standby promote'
follow_command='repmgr standby follow --upstream-node-id=%n'
monitoring_history=true
event_notifications=log
log_file='/var/log/repmgr/repmgrd.log'

Соответственно на pg02/pg03 меняем node_id, node_name, conninfo.

Регистрация primary

repmgr -f /etc/repmgr/16/repmgr.conf primary register
systemctl enable --now repmgrd

Проверьте статус:

repmgr cluster show

Клонирование standby

На узлах pg02 и pg03 перед клонированием остановите PostgreSQL, очистите data_directory и выполните:

systemctl stop postgresql
rm -rf /var/lib/postgresql/16/main/*
repmgr -h pg01 -U repmgr -d repmgr -f /etc/repmgr/16/repmgr.conf standby clone --fastcheck
systemctl start postgresql
repmgr -f /etc/repmgr/16/repmgr.conf standby register
systemctl enable --now repmgrd

Проверьте кластер:

repmgr cluster show

Если нужна синхронная репликация, на текущем primary задайте список синхронных:

sudo -u postgres psql -c "ALTER SYSTEM SET synchronous_standby_names = 'FIRST 1 (pg02, pg03)';"
systemctl reload postgresql

Параметр «FIRST 1» означает, что подтверждение от любого одного из указанных standby требуется для фиксации транзакции (RPO=0). Для минимизации лага и потерь при failover это предпочтительно, но влияет на задержки записи.

Keepalived/VRRP и VIP

Нам нужен VIP, который всегда укажет на настоящий primary. Логику решим так: keepalived запустит vrrp_script, который проверяет локальную роль PostgreSQL и состояние репликации. Если узел действительно primary, он может удерживать VIP; если нет — приоритет падает, VIP мигрирует. Если интересен альтернативный L4-подход с IPVS, посмотрите материал Keepalived + IPVS как L4‑балансировщик.

Скрипт проверки роли

# /usr/local/bin/pg-vip-check.sh
#!/usr/bin/env bash
set -euo pipefail
PRIMARY=$(psql -U repmgr -d repmgr -tAc "SELECT NOT pg_is_in_recovery();" || echo "f")
REPLAY_LAG=$(psql -U repmgr -d repmgr -tAc "SELECT COALESCE(EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())),0)" || echo "0")
if [ "${PRIMARY}" = "t" ]; then
  # Узел primary — ок
  exit 0
fi
# Не primary — запрещаем VIP
exit 1

Сделайте скрипт исполняемым:

chmod +x /usr/local/bin/pg-vip-check.sh

В примере мы фактически проверяем только факт «не в recovery». Можно расширить: запрещать VIP, если задержка реплея > N секунд после промоушена, или если repmgr cluster show не подтверждает лидерство локального узла.

Конфигурация keepalived

# /etc/keepalived/keepalived.conf
vrrp_script chk_pg_role {
  script "/usr/local/bin/pg-vip-check.sh"
  interval 2
  timeout 2
  fall 2
  rise 2
}

vrrp_instance VI_PG {
  state BACKUP
  interface eth0
  virtual_router_id 51
  priority 100
  advert_int 1
  nopreempt
  authentication {
    auth_type PASS
    auth_pass StrongVRRPPass
  }
  virtual_ipaddress {
    10.0.0.50/24 dev eth0
  }
  track_script {
    chk_pg_role
  }
}

Эту секцию одинаково кладём на все узлы, меняется только priority (на текущем primary можно поставить 200). Режим nopreempt предотвращает «перетягивание» VIP обратно на старший узел после восстановления — VIP останется там, где текущий primary, что логично для БД. Если в вашей сети запрещён VRRP multicast, используйте unicast-настройку keepalived (параметры unicast_src_ip и unicast_peer), но логика трека-скрипта остаётся прежней.

Согласование repmgr и VIP

Важен порядок событий: сначала repmgrd продвигает standby в primary, затем keepalived переносит VIP. В базовой схеме это достигается естественно: скрипт видит, что узел не в recovery, и поднимает приоритет (фактически — не снижает его), и VIP переезжает. Дополнительно можно:

  • Включить event hooks repmgr, чтобы после standby promote перезапустить keepalived или обновить параметры synchronous replication.
  • Ввести «замок» на VIP для узла, который в recovery, — через отрицательный вес в track_script (или просто exit-коды трек-скрипта).
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Автоматический failover на repmgrd

Убедитесь, что repmgrd включён и видит коллег:

systemctl status repmgrd
repmgr cluster show

Политика:

  • failover=automatic — разрешает автоматический выбор нового лидера.
  • promote_command и follow_command — стандартные команды repmgr.

Желательно настроить внешние уведомления через event_notifications и скрипты. Например, при событиях promotion, follow, failover писать в системный лог и дергать ваш алерт-скрипт.

Тестовый сценарий отказа

  1. Проверьте, что все standby синхронизированы: repmgr cluster show, pg_stat_replication.
  2. Остановите PostgreSQL на текущем primary: systemctl stop postgresql.
  3. Подождите реакцию repmgrd: standby станет primary.
  4. Убедитесь, что VIP переехал: ip a на нодах; клиенты подключаются к новому узлу.
  5. Запустите старый узел: он должен стать standby (follow) и не получить VIP из-за nopreempt.

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

Тест отказа: вывод repmgr и проверка миграции VIP через ip addr

Антисплит-брайн и кворумные меры

Главный риск для HA без внешнего DCS — split-brain, когда старый primary изолирован сетью, но жив, и параллельно новый primary уже работает. Что делать:

  • Fencing старого лидера: системный watchdog перезагружает узел при потере связи с кворумом; аппаратный STONITH вне рамок статьи, но крайне полезен.
  • VRRP только поверх реального статуса: наш трек-скрипт не даст VIP старому узлу, если он в recovery или не лидер.
  • nopreempt в keepalived, чтобы VIP не возвращался «по приоритету», а оставался на фактическом лидере.
  • Witness/арбитр: добавьте третью ноду (у нас она есть как standby), а трек-скрипт может дополнительно проверять доступность ключевых пиров.

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

Репликационные слоты и WAL

Для устойчивости потоковой репликации используйте физические слоты. Repmgr может управлять слотами автоматически, но проверьте фактическую конфигурацию:

sudo -u postgres psql -c "SELECT slot_name, active FROM pg_replication_slots;"

Слоты исключают преждевременную очистку WAL, но следите за диском: если standby недоступен долго, WAL будет расти.

Синхронная или асинхронная репликация

Выбор режима — баланс между RPO и задержкой записи:

  • Синхронная: RPO=0, но рост латентности записи; настройте synchronous_standby_names.
  • Асинхронная: минимальная задержка, но возможна небольшая потеря транзакций при аварии primary.

Компромисс — FIRST 1 с двумя репликами: вы живёте при отказе одной реплики, сохраняя RPO=0, и снижаете риск блокировок записи при кратковременных просадках одной из них.

Обслуживание и обновления

  • Плановые работы: переключайтесь на другой узел («switchover») командами repmgr. Это даёт минимум простоя.
  • Обновления PostgreSQL minor: поочерёдно на standby, затем switchover и обновление бывшего primary.
  • Резервные копии: даже с HA бэкапы обязательны; храните архив WAL и периодически проверяйте восстановление. Про автоматизацию копий в объектное хранилище см. статью бэкапы в S3 с Restic/Borg.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Наблюдаемость

  • repmgr cluster show — быстрый срез ролей и лагов.
  • pg_stat_replication — на лидере, чтобы видеть лаг и статус стриминга.
  • Логи repmgrd и keepalived в journald — при разборе аварий и доказательстве, почему VIP переехал.

Расширение трек-скрипта

Пример более строгой проверки: не только «не в recovery», но и «кластер считает меня лидером» (по данным repmgr):

# /usr/local/bin/pg-vip-check-strict.sh
#!/usr/bin/env bash
set -euo pipefail
IS_PRIMARY=$(psql -U repmgr -d repmgr -tAc "SELECT NOT pg_is_in_recovery();" || echo "f")
if [ "${IS_PRIMARY}" != "t" ]; then
  exit 1
fi
# Дополнительно проверим, что repmgr видит этот узел лидером
LEADER=$(repmgr cluster show --compact | awk '/primary/ {print $2}' | head -n1 || true)
HOSTNAME=$(hostname -s)
if [ "${LEADER}" = "${HOSTNAME}" ]; then
  exit 0
fi
exit 1

Такой подход минимизирует шанс ложного захвата VIP.

Безопасное тестирование

  • Сначала «мягкие» отказы: остановка PostgreSQL, затем repmgrd.
  • Потом сетевые: down/up интерфейса, фильтрация VRRP, потеря маршрута до пиров.
  • Наконец, отказ узла целиком (power off), восстановление, и проверка, что он корректно становится standby, а VIP остаётся на текущем лидере.

Чек-лист прод-настройки

  • Уникальные node_id, корректный conninfo на всех узлах.
  • Параметры репликации в postgresql.conf, разрешения в pg_hba.conf.
  • Репликационные слоты настроены, мониторится свободное место под WAL.
  • repmgrd включён, failover=automatic, корректные promote_command и follow_command.
  • Keepalived настроен с vrrp_script, nopreempt и безопасным паролем.
  • VIP перемещается только на реальный primary, что проверяет ваш скрипт.
  • Есть стратегия fencing (минимум — авто-ребут при потере кворума), чтобы исключить split-brain на уровне хоста.
  • Резервные копии и периодическое тестовое восстановление.
  • Алерты по событиям repmgr/keepalived и метрикам lag.

Итоги

Связка PostgreSQL + repmgr + streaming replication + keepalived/VRRP — практичный путь к HA без внешнего DCS. Repmgr управляет ролями и failover, keepalived даёт плавный VIP и простую точку подключения клиентов. При аккуратной настройке синхронной репликации, корректном трек-скрипте и мерах против split-brain вы получите предсказуемое поведение кластера и минимальные простои. Для расширения отказоустойчивости на уровне DNS пригодится подход с геораспределённым маршрутизацией, см. GeoDNS с приоритизацией и фейловером.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...