18-pgcluster — кластер PostgreSQL со стриминговой репликацией
☐ Опциональный. Превращает узел в участника кластера PostgreSQL (роль
primaryилиreplica) со стриминговой репликацией, эндпоинтом роли и watchdog-самовосстановлением на каждом узле.
Зачем нужен
Обычная установка PostgreSQL (модуль 12-postgresql) — это один сервер. Если он выйдет из строя (упало питание, kernel panic, оборвалась сеть, отказал диск), база данных недоступна целиком: приложения не могут ни читать, ни писать, пока сервер не поднимут вручную.
Отказоустойчивость базы данных простыми словами — это когда данные существуют больше чем на одном сервере, и когда главный сервер умирает, его место автоматически занимает запасной, а приложения этого почти не замечают. Достигается это тремя вещами, которые и настраивает этот модуль:
- Репликация — копия всех данных постоянно течёт с главного сервера на запасные.
- Автоматический failover — сторож (watchdog) на каждом узле замечает смерть главного и повышает одну из копий в главные.
- Единая точка входа — приложение всегда обращается по одному адресу, даже если за ним стоит уже другой сервер (это делает модуль
19-pgvipили командаmitdev pg proxy).
Термины, которые встретятся ниже (объясняем сразу):
- streaming replication (потоковая репликация) — главный сервер (primary) непрерывной струёй отправляет запасным (replica) журнал всех своих изменений, и они применяют его у себя. Аналогия: primary диктует, реплики стенографируют слово в слово с задержкой в миллисекунды.
- WAL (Write-Ahead Log) — журнал предзаписи. PostgreSQL сначала пишет каждое изменение в этот журнал, и только потом — в саму базу. Именно WAL и передаётся репликам. Аналогия: черновик-протокол всех операций, по которому можно точь-в-точь воспроизвести состояние базы.
- слот репликации (replication slot) — «закладка» на primary, которая помнит, до какого места каждая реплика уже дочитала WAL. Пока реплика не подтвердила приём, primary не удаляет соответствующий кусок журнала. Защищает реплику от отставания, при котором нужный ей WAL уже стёрт.
- timeline — «линия времени» кластера. Каждый раз, когда реплику повышают в primary, номер timeline увеличивается на 1. По нему всегда можно понять, кто новее: у повышенной реплики timeline старше, чем у прежнего primary.
Чем это НЕ является. Это не шардинг (данные не разрезаются по серверам — на каждом узле лежит полная копия базы). Это не балансировка записи (писать можно только в один узел — primary; реплики принимают только чтение). И это не защита от логических ошибок: DROP TABLE или кривая миграция мгновенно повторятся на всех репликах — от этого спасают только бэкапы.
Картина целиком
приложения (пишут и читают)
│
одна строка подключения
VIP 10.0.0.100:5432 ИЛИ 127.0.0.1:5432 (HAProxy)
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ srv-1 │ │ srv-2 │ │ srv-3 │
│ PRIMARY │══ WAL ══▶│ replica │ │ replica │
│ │══════ WAL ═════════════════▶ │ │
│ :5432 RW│ │ :5432 RO│ │ :5432 RO│
│ :8008 → │ 200 │ :8008 → │ 503 │ :8008 → │ 503
│ watchdog│ │ watchdog│ │ watchdog│
└─────────┘ └─────────┘ └─────────┘
└──── приватная сеть 10.0.0.0/24 (WireGuard mesh) ────┘
- primary — единственный узел, принимающий запись (RW). Отдаёт WAL всем репликам.
- replica — копия primary, доступна только на чтение (RO). Стримит WAL и применяет его.
- :8008 — эндпоинт роли — крохотный HTTP-сервис на каждом узле, отвечающий
200 primaryили503 replica. По нему HAProxy на серверах приложений понимает, куда слать запись. - watchdog — сторож на каждом узле; следит за соседями и при аварии перестраивает кластер сам.
- VIP / HAProxy — единая точка входа (см. 19-pg-vip.md); приложение всегда обращается по одному адресу.
Когда включать и когда не нужен
Включайте, если:
- недоступность базы данных на минуты/часы для вас недопустима (магазин, платежи, SaaS);
- у вас есть 3 (или 5) серверов в приватной сети, готовых держать копии;
- вы хотите переживать отказ одного сервера без ручного вмешательства ночью.
Не нужен, если:
- у вас один сервер (кластер требует минимум двух, а для нормального автоповышения без split-brain — трёх);
- база маленькая и её простой на время ручного восстановления из бэкапа приемлем;
- вам нужна масштабируемая запись — это другая задача (шардинг), модуль её не решает.
Важно про число узлов. Кластер из трёх узлов переживает отказ одного. Из пяти — двух. Два узла — это не отказоустойчивость: при разрыве сети ни один не может понять, умер сосед или просто пропала связь, и повышать себя опасно (получите два primary — split-brain).
Зависимости и требования
- Требует модуль
12-postgresql— установщик добавит его автоматически. Кластер настраивается поверх уже работающего PostgreSQL. - Версии PostgreSQL на всех узлах должны совпадать.
pg_basebackupкопирует бинарный каталог данных — реплика с другой мажорной версией не запустится. - Приватная сеть между узлами. Репликация должна идти по приватным адресам (
10.0.0.x), а не по публичному интернету «в открытую». Соберите mesh до кластерных команд — см. ../MESH.md. Если узлы в разных дата-центрах — трафик всё равно защищён TLS (см. ниже), но приватная сеть остаётся правильной практикой. - TLS. Между узлами репликация всегда шифруется (
hostssl+sslmode=require) — незашифрованные подключения отклоняются. Отдельно ничего настраивать не нужно, PostgreSQL использует самоподписанный сертификат из коробки. - systemd — модуль ставит systemd-юниты, socket-активацию и sudoers; на системах без systemd не работает.
Что именно делает — роль primary
При установке с PG_CLUSTER_ROLE=primary (_pg_setup_primary):
- Создаёт роль репликации
PG_REPLICATION_USER(по умолчаниюreplicator) с правамиREPLICATION LOGINи сгенерированным паролем. Пароль сохраняется вCREDENTIALS_FILE(/root/.mitdev-credentials) и в~postgres/.mitdev-repl-pass(для watchdog). Шаг идемпотентный: если роль уже есть — не трогает. - Пишет параметры репликации в drop-in
conf.d/99-mitdev-replication.conf:listen_addresses = '*' # слушать по сети, не только localhost ssl = on wal_level = replica # писать в WAL достаточно данных для реплик max_wal_senders = 10 # сколько реплик может стримить одновременно max_replication_slots = 10 wal_keep_size = '512MB' # запас WAL, чтобы реплика догнала после паузы hot_standby = on # разрешить чтение на репликах wal_log_hints = on # включает pg_rewind (нужно для fencing) - Открывает доступ реплик поимённо. Для каждого IP из
PG_REPLICA_IPSдобавляет вpg_hba.confзаписьhostssl replication replicator <ip>/32 scram-sha-256и правило UFW5432/tcpтолько с этого IP. Порт 5432 не открывается «всему миру». - Выдаёт GRANT'ы для pg_rewind роли
replicator(функцииpg_ls_dir,pg_stat_file,pg_read_binary_file) и добавляет hba-записиhostssl postgres replicator <ip>/32— чтобы понижённый бывший primary мог автоматически вернуться репликой (см. fencing ниже). - Перезапускает PostgreSQL и проверяет, что
wal_levelсталreplica. - Ставит watchdog (на primary — в режиме fencing, авто-повышение выключено) и эндпоинт роли.
Если IP реплик при установке не заданы — не беда, добавите позже командой mitdev pg add-replica <ip>.
Что именно делает — роль replica
При установке с PG_CLUSTER_ROLE=replica (_pg_setup_replica) нужны PG_PRIMARY_HOST и PG_REPLICATION_PASSWORD. Порядок:
Проверка доступности primary (
pg_isready -h <primary> -p 5432 -t 10). Это защитный барьер: пока primary недоступен, локальный PostgreSQL НЕ трогается вообще. Если проверка провалилась — модуль печатает, что проверить (mitdev pg add-replicaна primary,listen_addresses, файрвол провайдера) и выходит, ничего не сломав.Идемпотентность. Если узел уже является стримящей репликой (
pg_is_in_recovery()=tи статус WAL receiver =streaming) — пересоздание пропускается. Уже работающую реплику модуль не трогает. Исключение — принудительный ресинк (PG_FORCE_RESYNC=yes, его выставляетmitdev pg follow).Остановка PostgreSQL.
Старый каталог данных откладывается в сторону, а не удаляется. Если в каталоге данных что-то есть, он переименовывается в
<datadir>.mitdev-old.<дата-время>(например.../main.mitdev-old.20260710143012). Это и есть ваш откат: если что-то пойдёт не так, старые данные на месте. Также регистрируется автоматический rollback — при сбое установки каталог вернётся на место, и PostgreSQL запустится в исходном виде.pg_basebackupс primary — полная бинарная копия базы:pg_basebackup -d "host=<primary> port=5432 user=replicator password=… sslmode=require" \ -D <datadir> -R -X stream --checkpoint=fast -C -S mitdev_<hostname>-R— записываетstandby.signal(файл-признак «я реплика») иprimary_conninfo(строку подключения к primary, включая пароль; файл 0600, только для postgres);-X stream— тянет WAL параллельно с копированием, чтобы копия была согласованной;-C -S mitdev_<hostname>— создаёт на primary физический слот репликации с именемmitdev_<короткое-имя-хоста>;sslmode=require— WAL между узлами всегда шифруется.
При ошибке (не совпал пароль, нет hba-записи, слот уже существует) модуль печатает частые причины и восстанавливает прежние данные из отложенного каталога.
Запуск в режиме standby и проверка до 30 секунд (15 попыток × 2 с), что узел вышел в recovery и WAL receiver в состоянии
streaming.Сохранение данных в credentials (роль, primary_host, имя слота) и установка watchdog + эндпоинта роли. Watchdog ставится независимо от
PG_AUTO_FAILOVER: даже реплика, которой запрещено повышаться, обязана уметь автоматически перецепиться на нового primary.
Эндпоинт роли (порт 8008)
Зачем. Когда узлы в разных дата-центрах, VIP не работает (VRRP требует общей L2-сети — см. 19-pg-vip.md). Тогда единую точку входа даёт HAProxy на сервере приложений (mitdev pg proxy), а чтобы HAProxy понимал, кто сейчас primary, каждый узел кластера поднимает эндпоинт роли.
Что это. Персистентный systemd-сервис mitdev-pg-role.service (User=postgres), слушающий PG_ROLE_PORT (по умолчанию 8008) через один постоянный листенер socat (TCP-LISTEN:…,fork), который форкает лёгкий обработчик на каждый коннект внутри одного и того же юнита. На каждое HTTP-подключение он выполняет SELECT pg_is_in_recovery() и отвечает:
200 OK, телоprimary tl=<N>— узел принимает запись;tl— текущий timeline (по нему watchdog-fencing понимает, кто новее);503 Service Unavailable, телоreplica— узел стоит репликой (или PostgreSQL лежит).
HAProxy держит трафик на том узле, который отвечает 200. После установки модуль сам проверяет, что эндпоинт отвечает и его ответ совпадает с ролью узла.
Почему не socket-activation. Раньше эндпоинт был socket-activated (mitdev-pg-role.socket + [email protected]) — systemd поднимал отдельный юнит на каждое подключение. При частых опросах HAProxy с нескольких app-узлов это упиралось в systemd start-limit: часть коннектов сбрасывалась (RST), HAProxy метил primary как DOWN, и это каскадом валило запись 500/503 у приложений. Поэтому эндпоинт переведён на один персистентный листенер socat — юнит не пересоздаётся на каждый коннект. Модуль ставит зависимость socat на db-узлах автоматически (pkg_install socat) и при обновлении со старой версии сам снимает legacy-юниты mitdev-pg-role.socket/[email protected].
Диагностика. sudo systemctl status mitdev-pg-role.service, логи — journalctl -u mitdev-pg-role.service.
Безопасность. Порт 8008 закрыт в UFW и открывается точечно только серверам приложений командой mitdev pg allow <ip> (она открывает и 5432, и 8008 для указанного IP).
TLS между узлами
Репликация идёт по сети — иногда через публичный интернет между дата-центрами. Чтобы никто не мог перехватить WAL или подключиться без шифрования:
- все mitdev-записи в
pg_hba.confсоздаются какhostssl(а неhost) — соединение без TLS отклоняется на этапе аутентификации; primary_conninfoна репликах содержитsslmode=require— реплика откажется стримить по незашифрованному каналу;- pg_rewind и pg_basebackup внутри watchdog тоже подключаются с
sslmode=require.
PostgreSQL использует самоподписанный сертификат, который Debian/Ubuntu генерируют при установке. sslmode=require шифрует канал, но не проверяет имя сертификата — этого достаточно против пассивного перехвата; строгая проверка (verify-full) потребовала бы собственного CA и здесь не настраивается.
Watchdog самовосстановления v3
Это ключевая часть, делающая конструкцию отказоустойчивой, а не просто реплицированной. Скрипт — lib/pg-watchdog.sh, ставится в /usr/local/lib/mitdev/pg-watchdog.sh.
Где и как работает:
- ставится на ВСЕ узлы кластера (и primary, и реплики);
- юнит
mitdev-pg-watchdog.service, работает отUser=postgres, с hardening (NoNewPrivileges,ProtectHome=read-only,PrivateTmp),Restart=always; - конфиг
/etc/default/mitdev-pg-watchdog(права 640,root:postgres) — параметрыPRIMARY_HOST,CHECK_INTERVAL,FAIL_THRESHOLD,AUTO_PROMOTE,PROMOTE_DELAY,PEERS,ROLE_PORT,SELF_IP,WITNESS_HOSTS; - рабочее состояние в домашнем каталоге postgres:
~postgres/.mitdev-pg-primary(текущий primary после перецепления),~postgres/.mitdev-repl-pass(пароль репликации, 0600),~postgres/.mitdev-pg-promoted(отметка о повышении),~postgres/.mitdev-pg-unverified(роль primary ещё не подтверждена).
Кворум — почему реплика не всегда повышается
Само по себе «я не вижу primary» не означает, что primary умер. Может быть, это вас отрезали от сети, а primary жив и продолжает принимать запись от приложений. Если такая реплика повысится, в кластере окажется два primary, а когда связь восстановится, pg_rewind молча откатит всё, что настоящий primary успел записать.
Поэтому реплика повышается, только пока видит большинство узлов кластера (себя + достижимые пиры ≥ N/2+1). Симметрично: primary, оказавшийся в меньшинстве, переводит свой эндпоинт роли в 503 — HAProxy и VIP уводят от него запись, хотя PostgreSQL продолжает работать и обслуживать чтение. Это «мягкий self-fence»: узел не останавливается, но перестаёт быть точкой записи.
Кластер из двух узлов — особый случай. Большинство там недостижимо: после потери одного узла второй видит 1 из 2. Правила асимметричны:
| Ситуация | Без PG_WITNESS_HOSTS |
С witness |
|---|---|---|
| Реплика не видит primary | не повышается (primary может быть жив) | повышается, если видит witness |
| Primary не видит реплику | продолжает работать (иначе отказ реплики убил бы запись) | уходит в 503, если witness недоступен |
Witness — это любой хост вне обоих узлов БД, достижимость которого доказывает, что вы на связной стороне сети: сервер приложений, шлюз. Задаётся PG_WITNESS_HOSTS="10.0.0.9". Для кластера из трёх и более узлов witness не нужен — там работает обычное большинство.
Возврат упавшего primary: почему он сначала «невидим»
Когда упавший primary включается обратно, PostgreSQL поднимает его как обычный primary — он ещё не знает, что за время его отсутствия повысили реплику. Раньше в это окно HAProxy и VIP успевали направить в него записи, которые затем уничтожал pg_rewind.
Теперь watchdog при старте в роли primary создаёт ~postgres/.mitdev-pg-unverified. Пока файл существует, эндпоинт роли отвечает 503 unverified, а pg-vip-check не отдаёт узлу VIP — то есть запись в него не попадает. Файл снимается после первой же проверки пиров, когда выясняется, что никто узел не превзошёл. Если watchdog остановлен, файл удаляется автоматически (ExecStopPost), чтобы узел не выпал из кластера навсегда.
На реплике: авто-failover и авто-перецепление
Каждые CHECK_INTERVAL (5 с) реплика дважды проверяет primary: pg_isready и сырой TCP-коннект на 5432. Отказ засчитывается, только если оба способа провалились — это отсекает ложные срабатывания при кратковременной загрузке БД.
После FAIL_THRESHOLD (6) неудач подряд и паузы PROMOTE_DELAY:
- watchdog ещё раз проверяет, не ожил ли primary (если да — failover отменяется);
- если другой пир уже стал primary (спрашивает у пиров через эндпоинт роли :8008) — реплика перецепляется к нему: переписывает
host=вprimary_conninfo, обнуляетprimary_slot_name, перезапускается. Копировать данные не нужно — непрерывность WAL обеспеченаwal_keep_sizeна primary. Этоrefollow; - иначе, если
AUTO_PROMOTE=yes— реплика повышается в primary (promote), поднимает timeline на +1, и остальные реплики сами перецепятся к ней.
Если AUTO_PROMOTE=no и нового primary среди пиров нет — реплика просто ждёт (она заведомо не должна становиться главной, например слабее по железу), но перецепиться к чужому повышению всё равно сможет.
На primary: fencing (защита от split-brain)
Primary следит за эндпоинтами ролей пиров. Если он видит другой узел, который сообщает primary со старшим timeline, — значит, этот узел был недоступен, за это время реплику повысили, и он устарел. Тогда он сам себя понижает:
- останавливается;
- делает
pg_rewindот победителя (пароль передаётся черезPGPASSWORD, а не в командной строке — чтобы не светился вps); - если
pg_rewindне удался — fallback: откладывает каталог в.mitdev-old.<дата>и делает полныйpg_basebackup; - запускается уже репликой нового primary.
Именно поэтому split-brain не случается: узел со старым timeline никогда не остаётся primary. Ожившый после сбоя старый primary возвращается в кластер репликой — без единой ручной команды.
Два primary с одинаковым timeline (обе реплики повысились одновременно) разрешаются автоматически и детерминированно: узел с бóльшим IP-адресом самопонижается через pg_rewind, узел с меньшим остаётся primary. Оба узла выполняют одно и то же сравнение и приходят к противоположным выводам, поэтому понижается ровно один. Сравнение числовое, а не строковое — иначе 23.134.42.10 оказался бы «меньше» 23.134.42.9.
Журнал pg_rewind при самопонижении — /var/log/postgresql/mitdev-pg-rewind.log.
Что делает watchdog возможным
wal_log_hints=on— без негоpg_rewindневозможен;- GRANT'ы функций pg_rewind роли
replicatorи hba-записиdbname=postgres— чтобы понижение работало без суперпользователя; - узкий sudoers
/etc/sudoers.d/mitdev-pg-watchdog— даёт postgres право только наsystemctl stop/start/restart postgresql, и ничего больше; - пароль репликации в
~postgres/.mitdev-repl-pass(0600) — для авто-rewind/basebackup.
Управление watchdog
mitdev pg watchdog status # состояние службы, конфиг, последние события
sudo mitdev pg watchdog enable # включить
sudo mitdev pg watchdog disable # выключить (тогда повышать — вручную: mitdev pg promote)
sudo mitdev pg watchdog set FAIL_THRESHOLD 12 # поменять порог (служба перезапустится)
События сторожа: journalctl -t mitdev-pg-watchdog.
Настройка
Все переменные можно задать при установке (установщик спрашивает основные) или экспортировать перед install.sh. Значения по умолчанию — из config/defaults.conf.
| Переменная | Тип | По умолчанию | Влияние | Когда менять |
|---|---|---|---|---|
PG_CLUSTER_ROLE |
primary|replica |
primary |
какую роль настроить на узле | обязательно на репликах — replica (спрашивает установщик) |
PG_REPLICA_IPS |
список IP | пусто | IP реплик, которым открыть доступ (только на primary) | заполнить на primary; иначе добавлять mitdev pg add-replica |
PG_PRIMARY_HOST |
IP/хост | пусто | адрес primary (только на реплике) | обязательно на реплике (спрашивает установщик) |
PG_REPLICATION_PASSWORD |
строка | пусто | пароль роли replicator с primary (на реплике) |
обязательно на реплике (берётся из credentials primary) |
PG_REPLICATION_USER |
строка | replicator |
имя роли репликации | почти никогда |
PG_CLUSTER_PEERS |
список IP | PG_PRIMARY_HOST |
пиры для watchdog реплики (кого спрашивать про новый primary) | указать все другие узлы кластера |
PG_AUTO_FAILOVER |
yes|no |
no |
разрешить этой реплике авто-повышение в primary | yes на основной реплике; no на резервной/слабой |
PG_MAX_WAL_SENDERS |
число | 10 |
сколько реплик может стримить одновременно | при > ~8 реплик |
PG_MAX_REPLICATION_SLOTS |
число | 10 |
максимум слотов репликации | вместе с числом реплик |
PG_WAL_KEEP_SIZE |
размер | 512MB |
запас WAL для отставшей реплики | увеличить при частых больших паузах реплик |
PG_WATCHDOG_INTERVAL |
сек | 5 |
период проб primary | реже — при «дёрганой» сети |
PG_WATCHDOG_THRESHOLD |
число | 6 |
неудач подряд до реакции | больше — консервативнее (см. формулу) |
PG_WATCHDOG_PROMOTE_DELAY |
сек | 0 |
доп. пауза перед повышением | разнести приоритет реплик (резервной дать 45 с) |
PG_ROLE_PORT |
порт | 8008 |
порт эндпоинта роли | при конфликте порта |
Время до повышения = PG_WATCHDOG_INTERVAL × PG_WATCHDOG_THRESHOLD + PG_WATCHDOG_PROMOTE_DELAY. По умолчанию 5 × 6 + 0 = 30 с. Агрессивнее (3 × 4 = 12 с) — стабильная сеть в одной стойке; консервативнее (5 × 12 = 60 с) — разные ДЦ со всплесками задержки.
Основные значения (PG_CLUSTER_ROLE, PG_REPLICA_IPS / PG_PRIMARY_HOST, PG_REPLICATION_PASSWORD, PG_AUTO_FAILOVER, приоритет) собирает интерактивный установщик.
Пошаговое развёртывание кластера из 3 узлов
Предполагаем приватную сеть 10.0.0.0/24: primary 10.0.0.1, реплики 10.0.0.2 и 10.0.0.3. Соберите mesh заранее (см. ../MESH.md).
Шаг 1. Узел A (10.0.0.1) — primary.
curl -fsSL https://ваш-домен/get | sudo bash
# в установщике: компоненты PostgreSQL + «PostgreSQL-кластер»
# роль: primary
# IP реплик: 10.0.0.2 10.0.0.3
sudo mitdev net bind postgresql private # 5432 только в приватной сети
Пароль репликации показан в /root/.mitdev-credentials — он понадобится на репликах.
Шаг 2. (Опционально) Залить существующую базу на A.
sudo mitdev pg import /path/backup.dump mydb # реплики получат данные сами
Шаг 3. Узел B (10.0.0.2) — основная реплика.
sudo ./install.sh
# модуль «PostgreSQL-кластер», роль: replica
# primary: 10.0.0.1
# пароль репликации: <из credentials узла A>
# авто-failover: Да, приоритет: 1 — основная
sudo mitdev net bind postgresql private
Шаг 4. Узел C (10.0.0.3) — резервная реплика.
sudo ./install.sh
# роль: replica, primary: 10.0.0.1, пароль тот же
# авто-failover: Да, приоритет: 2 — резервная (задержка 45 с настроится сама)
sudo mitdev net bind postgresql private
Шаг 5. Единая точка входа.
- Узлы в одной L2-сети → модуль
pgvip(см. 19-pg-vip.md). - Узлы в разных ДЦ → на сервере приложений
sudo mitdev pg proxy 10.0.0.1 10.0.0.2 10.0.0.3, и на каждом узле БДsudo mitdev pg allow <ip-сервера-приложений>.
Шаг 6. Проверка.
sudo mitdev pg status # A: primary, 2 реплики, лаг ~0
sudo mitdev pg replication # слоты активны
sudo mitdev pg watchdog status # активен на всех трёх
Повседневная эксплуатация
Все команды — подкоманда mitdev pg:
mitdev pg status # роль, базы, подключения, wal_level + сводка репликации
mitdev pg replication # детально: на primary — реплики/слоты/лаг; на реплике — WAL receiver
sudo mitdev pg add-replica 10.0.0.4 # (на primary) разрешить новую реплику: pg_hba + UFW + восстановить listen_addresses
sudo mitdev pg remove-replica 10.0.0.3 # (на primary) убрать одну реплику: слот + pg_hba + UFW
sudo mitdev pg allow 10.0.0.20 mydb deploy # открыть доступ серверу приложений (5432 + 8008)
sudo mitdev pg promote # ручное повышение реплики в primary (плановое)
sudo mitdev pg follow 10.0.0.2 # ручное перецепление реплики на другого primary (плановое)
sudo mitdev pg import dump.sql mydb # (на primary) залить бекап, формат — автоопределение
sudo mitdev pg proxy 10.0.0.1 10.0.0.2 10.0.0.3 # (на сервере приложений) локальный HAProxy
mitdev pg vip # состояние VIP (см. модуль 19)
mitdev pg watchdog status # см. раздел watchdog
Про import: формат определяется сам (pg_dump -Fc → pg_restore; .sql → psql; .sql.gz → gunzip|psql; pg_dumpall → все базы и роли). База создаётся, если её нет (владелец — deploy-пользователь). Выполняется только на primary — на реплике команда честно откажется. Реплики получат данные автоматически.
Про promote/follow: они для плановых операций (обслуживание primary, переезд между ДЦ). При авариях watchdog делает всё сам — не мешайте ему параллельными ручными командами. Перед follow на новом primary выполните mitdev pg add-replica <ip этой реплики>.
Про allow: выполняйте на каждом узле кластера — после failover новый primary тоже должен принимать это приложение.
Проверка, что всё работает
На primary:
sudo -u postgres psql -tAc "SHOW wal_level" # replica (или logical)
sudo -u postgres psql -c "SELECT * FROM pg_stat_replication" # строка на каждую подключённую реплику
mitdev pg replication # слоты active=t, лаг маленький
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8008/ # 200
На реплике:
sudo -u postgres psql -tAc "SELECT pg_is_in_recovery()" # t
sudo -u postgres psql -tAc "SELECT status FROM pg_stat_wal_receiver" # streaming
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8008/ # 503
module_verify считает узел исправным, если служба postgresql активна и: primary — wal_level ∈ {replica, logical}; replica — pg_is_in_recovery()=t.
Сценарии отказа
| Что упало | Что делает система сама | Что делать вам |
|---|---|---|
| Реплика (одна из) | ничего; primary продолжает, её слот копит WAL | вернуть узел; при долгой смерти — pg remove-replica <ip>, чтобы освободить WAL |
| Primary (авто-failover on) | через ~30 с одна реплика → promote (timeline +1); остальные реплики перецепляются к ней; VIP/HAProxy переключается | ничего; позже вернуть старый узел |
| Ожившый старый primary | до подтверждения роли эндпоинт отдаёт 503 (запись в него не идёт); затем fencing по timeline: сам понижается через pg_rewind и возвращается репликой |
ничего |
| Отставание реплики так, что WAL стёрт | слот удерживает WAL (wal_keep_size); при переполнении реплика отвалится |
увеличить PG_WAL_KEEP_SIZE; пересобрать реплику mitdev pg follow |
| Сетевой раздел (реплика отрезана) | реплика видит меньшинство → не повышается; primary в большинстве продолжает принимать запись | ничего; данные не расходятся |
| Сетевой раздел (primary отрезан) | primary в меньшинстве → эндпоинт роли в 503, HAProxy/VIP уводят запись; большинство повышает реплику |
ничего; при схождении старый primary сам вернётся репликой |
| Две реплики повысились одновременно (равный timeline) | узел с бóльшим IP самопонижается через pg_rewind, меньший остаётся primary | ничего |
| Отказ 2 из 3 узлов | кворум не набран: последний узел НЕ повышается (не отличит «все умерли» от «я изолирован») — БД read-only | человеческое решение: pg watchdog disable → убедиться, что узлы мертвы → pg promote → потом enable |
Отказ 1 из 2 узлов, PG_WITNESS_HOSTS не задан |
primary продолжает работать; реплика не повышается | задать witness — иначе авто-failover на 2 узлах невозможен в принципе |
| Split-brain (два primary, равный timeline) | watchdog пишет предупреждение, но не «лечит» | разобраться вручную, какой узел оставить primary, второй — pg follow |
Частые ошибки
Симптом: на реплике pg_basebackup не удался, установка откатилась.
Причина: не совпал пароль репликации; на primary нет hba-записи для IP этой реплики; слот уже существует.
Решение: взять пароль из /root/.mitdev-credentials узла-primary; на primary sudo mitdev pg add-replica <ip реплики>; при «слот существует» на primary sudo -u postgres psql -c "SELECT pg_drop_replication_slot('mitdev_<hostname>')".
Симптом: реплика не выходит в streaming, primary недоступен.
Причина: файрвол провайдера/панели блокирует 5432 между узлами (UFW открыт, но внешний фильтр — нет); listen_addresses не *.
Решение: проверить sudo ss -tlnp | grep 5432 и SHOW listen_addresses на primary; открыть 5432 с IP реплики в панели провайдера.
Симптом: mitdev pg status показывает на primary «0 реплик» и растущее число неактивных слотов.
Причина: реплика отключилась, но её слот остался и копит WAL — грозит переполнением диска primary.
Решение: sudo mitdev pg remove-replica <ip> или удалить слот вручную (pg_drop_replication_slot).
Симптом: приложение получает permission denied при подключении к базе.
Причина: доступ этому серверу приложений открыт не на всех узлах — после failover новый primary его не пускает.
Решение: sudo mitdev pg allow <ip приложения> на каждом узле кластера.
Симптом: после ручного pg promote при живой сети — два primary.
Причина: повысили реплику, не убедившись в смерти старого primary → split-brain.
Решение: promote только при реальной аварии; ожившый старый primary с меньшим timeline сам понизится (fencing), но при равном timeline разрешайте вручную.
Безопасность и продакшен
- 5432 наружу не открывается. Доступ выдаётся поимённо (
add-replica,allow) по /32, толькоhostssl. Проверьтеmitdev net bind postgresql private— база не должна быть видна из интернета. - Пароль репликации лежит в
/root/.mitdev-credentials(root-only) и в~postgres/.mitdev-repl-pass(0600). Не копируйте его в общедоступные места. - sudoers watchdog намеренно узкий — только
systemctl … postgresql. Не расширяйте. - Нечётное число узлов (3 или 5). Два — не HA.
- Синхронной репликации по умолчанию нет — при failover теряются последние миллисекунды незафиксированных на реплике записей (RPO ≈ 0, но не строгий ноль). Для строгих гарантий настройте
synchronous_commit/synchronous_standby_namesвручную, ценой роста задержки записи. - Failover не заменяет бэкапы.
DROP TABLEреплицируется мгновенно. Держите бэкапы и проверяйте восстановление. - Проведите учения — «убить primary» и «оживить старый primary» на стенде, измерьте реальный RTO. Подробности — ../HA.md.
Откат и удаление
Автоматический откат при сбое установки: отложенный каталог данных возвращается на место, удаляются drop-in, hba-записи, правила UFW, watchdog, эндпоинт роли.
Снять кластерный оверлей (PostgreSQL остаётся работать автономно):
sudo mitdev remove pgcluster # = mitdev pg cluster-remove
Удаляет watchdog, эндпоинт роли, конфиг репликации, mitdev-записи pg_hba, правила файрвола, файл пароля. Данные и сама СУБД не трогаются.
Ролевое удаление с --purge:
sudo mitdev remove pgcluster --purge
- на replica — её база это физическая копия primary, поэтому
--purgeстирает реплицированные данные и пересоздаёт пустой кластер (узел готов к переустановке репликой или к автономной работе); - на primary — удаляет роль репликации
replicatorи все слотыmitdev_*(чистит зависший слот, оставшийся от отпавшей реплики).
Убрать одну реплику из кластера, не трогая остальные (со стороны primary):
sudo mitdev pg remove-replica <ip> # слот + pg_hba + UFW этого IP
Затем на самом сервере-реплике: mitdev remove pgcluster --purge (стереть её БД) или mitdev remove postgresql --purge (снести PostgreSQL целиком).
См. также
- 19-pg-vip.md — виртуальный IP: единая строка подключения в одной L2-сети
- ../HA.md — отказоустойчивость без Kubernetes, учения, чек-лист продакшена
- ../MESH.md — приватная сеть (WireGuard), без которой кластер небезопасен
- ../COMMANDS.md — полный справочник
mitdev pgи сценарии развёртывания - 12-postgresql.md — базовая установка PostgreSQL (зависимость)
- ../TROUBLESHOOTING.md — разбор конкретных аварий