Если нужен надёжный 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 с гарантированными ресурсами и собственными сетями.

Подготовка 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
- Создайте БД и пользователя 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;"
- Перезапустите 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-коды трек-скрипта).
Автоматический failover на repmgrd
Убедитесь, что repmgrd включён и видит коллег:
systemctl status repmgrd
repmgr cluster show
Политика:
failover=automatic— разрешает автоматический выбор нового лидера.promote_commandиfollow_command— стандартные команды repmgr.
Желательно настроить внешние уведомления через event_notifications и скрипты. Например, при событиях promotion, follow, failover писать в системный лог и дергать ваш алерт-скрипт.
Тестовый сценарий отказа
- Проверьте, что все standby синхронизированы:
repmgr cluster show,pg_stat_replication. - Остановите PostgreSQL на текущем primary:
systemctl stop postgresql. - Подождите реакцию
repmgrd: standby станет primary. - Убедитесь, что VIP переехал:
ip aна нодах; клиенты подключаются к новому узлу. - Запустите старый узел: он должен стать standby (follow) и не получить VIP из-за
nopreempt.
Проведите несколько итераций, включая остановку сети интерфейса, а не демона PostgreSQL — важны разные типы отказов.

Антисплит-брайн и кворумные меры
Главный риск для 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.
Наблюдаемость
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 с приоритизацией и фейловером.


