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

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

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

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

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

Типичная архитектура: 3 ноды PostgreSQL + Patroni, 3 ноды etcd (могут быть на тех же серверах), HAProxy или PgBouncer для маршрутизации клиентских подключений.

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

Etcd — распределённое key-value хранилище, которое Patroni использует для leader election и хранения состояния кластера. Для отказоустойчивости etcd нужно минимум 3 ноды.

Установка и настройка 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 нодах.

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

На каждом из 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 секунд), затем запустите на 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 -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 напрямую.

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

Просмотр текущей конфигурации:

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

Изменение параметров:

# Изменить параметр без перезапуска
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 на новом сервере и указать ту же конфигурацию кластера.

# На новом сервере (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 (могут быть на тех же серверах). Для etcd нужно нечётное число нод (3 или 5) для обеспечения кворума. 3-нодный кластер переживёт падение 1 ноды, 5-нодный — падение 2 нод. Для production рекомендуется 3 ноды 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]. Выбор DCS зависит от того, что уже есть в инфраструктуре. Etcd — самый популярный выбор для standalone-кластеров, Consul — если уже используется для service discovery.

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

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

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

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

загрузка...