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 из кластера — удалите модуль на всех узлах.

См. также