19-pgvip — виртуальный IP (VIP) для кластера PostgreSQL

☐ Опциональный. Ставит keepalived на каждый узел кластера с одинаковым конфигом: узел, который сейчас primary, автоматически держит один плавающий адрес (VIP). Приложение подключается по нему всегда — даже после failover.

Зачем нужен

Кластер из модуля 18-pgcluster умеет переживать смерть primary: одна из реплик автоматически станет главной. Но у нового primary — другой IP-адрес. Приложение об этом не знает и продолжает стучаться на мёртвый старый адрес.

VIP (virtual IP, виртуальный/плавающий IP) простыми словами — это отдельный адрес вашей подсети, который не закреплён ни за одним сервером. Его в каждый момент времени держит тот узел, который сейчас primary. Умер держатель — адрес за 2-3 секунды «переезжает» на новый primary. Аналогия: служебный телефон дежурной смены. Номер один и тот же, а трубку берёт тот, кто сегодня дежурит; смена меняется — звонящий этого не замечает.

Результат — одна строка подключения навсегда:

postgresql://user:[email protected]:5432/db?sslmode=require

После любого failover меняется только то, какой физический сервер держит 10.0.0.100. Строка подключения в .env приложения не меняется; драйвер просто переподключится при обрыве — уже к новому primary.

Термины, которые встретятся ниже:

Картина целиком

                       приложения
                           │
              postgresql://user:[email protected]:5432/db
                           │  (VIP — один адрес навсегда)
                           ▼
                    ┌──────────────┐
                    │ VIP 10.0.0.100│ ← держит текущий primary
                    └──────┬───────┘
        ┌──────────────────┼──────────────────┐
        ▼                  ▼                  ▼
   ┌─────────┐        ┌─────────┐        ┌─────────┐
   │ srv-1   │        │ srv-2   │        │ srv-3   │
   │ PRIMARY │        │ replica │        │ replica │
   │keepalived│◀─VRRP─│keepalived│◀─VRRP─│keepalived│
   │ prio 150 │       │ prio 100 │       │ prio 100 │
   │ (100+50) │       │          │       │          │
   └─────────┘        └─────────┘        └─────────┘
     держит VIP        не держит          не держит

На всех узлах keepalived с одинаковым конфигом и базовым приоритетом 100. Check-скрипт pg-vip-check.sh спрашивает pg_is_in_recovery(): если узел primary (не в recovery) — скрипт успешен, keepalived добавляет +50 к приоритету (150 > 100), и этот узел забирает VIP. Реплики скрипт проваливают (+0), VIP к ним не идёт. После failover повышенная реплика начинает проходить проверку — и VIP переезжает к ней за 2-3 секунды.

Когда включать и когда не нужен

Включайте, если все узлы кластера PostgreSQL находятся в одной L2-сети (один ДЦ, общий VLAN, одна приватная сеть облака) и вы хотите единую строку подключения без прокси на стороне приложений.

Не нужен (используйте mitdev pg proxy вместо VIP), если узлы в разных сетях / дата-центрах — например, три сервера у разных провайдеров с разными публичными IP. VRRP там работать не может (см. «Ограничение» ниже).

Развилка: выбери своё

VIP (pgvip) HAProxy-прокси (mitdev pg proxy)
Узлы БД в одной L2-сети
Узлы в разных ДЦ / сетях ✘ (VRRP не пройдёт)
Где настраивается на каждом узле БД на каждом сервере приложений
Что видит приложение адрес VIP (10.0.0.100) 127.0.0.1
Точка отказа нет (сам VRRP) HAProxy на сервере приложений
Скорость переключения 2-3 с (VRRP) ~2 с (health-check :8008)

Если серверов приложений много, а узлов БД три — VIP настраивается один раз на узлах БД. Если приложение и БД в разных ДЦ — только прокси. Прокси-вариант подробно описан в 18-postgresql-cluster.md (раздел «Эндпоинт роли») и ../HA.md.

Зависимости и требования

Что именно делает

При установке (module_install) нужны PG_VIP_ADDRESS и PG_VIP_PEERS. Порядок:

  1. Определяет сетевой интерфейс. Если PG_VIP_IFACE пуст — автоопределение через маршрут по умолчанию (ip route get 1.1.1.1).
  2. Проверяет IP пиров из PG_VIP_PEERS (должны быть корректные IPv4 других узлов).
  3. Ставит keepalived (pkg_install keepalived).
  4. Кладёт check-скрипт lib/pg-vip-check.sh в /usr/local/lib/mitdev/pg-vip-check.sh (0755, запускается от root).
  5. Рендерит конфиг /etc/keepalived/keepalived.conf (права 600) из шаблона, подставляя VIP, интерфейс, VRID, auth-пароль, свой адрес (unicast_src_ip) и список пиров (unicast_peer). Конфиг одинаков по смыслу на всех узлах: state BACKUP, базовый priority 100, weight 50 у check-скрипта.
  6. Открывает VRRP в файрволе (см. следующий раздел).
  7. Запускает keepalived. Если узел — primary, модуль ждёт до 10 секунд и проверяет, что VIP реально поднялся на интерфейсе (ip addr show). Если узел — replica, печатает, что VIP останется на primary и переедет сюда после failover.
  8. Сохраняет адрес VIP в credentials и печатает строку подключения приложений.

Как check-скрипт определяет primary

pg-vip-check.sh выполняет sudo -u postgres psql -tAc 'SELECT pg_is_in_recovery()':

keepalived запускает скрипт каждые 2 секунды (interval 2), с двойным подтверждением (fall 2, rise 2) — чтобы одна случайная осечка не дёрнула VIP.

Firewall: VRRP (proto 112)

keepalived общается между узлами по IP-протоколу 112 — это не порт TCP/UDP, а отдельный номер протокола, и файрволы обрабатывают его особо.

Без открытого proto 112 узлы не увидят VRRP-объявления друг друга — и оба (или все) сочтут себя единственными, что приведёт к конфликту за VIP.

Unicast VRRP и детерминированный пароль

Unicast. В облаках и многих виртуальных сетях multicast (широковещание) отключён — обычный VRRP там молчит. Поэтому mitdev настраивает unicast: каждый узел шлёт VRRP-объявления напрямую по списку пиров (PG_VIP_PEERS, задаётся при установке). В конфиге это unicast_src_ip <свой IP> и блок unicast_peer { … } с адресами остальных узлов. Работает и в облаке, и на bare metal.

Auth-пароль выводится детерминированно. VRRP умеет простую парольную аутентификацию (auth_type PASS) — она защищает не от злоумышленника (для этого есть файрвол и приватная сеть), а от случайного столкновения двух групп VRRP. mitdev вычисляет 8-символьный пароль как sha256(PG_VIP_ADDRESS|PG_VIP_VRID): он получается одинаковым на всех узлах без всякой координации — каждый узел считает его из тех же данных сам. Не нужно передавать секрет между серверами.

Настройка

Переменная Тип По умолчанию Влияние Когда менять
PG_VIP_ADDRESS IP/маска пусто (обязательна) сам плавающий адрес, например 10.0.0.100/24 всегда задаётся при установке
PG_VIP_PEERS список IP пусто (обязательна) IP остальных узлов кластера (для unicast) всегда; перечислить все другие узлы
PG_VIP_IFACE имя интерфейса пусто = автоопределение на каком интерфейсе поднимать VIP если автоопределение выбрало не тот интерфейс (несколько сетей)
PG_VIP_VRID 1-255 51 ID группы VRRP обязательно менять, если в одном L2-сегменте есть другой VIP/VRRP

PG_VIP_VRID уникален на L2-сегмент. Если в вашей сети уже работает keepalived (другой VIP, другой кластер) с тем же VRID — будет конфликт: узлы двух разных групп начнут считать друг друга «своими». Дайте каждому кластеру свой VRID.

PG_VIP_ADDRESS, PG_VIP_PEERS и PG_VIP_IFACE собирает интерактивный установщик; PG_VIP_VRID берётся из config/defaults.conf (51), меняйте при коллизии.

Развёртывание (после того, как кластер уже собран)

Кластер PostgreSQL из 18-pgcluster должен уже работать. Узлы 10.0.0.1 (primary), 10.0.0.2, 10.0.0.3, VIP — свободный 10.0.0.100.

На каждом узле запустите установку с модулем pgvip:

# srv-1 (10.0.0.1)
sudo ./install.sh
#   модуль «виртуальный IP PostgreSQL»
#   PG_VIP_ADDRESS = 10.0.0.100/24
#   PG_VIP_PEERS   = 10.0.0.2 10.0.0.3      (другие узлы)

# srv-2 (10.0.0.2)
sudo ./install.sh
#   PG_VIP_ADDRESS = 10.0.0.100/24
#   PG_VIP_PEERS   = 10.0.0.1 10.0.0.3

# srv-3 (10.0.0.3)
sudo ./install.sh
#   PG_VIP_ADDRESS = 10.0.0.100/24
#   PG_VIP_PEERS   = 10.0.0.1 10.0.0.2

Проверка на любом узле:

sudo mitdev pg vip        # кто держит VIP, состояние keepalived, последние события

Строка подключения приложений (одна и та же навсегда):

postgresql://user:[email protected]:5432/db?sslmode=require

Не забудьте открыть доступ приложению на каждом узле (после failover primary — другой):

sudo mitdev pg allow <ip-сервера-приложений>

Проверка, что всё работает

mitdev pg vip                        # сводка: служба, VIP, держатель, роль узла, журнал
ip -4 addr show                      # на primary среди адресов интерфейса виден 10.0.0.100
systemctl is-active keepalived       # active на всех узлах
journalctl -u keepalived -n 20       # переходы MASTER/BACKUP

Ожидаемо: ip addr show показывает VIP ровно на одном узле — текущем primary. На репликах VIP отсутствует. mitdev pg vip на держателе печатает «этот узел (VIP поднят здесь)».

module_verify считает модуль исправным, если keepalived активен, check-скрипт на месте и исполняемый, конфиг существует.

Ограничение: только один L2-сегмент

VIP работает только внутри одной L2-сети. Это не недоработка mitdev, а свойство VRRP: он опирается на ARP и gratuitous ARP («всем: адрес 10.0.0.100 теперь у меня, обновите таблицы»). ARP — широковещательный и не проходит через маршрутизаторы и через L3-туннели вроде WireGuard. Поэтому:

Для разных ДЦ единую точку входа даёт не VIP, а mitdev pg proxy — локальный HAProxy на сервере приложений, который через эндпоинты ролей (:8008) сам находит текущий primary:

# на сервере приложений, вместо модуля pgvip
sudo mitdev pg proxy 203.0.113.10 198.51.100.20 192.0.2.30
# на каждом узле БД
sudo mitdev pg allow <ip-сервера-приложений>

Строка подключения тогда — postgresql://user:[email protected]:5432/db?sslmode=require. Подробно — в 18-postgresql-cluster.md и ../HA.md.

Частые ошибки

Симптом: после установки mitdev pg vip пишет «VIP пока не поднят». Причина: узлы не видят VRRP-объявления друг друга — закрыт proto 112 (файрвол провайдера, не UFW) или узлы в разных L2-сегментах. Решение: проверить связность и файрвол провайдера для proto 112; убедиться, что все узлы в одной L2-сети. Если сети разные — VIP неприменим, используйте mitdev pg proxy.

Симптом: VIP поднят сразу на двух узлах (оба «держат» адрес). Причина: узлы не слышат друг друга (proto 112 закрыт) и оба считают себя MASTER; или конфликт PG_VIP_VRID с другой VRRP-группой в той же сети. Решение: открыть proto 112 между узлами; убедиться, что VRID уникален в L2-сегменте — при коллизии переустановить с другим PG_VIP_VRID. После починки связности лишний узел сам отдаст VIP.

Симптом: VIP не переехал после failover. Причина: повышенная реплика ещё не стала primary (не прошёл promote), либо keepalived на ней не запущен. Решение: mitdev pg status — убедиться, что появился новый primary; systemctl status keepalived на нём; mitdev pg vip — посмотреть журнал переходов.

Симптом: приложение через VIP получает permission denied после failover. Причина: доступ приложению открыт не на всех узлах — новый primary его не пускает. Решение: sudo mitdev pg allow <ip приложения> на каждом узле кластера.

Симптом: VIP не поднимается, в журнале keepalived — жалоба на интерфейс. Причина: автоопределение выбрало не тот интерфейс (на узле несколько сетей). Решение: переустановить с явным PG_VIP_IFACE=<нужный интерфейс> (посмотреть имя — ip -4 route get 1.1.1.1).

Безопасность и продакшен

Откат и удаление

Автоматический откат при сбое установки: keepalived останавливается и удаляется, снимаются конфиг, check-скрипт и правило VRRP из UFW.

Удаление:

sudo mitdev remove pgvip           # остановить keepalived (VIP снимается с узла),
                                   # удалить конфиг, check-скрипт и правило VRRP;
                                   # пакет keepalived остаётся установленным
sudo mitdev remove pgvip --purge   # то же + удалить пакет keepalived

При удалении с одного узла VIP просто перестаёт держаться этим узлом и остаётся на остальных (пока есть хотя бы один узел с keepalived и ролью primary). Чтобы полностью убрать VIP из кластера — удалите модуль на всех узлах.

См. также