Patroni: автоматический failover для PostgreSQL кластера — АйТи Фреш

Patroni: автоматический failover для PostgreSQL кластера

Зачем нужен Patroni для PostgreSQL

Потоковая репликация PostgreSQL из коробки умеет копировать данные на реплики — и на этом её возможности заканчиваются. Если мастер упал, реплики живут, отвечают на чтение, но записать что-то невозможно. Кластер висит в read-only до тех пор, пока DBA вручную не переключит роли. В три часа ночи это особенно «весело».

Patroni — Python-демон, который берёт на себя всю рутину управления PostgreSQL-кластером:

  • Автоматический failover — при падении мастера Patroni сам промотирует наиболее актуальную реплику, обычно за 10–30 секунд
  • Distributed consensus — работает через etcd, ZooKeeper или Consul, что исключает split-brain на уровне архитектуры
  • Инициализация реплик — новая реплика поднимается автоматически через pg_basebackup, без ручной возни с резервными копиями
  • Управление конфигурацией — параметры PostgreSQL хранятся централизованно в DCS, а не размазаны по конфигам на каждом сервере
  • REST API — HTTP-эндпоинты для мониторинга и управления кластером, удобно встраивается в любой health check

На практике схема выглядит так: 3 ноды PostgreSQL + Patroni, рядом 3 ноды etcd — их можно поднять на тех же серверах, чтобы сэкономить. Перед кластером — HAProxy или PgBouncer, которые маршрутизируют клиентские подключения на текущего мастера.

Установка etcd кластера

Etcd — распределённое key-value хранилище. Patroni использует его для leader election и хранения состояния кластера. Почему минимум 3 ноды? Потому что etcd работает на кворуме: при 2 нодах потеря одной означает потерю кворума и остановку всего кластера. Три ноды — минимально рабочая конфигурация.

Установка и настройка etcd

Ставим etcd на 3 серверах: node1 — 10.0.0.1, node2 — 10.0.0.2, node3 — 10.0.0.3:

sudo apt install etcd

Конфигурация на node1 (/etc/default/etcd):

ETCD_NAME="etcd1"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://10.0.0.1:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.0.0.1:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.1:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.1:2379"
ETCD_INITIAL_CLUSTER="etcd1=http://10.0.0.1:2380,etcd2=http://10.0.0.2:2380,etcd3=http://10.0.0.3:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="patroni-etcd-cluster"

Для node2 и node3 делаем то же самое, меняя ETCD_NAME и IP-адреса под каждый сервер. Затем запускаем на всех нодах:

sudo systemctl enable --now etcd

Проверяем, что кластер собрался:

etcdctl member list
etcdctl endpoint health --cluster

Установка и настройка Patroni

Теперь ставим Patroni и PostgreSQL — на всех 3 нодах, без исключений.

Установка компонентов

На каждом из трёх серверов выполняем:

# PostgreSQL
sudo apt install postgresql-16 postgresql-client-16

# Остановите автозапуск PostgreSQL — Patroni будет управлять им
sudo systemctl stop postgresql
sudo systemctl disable postgresql

# Patroni
sudo apt install python3-pip python3-psycopg2
sudo pip3 install patroni[etcd]

# Проверка
patroni --version

Конфигурация Patroni

Создаём конфигурацию на node1 (/etc/patroni/config.yml):

scope: pg-cluster
namespace: /service/
name: node1

restapi:
  listen: 10.0.0.1:8008
  connect_address: 10.0.0.1:8008

etcd:
  hosts: 10.0.0.1:2379,10.0.0.2:2379,10.0.0.3:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_keep_size: 1GB
        archive_mode: "on"
        archive_command: '/bin/true'
        shared_preload_libraries: 'pg_stat_statements'
        max_connections: 200
        shared_buffers: 2GB
        effective_cache_size: 6GB
        work_mem: 32MB
  initdb:
    - encoding: UTF8
    - data-checksums
  pg_hba:
    - host replication replicator 10.0.0.0/24 md5
    - host all all 10.0.0.0/24 md5
    - host all all 0.0.0.0/0 md5
  users:
    admin:
      password: 'AdminSecretPass'
      options:
        - createrole
        - createdb
    replicator:
      password: 'ReplicaSecretPass'
      options:
        - replication

postgresql:
  listen: 10.0.0.1:5432
  connect_address: 10.0.0.1:5432
  data_dir: /var/lib/postgresql/16/main
  bin_dir: /usr/lib/postgresql/16/bin
  pgpass: /tmp/pgpass
  authentication:
    replication:
      username: replicator
      password: 'ReplicaSecretPass'
    superuser:
      username: postgres
      password: 'PostgresSecretPass'
  parameters:
    unix_socket_directories: '/var/run/postgresql'

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false

На node2 и node3 меняем name и IP-адреса в секциях restapi и postgresql — всё остальное остаётся одинаковым.

Запуск кластера и проверка репликации

Создаём systemd-юнит и запускаем Patroni на всех нодах.

Systemd-юнит для Patroni

Создаём файл /etc/systemd/system/patroni.service:

[Unit]
Description=Patroni - PostgreSQL HA
After=syslog.target network.target etcd.service

[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/config.yml
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=no

[Install]
WantedBy=multi-user.target

Сначала запускаем на первой ноде — она инициализирует кластер и станет мастером:

sudo systemctl daemon-reload
sudo systemctl enable --now patroni

Ждём 10–30 секунд, пока node1 поднимется, и только потом запускаем на node2 и node3. Patroni сам создаст реплики через pg_basebackup — ничего дополнительно делать не нужно.

Проверка состояния кластера

Для управления кластером используем patronictl:

# Состояние кластера
patronictl -c /etc/patroni/config.yml list

Вывод:

+ Cluster: pg-cluster (7289456123456) --+----+-----------+
| Member | Host     | Role    | State   | TL | Lag in MB |
+--------+----------+---------+---------+----+-----------+
| node1  | 10.0.0.1 | Leader  | running |  1 |           |
| node2  | 10.0.0.2 | Replica | running |  1 |       0.0 |
| node3  | 10.0.0.3 | Replica | running |  1 |       0.0 |
+--------+----------+---------+---------+----+-----------+

Репликацию можно проверить прямо через SQL:

sudo -u postgres psql -c "SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;"

Настройка HAProxy для маршрутизации

Подключать клиентов напрямую к нодам PostgreSQL — плохая идея. После failover адрес мастера меняется, и все соединения рвутся. HAProxy решает это: он проксирует трафик на текущего мастера и сам разбирается, кто сейчас главный.

Конфигурация HAProxy

HAProxy ставим либо на отдельный сервер, либо на каждый app-сервер — второй вариант надёжнее:

sudo apt install haproxy

Конфигурация /etc/haproxy/haproxy.cfg:

global
    maxconn 1000
    log /dev/log local0

defaults
    log     global
    mode    tcp
    retries 3
    timeout client  30m
    timeout connect 4s
    timeout server  30m
    timeout check   5s

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /
    stats refresh 10s

# Подключения на запись (мастер)
listen postgresql-primary
    bind *:5432
    option httpchk OPTIONS /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server node1 10.0.0.1:5432 maxconn 100 check port 8008
    server node2 10.0.0.2:5432 maxconn 100 check port 8008
    server node3 10.0.0.3:5432 maxconn 100 check port 8008

# Подключения на чтение (реплики)
listen postgresql-replicas
    bind *:5433
    balance roundrobin
    option httpchk OPTIONS /replica
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server node1 10.0.0.1:5432 maxconn 100 check port 8008
    server node2 10.0.0.2:5432 maxconn 100 check port 8008
    server node3 10.0.0.3:5432 maxconn 100 check port 8008

HAProxy стучится на REST API Patroni по порту 8008 и делает health check. Эндпоинт /primary отдаёт 200 только на мастере, /replica — только на репликах. Как только происходит failover, HAProxy в течение нескольких секунд сам перенаправляет трафик на нового мастера — приложение этого даже не замечает.

sudo systemctl enable --now haproxy

Тестирование failover

Проверяем автоматическое переключение — симулируем падение мастера и смотрим, что происходит.

Сценарии тестирования

Плановое переключение (switchover) — когда нужно перевести нагрузку без простоя:

# Переключить мастер на node2
patronictl -c /etc/patroni/config.yml switchover --master node1 --candidate node2 --force

# Проверить состояние
patronictl -c /etc/patroni/config.yml list

Аварийное переключение (failover) — просто останавливаем мастер и наблюдаем:

# На текущем мастере
sudo systemctl stop patroni

# Через 30 секунд (TTL) Patroni промотирует реплику
# На любой работающей ноде проверьте
patronictl -c /etc/patroni/config.yml list

Жёсткое падение — симулируем настоящий краш процесса:

# Kill PostgreSQL без graceful shutdown
sudo pkill -9 postgres

# Patroni обнаружит падение и перезапустит PG на той же ноде
# Если нода недоступна — произойдёт failover

Мониторинг через REST API — можно дёрнуть прямо из curl:

# Статус ноды
curl -s http://10.0.0.1:8008/patroni | jq

# Кто мастер?
curl -s http://10.0.0.1:8008/primary
curl -s http://10.0.0.2:8008/primary

# Для мониторинга (Zabbix/Nagios)
curl -s http://10.0.0.1:8008/health | jq '.state'

Управление конфигурацией PostgreSQL через Patroni

Patroni хранит конфигурацию PostgreSQL в DCS — то есть в etcd. Менять параметры нужно через patronictl, а не руками в postgresql.conf. Если отредактировать файл напрямую, Patroni при следующей синхронизации просто перезапишет ваши изменения.

Изменение параметров кластера

Смотрим текущую конфигурацию кластера:

patronictl -c /etc/patroni/config.yml show-config

Меняем параметры через patronictl:

# Изменить параметр без перезапуска
patronictl -c /etc/patroni/config.yml edit-config \
  --set 'postgresql.parameters.work_mem=64MB' --force

# Изменить параметр, требующий перезапуска
patronictl -c /etc/patroni/config.yml edit-config \
  --set 'postgresql.parameters.shared_buffers=4GB' --force

# Проверить, нужен ли перезапуск
patronictl -c /etc/patroni/config.yml list
# В столбце Pending restart будет "*" если нужен

# Перезапуск всех нод (сначала реплики, потом мастер)
patronictl -c /etc/patroni/config.yml restart pg-cluster --role replica --force
patronictl -c /etc/patroni/config.yml restart pg-cluster --role master --force

Добавление и удаление нод

Добавить реплику в работающий кластер — проще, чем кажется. Ставим Patroni на новый сервер, указываем ту же конфигурацию кластера, запускаем — и Patroni сам подтянет данные через pg_basebackup.

# На новом сервере (node4: 10.0.0.4)
sudo apt install postgresql-16 python3-pip
sudo pip3 install patroni[etcd]

# Создайте /etc/patroni/config.yml с name: node4 и соответствующими IP
# Patroni автоматически выполнит pg_basebackup с мастера
sudo systemctl enable --now patroni

# Проверьте, что нода присоединилась
patronictl -c /etc/patroni/config.yml list

Удаление ноды из кластера:

# Остановите Patroni
sudo systemctl stop patroni

# Удалите данные PostgreSQL
sudo rm -rf /var/lib/postgresql/16/main

# Нода автоматически пропадёт из кластера после TTL

Часто задаваемые вопросы

Split-brain при правильной настройке исключён на уровне архитектуры. Patroni использует etcd для leader election: только одна нода держит leader lock, и два мастера одновременно физически невозможны. Потеряла нода связь с etcd — сразу уходит в read-only, без вариантов. Параметр maximum_lag_on_failover не даст промотировать реплику, которая сильно отстала. Для продакшена рекомендую ещё включить watchdog: watchdog.mode: required в конфиге Patroni — это дополнительная страховка от аппаратных зависаний.

Минимальная конфигурация — 3 ноды PostgreSQL и 3 ноды etcd. Почему нечётное число? Кворум. 3-нодный кластер переживёт падение одной ноды, 5-нодный — двух. Etcd можно поднять на тех же серверах, что и PostgreSQL, но для продакшена с нагрузкой лучше разнести: конкуренция за диск между etcd и Postgres в неподходящий момент — это отдельная история. Три ноды PostgreSQL плюс etcd на отдельных серверах — оптимальный вариант для большинства задач.

PgBouncer отлично работает как отдельно, так и в связке с HAProxy. На практике чаще встречаем три схемы: (1) PgBouncer на каждой ноде с Patroni callback-скриптами для переключения; (2) PgBouncer перед HAProxy — для пулинга на входе; (3) PgBouncer прямо на app-серверах, подключённый к HAProxy. Patroni умеет вызывать callback-скрипты (on_start, on_stop, on_role_change) — именно через них и перенастраивается PgBouncer при failover. Работает надёжно, проверено не раз.

Считайте сами: TTL по умолчанию — 30 секунд, промотирование реплики занимает ещё 1–5 секунд, HAProxy переключается за 3–10 секунд. Итого реальный даунтайм — 30–45 секунд. Можно срезать TTL до 15 секунд, но тогда вырастет нагрузка на etcd и повысится риск ложных срабатываний — мы пробовали, удовольствие сомнительное. Для большинства приложений 30–45 секунд вполне терпимо.

Да, Patroni работает с etcd, Consul и ZooKeeper в качестве DCS. Если выбираете Consul — замените секцию etcd в конфиге на consul: host: 127.0.0.1:8500 и поставьте pip3 install patroni[consul]. Что выбрать? Смотрите на то, что уже крутится в инфраструктуре. Etcd — очевидный выбор для standalone-кластера с нуля. Consul берём, если он уже живёт в стеке и занимается service discovery — незачем плодить лишние сущности.

Нужна помощь с настройкой?

Специалисты АйТи Фреш возьмут внедрение и настройку на себя — 15+ лет в деле, обслуживание от 15 000 ₽/мес.

📞 Связаться с нами
#patroni postgresql#postgresql ha кластер#patroni etcd#postgresql failover#postgresql репликация#patroni настройка#высокая доступность postgresql#patroni haproxy
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Каждую неделю мы выпускаем практические гайды для руководителей IT и системных администраторов. Это не просто теория! Здесь вы найдёте всё: безопасность, 1С, миграции, резервные копии и проверенные лайфхаки из наших реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.