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.
Термины, которые встретятся ниже:
- VRRP (Virtual Router Redundancy Protocol) — протокол, по которому несколько узлов договариваются, кто из них держит общий адрес. Работает на «IP-протоколе 112» (не TCP и не UDP — отдельный номер протокола). Реализует его демон keepalived.
- VRRP-приоритет — число; адрес держит узел с наибольшим приоритетом. mitdev делает так, чтобы приоритет текущего primary всегда был выше, чем у реплик.
- unicast VRRP — обычно VRRP рассылает объявления широковещательно (multicast). В облаках multicast часто отключён, поэтому mitdev настраивает unicast: каждый узел шлёт объявления напрямую перечисленным пирам. Работает где угодно.
- check-скрипт — маленький скрипт, который keepalived периодически запускает, чтобы решить, добавлять узлу приоритет или нет.
- VRID (Virtual Router ID) — номер «виртуального роутера» (группы VRRP). Должен быть уникален в пределах L2-сегмента: два разных VIP с одним VRID в одной сети конфликтуют.
- L2-сегмент — участок сети, внутри которого работает ARP (широковещательное «у кого адрес X?»). Грубо: одна физическая/виртуальная локалка, один VLAN, одна приватная сеть облака.
Картина целиком
приложения
│
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.
Зависимости и требования
- Требует модуль
pgcluster— установщик добавит его сам. VIP бессмыслен без кластера. - Один L2-сегмент для всех узлов (ключевое ограничение, см. ниже).
- Свободный IP в подсети узлов, не занятый ни одним сервером и не выдаваемый DHCP.
- keepalived — ставится модулем.
- Если активен UFW — модуль сам добавит правило для VRRP (proto 112). Если активен firewalld — добавит протокол
vrrp.
Что именно делает
При установке (module_install) нужны PG_VIP_ADDRESS и PG_VIP_PEERS. Порядок:
- Определяет сетевой интерфейс. Если
PG_VIP_IFACEпуст — автоопределение через маршрут по умолчанию (ip route get 1.1.1.1). - Проверяет IP пиров из
PG_VIP_PEERS(должны быть корректные IPv4 других узлов). - Ставит keepalived (
pkg_install keepalived). - Кладёт check-скрипт
lib/pg-vip-check.shв/usr/local/lib/mitdev/pg-vip-check.sh(0755, запускается от root). - Рендерит конфиг
/etc/keepalived/keepalived.conf(права 600) из шаблона, подставляя VIP, интерфейс, VRID, auth-пароль, свой адрес (unicast_src_ip) и список пиров (unicast_peer). Конфиг одинаков по смыслу на всех узлах:state BACKUP, базовыйpriority 100,weight 50у check-скрипта. - Открывает VRRP в файрволе (см. следующий раздел).
- Запускает keepalived. Если узел — primary, модуль ждёт до 10 секунд и проверяет, что VIP реально поднялся на интерфейсе (
ip addr show). Если узел — replica, печатает, что VIP останется на primary и переедет сюда после failover. - Сохраняет адрес VIP в credentials и печатает строку подключения приложений.
Как check-скрипт определяет primary
pg-vip-check.sh выполняет sudo -u postgres psql -tAc 'SELECT pg_is_in_recovery()':
- ответ
f(не в recovery) → узел primary →exit 0→ keepalived добавляет +50 к приоритету → узел забирает VIP; - ответ не
f(реплика или БД лежит) →exit 1→ приоритет остаётся базовым → VIP держится подальше.
keepalived запускает скрипт каждые 2 секунды (interval 2), с двойным подтверждением (fall 2, rise 2) — чтобы одна случайная осечка не дёрнула VIP.
Firewall: VRRP (proto 112)
keepalived общается между узлами по IP-протоколу 112 — это не порт TCP/UDP, а отдельный номер протокола, и файрволы обрабатывают его особо.
- UFW (Debian/Ubuntu): если UFW активен, модуль добавляет в
/etc/ufw/before.rulesпосле маркера# End required linesдве строки (-A ufw-before-input -p 112 -j ACCEPTи-output) с меткойmitdev-vrrp, и перечитывает UFW. Если маркер в before.rules не найден — модуль не ломает файл, а печатает предупреждение с готовой строкой для ручного добавления. - firewalld (RHEL/SUSE): добавляет протокол
vrrp(firewall-cmd --permanent --add-protocol=vrrp) и перезагружает firewalld.
Без открытого 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. Поэтому:
- узлы в одном ДЦ / VLAN / приватной сети облака → VIP работает;
- узлы у разных провайдеров с разными публичными IP, соединённые WireGuard-туннелем → VIP не заработает (WireGuard создаёт L3-туннель, ARP через него не ходит).
Для разных ДЦ единую точку входа даёт не 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).
Безопасность и продакшен
- VIP держите в приватной подсети — не выставляйте базу в интернет. Доступ приложениям — поимённо через
mitdev pg allow. - VRRP-пароль защищает только от случайных коллизий VRRP; реальная защита — файрвол (proto 112 только между узлами кластера) и приватная сеть.
- Свободный IP под VIP не должен выдаваться DHCP и не должен быть закреплён за сервером.
- Один L2-сегмент → один ДЦ. Если все узлы в одном ДЦ, отказ этого ДЦ снесёт весь кластер вместе с VIP. Разносите узлы географически — но тогда VIP отпадает (нужен
pg proxy). - Проверяйте переезд VIP на учениях («убить primary»): см. чек-лист в ../HA.md.
Откат и удаление
Автоматический откат при сбое установки: keepalived останавливается и удаляется, снимаются конфиг, check-скрипт и правило VRRP из UFW.
Удаление:
sudo mitdev remove pgvip # остановить keepalived (VIP снимается с узла),
# удалить конфиг, check-скрипт и правило VRRP;
# пакет keepalived остаётся установленным
sudo mitdev remove pgvip --purge # то же + удалить пакет keepalived
При удалении с одного узла VIP просто перестаёт держаться этим узлом и остаётся на остальных (пока есть хотя бы один узел с keepalived и ролью primary). Чтобы полностью убрать VIP из кластера — удалите модуль на всех узлах.
См. также
- 18-postgresql-cluster.md — сам кластер PostgreSQL (обязательная зависимость), эндпоинт роли,
mitdev pg proxy - ../HA.md — единая точка входа: VIP или прокси, сценарии отказов, учения
- ../MESH.md — приватная сеть между узлами
- ../COMMANDS.md —
mitdev pg vip,mitdev pg allow,mitdev pg proxy - ../TROUBLESHOOTING.md — диагностика сети и служб