Patroni - настройка кластера на проекте EDIR

Patroni - это менеджер кластера, используемый для настройки и автоматизации развертывания и обслуживания кластеров PostgreSQL HA (High Availability). Он использует распределенные хранилища конфигурации, такие как etcd, consul, ZooKeeper или Kubernetes для максимальной доступности.

В этом руководстве мы будем использовать три локальных экземпляра etcd и три локальных экземпляра patroni. ОС на виртуалках Alt Linux 8.

Список ВМ - участников кластера:

172.29.11.197   patroni1
172.29.11.198   patroni2
172.29.11.199   patroni3

A simple Patroni cluster based on etcd, managing two highly-available PostgreSQL instances

Requirements

Ubuntu/Debian

apt update
apt install --no-install-recommends postgresql-11 postgresql-client-11
apt install etcd
apt install patroni

Alt Linux

#!/bin/bash
sudo apt-get -y install postgresql11-server etcd
sudo apt-get -y install python3-module-psycopg2
sudo apt-get -y install python3-module-pip python-module-pip
sudo pip3 install psycopg2>=2.5.4
sudo apt-get -y install python3-module-setuptools
sudo pip install psycopg2-binary
sudo pip3 install patroni[etcd]

Изменим параметр ядра для max_connections increase

echo "kernel.shmmax = 100663296" >> /etc/sysctl.conf; sysctl -p

etcd

Инициализация кластера

Кластер etcd будет развёрнут на всех машинах стенда.

Построение кластера начинаем с первого узла - partoni1

Создаем файл конфигурации /etc/etcd/etcd.conf

#[Member]
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="core"
ETCD_HEARTBEAT_INTERVAL="1000"
ETCD_ELECTION_TIMEOUT="5000"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://patroni1:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://patroni1:2379"
ETCD_INITIAL_CLUSTER="core=http://patroni1:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-example"
ETCD_INITIAL_CLUSTER_STATE="new"

Справка по параметрам:

  1. ETCD_DATA_DIR - указывает расположение каталога данных кластера
  2. ETCD_LISTEN_PEER_URLS - задаёт схему и точку подключения для остальных узлов кластера, по шаблону scheme://IP:port. Схема может быть http, https. Альтернатива, unix:// или unixs:// для юникс сокетов. Если в качестве IP адреса указано 0.0.0.0, то указанный порт будет прослушиваться на всех интерфейсах.
  3. ETCD_LISTEN_CLIENT_URLS - задаёт схему и точку подключения для клиентов кластера. В остальном совпадает с ETCD_LISTEN_PEER_URLS.
  4. ETCD_NAME - человекочитаемое имя этого узла кластера. Должно быть уникально в кластере. Для первого узла может быть любым. Для последующих должно совпадать с именем, указанным при добавлении узла.
  5. ETCD_HEARTBEAT_INTERVAL - время в миллисекудах, между рассылками лидером оповещений о том, что он всё ещё лидер. Рекомендуется задавать с учётом сетевой задержки между узлами кластера.
  6. ETCD_ELECTION_TIMEOUT - время в миллисекундах, которое проходит между последним принятым оповещением от лидера кластера, до попытки захватить роль лидера на ведомом узле. Рекомендуется задавать его в несколько раз большим, чем ETCD_HEARTBEAT_INTERVAL. Более подробно о этих параметрах можно прочесть в документации.
  7. ETCD_INITIAL_ADVERTISE_PEER_URLS - Список равноправных URL-адресов, по которым его могут найти остальные узлы кластера. Эти адреса используются для передачи данных по кластеру. По крайней мере, один из этих адресов должен быть маршрутизируемым для всех членов кластера. Могут содержать доменные имена. Используется только при первом запуске нового узла кластера.
  8. ETCD_ADVERTISE_CLIENT_URLS - Список равноправных URL-адресов, по которым его могут найти остальные узлы кластера. Эти адреса используются для передачи данных по кластеру. По крайней мере, один из этих адресов должен быть маршрутизируемым для всех членов кластера. Могут содержать доменные имена.
  9. ETCD_INITIAL_CLUSTER - Список узлов кластера на момент запуска. Используется только при первом запуске нового узла кластера.
  10. ETCD_INITIAL_CLUSTER_TOKEN - Токен кластера. Должен совпадать на всех узлах кластера. Используется только при первом запуске нового узла кластера.
  11. ETCD_INITIAL_CLUSTER_STATE - может принимать два значения “new” и “existing”. Значение “new” используется при первом запуске первого узла в кластере. При значении “existing”, узел при старте будет пытаться установить связь с остальными узлами кластера.

Добавляем сопоставление IP-адреса и имена хостов на каждой из трех ВМ:

echo -e "172.29.11.197\tpatroni1\n172.29.11.198\tpatroni2\n172.29.11.199\tpatroni3" >> /etc/hosts

Запускаем сервис etcd на patroni1:

systemctl start etcd

Проверяем, что демон запущен и находится в состоянии “active”.

Посмотрим список членов кластера - так пока только один узел - patroni1

# etcdctl member list
e630a6438e488ddc, started, core, http://patroni1:2380, http://patroni1:2379, false

Добавление нового члена кластера

Добавление нового узла в кластер etcd происходит в два этапа. На первом этапе кластер предупреждается о появлении нового узла. На втором запускается сам новый узел. Следующие действия необходимо последовательно выполнить на всех оставшихся серверах стенда. Для примера будет использоваться patroni2

Создаем файл конфигурации второго члена кластера на ВМ patroni2:

#[Member]
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="patroni2"
ETCD_HEARTBEAT_INTERVAL="1000"
ETCD_ELECTION_TIMEOUT="5000"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://patroni2:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://patroni2:2379"
ETCD_INITIAL_CLUSTER="core=http://patroni1:2380,patroni2=http://patroni2:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-example"
ETCD_INITIAL_CLUSTER_STATE="existing"

Добавляем его:

# etcdctl member add patroni2 --peer-urls=http://patroni2:2380
Member b76a9d1b01d7d4e9 added to cluster bafb2cc87b631fb0

ETCD_NAME="patroni2"
ETCD_INITIAL_CLUSTER="patroni2=http://patroni2:2380,core=http://patroni1:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://patroni2:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

Запускаем службу etcd и смотрим обновленный список членов кластера:

systemctl start etcd.service
systemctl start etcd
etcdctl member list
b76a9d1b01d7d4e9, started, patroni2, http://patroni2:2380, http://patroni2:2379, false
e630a6438e488ddc, started, core, http://patroni1:2380, http://patroni1:2379, false

Если все хорошо, повторяем операцию на 3-ем узле (patroni3)

#[Member]
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="patroni3"
ETCD_HEARTBEAT_INTERVAL="1000"
ETCD_ELECTION_TIMEOUT="5000"
#[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://patroni3:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://patroni3:2379"
ETCD_INITIAL_CLUSTER="core=http://patroni1:2380,patroni2=http://patroni2:2380,patroni3=http://patroni3:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-example"
ETCD_INITIAL_CLUSTER_STATE="existing"

Добавляем, запускаем, проверяем:

rm -Rf /var/lib/etcd/default.etcd
etcdctl member add patroni3 --peer-urls=http://patroni3:2380
systemctl start etcd
etcdctl member list
a4e2c463094b9e35, started, patroni3, http://patroni3:2380, http://patroni3:2379, false
b76a9d1b01d7d4e9, started, patroni2, http://patroni2:2380, http://patroni2:2379, false
e630a6438e488ddc, started, core, http://patroni1:2380, http://patroni1:2379, false

Закрепление кластера

После установки и успешного запуска etcd на всех серверах, следует привести содержание файла /etc/etcd/etcd.conf в окончательное состояние. Для этого необходимо изменить следующие параметры в этом файле на всех серверах.

параметр “ETCD_INITIAL_CLUSTER” должен быть одинаковым на всех узлах

ETCD_INITIAL_CLUSTER="core=http://patroni1:2380,patroni2=http://patroni2:2380,patroni3=http://patroni3:2380"

параметр “ETCD_INITIAL_CLUSTER_STATE” следует установить в значение “existing”

После этого необхолимо сделать рестарт службы etcd на всех узлах, кроме 3-го (на нем не меняли конфигурацию).

Авторизация

Добавим авторизацию по логину и паролю при обращениях на клиентский интерфейс etcd.

Создаём пользователя “root”

etcdctl user add root
 New password: bVeSXiUx
 User root creatd

Назначаем роль “root”.

# etcdctl user grant-role root root
Role root is granted to user root

Проверим:

etcdctl user get root
User: root
Roles: root

Включаем проверку логина и пароля

etcdctl auth enable
Authentication Enabled

Проверяем, что изменения вступили в силу

etcdctl user get root
Insufficient credentials
etcdctl --user root user get root
Password:
User: root
Roles: root

Patroni

Перед запуском кластера Patroni убедитесь, что кластер PostgreSQL по умолчанию остановлен и отключен.

YML

Создаем файл описание сервиса patroni - /etc/patroni/patroni.yml

[root@dro-super-dev-db-master-cluster patroni]# clear; cat patroni.yml
name: patroni1
#namespace: /db/
scope: edir_cluster

restapi:
    listen: 0.0.0.0:8008
    connect_address: patroni1:8008
    authentication:
        username: patroni
        password: patroni

etcd:
    hosts: patroni1:2379,patroni2:2379,patroni3:2379
    username: root
    password: bVeSXiUx

bootstrap:
    dcs:
        ttl: 100
        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"
                wal_keep_segments: 5120
                max_wal_senders: 5
                max_replication_slots: 5
                checkpoint_timeout: 30
    initdb:
    - auth-host: md5
    - auth-local: peer
    - encoding: UTF8
    - data-checksums
    - locale: ru_RU.UTF-8
    pg_hba:
    - host replication postgres ::1/128 md5
    - host replication postgres 127.0.0.1/8 md5
    - host replication postgres 172.29.11.197/24 md5
    - host replication postgres 172.29.11.198/24 md5
    - host replication postgres 172.29.11.199/24 md5
    - host all all 0.0.0.0/0 md5
    - host replication replicator samenet md5
    - host replication all 127.0.0.1/32 md5
    - host replication all ::1/128 md5
    users:
        admin:
        password: admin
        options:
            - superuser

postgresql:
    listen: 0.0.0.0:5432
    connect_address: patroni1:5432
    #conf_dir: /etc/postgresql/11.9/main
    bin_dir: /usr/bin/
    data_dir: /data/patroni
    pgpass: /tmp/pgpass
    authentication:
        superuser:
            username: postgres
            password: N29b6R4HKO1I
        replication:
            username: replicator
            password: DDqNmBRyjyik
        rewind:
            username: rewind_user
            password: kI3Sowj9KHZz
    parameters:
        unix_socket_directories: '/var/lib/pgsql/'

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


Важно!
unix_socket_directories: ‘/var/lib/pgsql/’ нельзя ставить /var/run/postgresql, т.к. не хватит прав на создание каталога.

В параметре scope содержится имя кластера (postgres)

В приведённом примере настроек, есть ряд параметров, влияющих на выполнение переключения на резервный сервер.

  • maximum_lag_on_failover - максимальное количество байт, на которые может отставать резервный сервер от ведущего, участвующий в выборах нового лидера.
  • master_start_timeout - задержка в секундах, между обнаружением аварийной ситуации и началом отработки переключения на резервный сервер. По умолчанию 300 секунд. Если задано 0, то переключение начнётся немедленно.

При использовании асинхронной репликации (как в приведённом примере) это может привести к потере последних транзакций.

Максимальное время переключения на реплику равно “loop_wait” + “master_start_timeout” + “loop_wait”.
Если “master_start_timeout” установленно в 0, то это время становится равно значению параметра “loop_wait”. Подробнее о возможных вариантах репликации можно прочитать в документации https://patroni.readthedocs.io/en/latest/replication_modes.html#replication-modes

Unit

Создаем юнит сервиса:

# cat /etc/systemd/system/patroni.service
[Unit]
Description=Runners to orchestrate a high-availability PostgreSQL
After=syslog.target network.target
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=no
[Install]
WantedBy=multi-user.target

Master node bootstrap

Создаем нужные каталоги и запускаем сервис:

mkdir -p /etc/postgresql/11.9/main /var/run/postgresql/ /etc/patroni /data/patroni
chown -R postgres: /etc/postgresql /var/run/postgresql/ /etc/patroni /data/patroni
chmod 750 /data/patroni
systemctl enable patroni; systemctl start patroni

Смотрим статус порта 5432:

netstat -tulpn | grep 5432
LISTEN    0         224                0.0.0.0:5432             0.0.0.0:*

В каталоге /data/patroni должны появиться структура баз данных и настройки службы.

Состояние сервиса будет примерно таким:

 patroni.service - Runners to orchestrate a high-availability PostgreSQL
   Loaded: loaded (/etc/systemd/system/patroni.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2021-07-29 15:04:52 MSK; 21min ago
 Main PID: 182875 (patroni)
    Tasks: 13 (limit: 4707)
   Memory: 77.6M
   CGroup: /system.slice/patroni.service
           ├─182875 /usr/bin/python3 /usr/local/bin/patroni /etc/patroni/patroni.yml
           ├─182894 /usr/bin/postgres -D /data/patroni --config-file=/data/patroni/postgresql.conf --listen_addresses=0.0.0.0 --port=5432 --cluster_name=postgres --wal_level=replica --hot_standby=on --m>
           ├─182897 postgres: postgres: checkpointer
           ├─182898 postgres: postgres: background writer
           ├─182899 postgres: postgres: stats collector
           ├─182903 postgres: postgres: postgres postgres 127.0.0.1(59188) idle
           ├─182908 postgres: postgres: walwriter
           ├─182909 postgres: postgres: autovacuum launcher
           └─182910 postgres: postgres: logical replication launcher

июл 29 15:24:54 dro-super-dev-db-master-cluster.super-dev.lan patroni[182875]: 2021-07-29 15:24:54,575 INFO: no action. I am (patroni1) the leader with the lock

Посмотрим список членов кластера:

# patronictl -d etcd://localhost:2379 list postgres
+ Cluster: postgres (uninitialized) +-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+------+------+-------+----+-----------+
| patroni1 | patroni1 | Leader  | running |  4 |           |
+--------+------+------+-------+----+-----------+

Add new cluster members

Для добавления нового узла в кластер Patroni, выполняются действия из раздела Master node bootstrap. Различия будут заключаться только в файле /etc/patroni/patroni.yml - все изменения заключаются в замене “patroni1” на имя текущего сервера.

После запуска Patroni на резервном сервере происходит следующее:

Patroni подключается к кластеру на мастер-ноде patroni1
Создаётся новый кластер PostgreSQL и заполняется данными с patroni1
Новый член кластера PostgreSQL переводится в “slave mode”

В результате в кластере Patroni должно быть три узла:

# patronictl -d etcd://patroni1:2379 list edir_cluster
+ Cluster: edir_cluster (6990307902978054488) -+-----------+
| Member   | Host     | Role    | State   | TL | Lag in MB |
+----------+----------+---------+---------+----+-----------+
| patroni1 | patroni1 | Leader  | running |  4 |           |
| patroni2 | patroni2 | Replica | running |  4 |         0 |
| patroni3 | patroni3 | Replica | running |  4 |         0 |
+----------+----------+---------+---------+----+-----------+

Чтобы использовать короткий синтаксис команды patronictl, лучше создать файл с настройками кластера для нужного пользователя: ~/.config/patroni/patronictl.yaml

# cat ~/.config/patroni/patronictl.yaml
dcs_api:
    etcd://localhost:2379
#namespace: /db/
scope: edir_cluster

authentication:
    username: patroni
    password: patroni

Теперь можно смотреть список членов кластера краткой командой:

patronictl list

Пробное подключение

Теперь можно подключиться к базе данных, используя порт, на котором в настоящее время находится лидер вашего кластера:

psql -p 5432 -h 127.0.0.1 -U replicator postgres
postgres=> show  max_connections;
 max_connections
-----------------
 300
(1 строка)

Создание тестовой БД

От имени пользователя postgres запускаем командный процессор psql и в нем выполняем команды:

postgres=>
CREATE USER sani WITH PASSWORD 'Passw@rd';
CREATE DATABASE customers ENCODING 'UTF8' owner sani;
GRANT ALL PRIVILEGES ON DATABASE customers TO sani;

Далее создаем описание таблицы accounts

cat >> accounts.sql << EOF
CREATE TABLE accounts (
        user_id serial PRIMARY KEY,
        username VARCHAR ( 50 ) UNIQUE NOT NULL,
        password VARCHAR ( 50 ) NOT NULL,
        email VARCHAR ( 255 ) UNIQUE NOT NULL,
        created_on TIMESTAMP NOT NULL
);
EOF

Запускаем создание таблицы accounts в БД customers:

psql -p 5432 -h 127.0.0.1 -U sani customers < accounts.sql

Keepalived

Демон keepalived используется службой haproxy для организации высокой доступности сервиса. Ставим на всех трех узлах кластера.

sudo apt-get update && sudo apt-get install -y keepalived

keepalived.conf

Настраиваем /etc/keepalived/keepalived.conf

global_defs {
    router_id patroni1
}

vrrp_script haproxy {
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance ADMIN {
    virtual_router_id 50
    advert_int 1
    priority 101
    state MASTER
    interface ens192
    virtual_ipaddress {
        172.29.11.200/24
    }
    track_script {
        haproxy
    }
}

Здесь необходимо заменить имя интерфейса (ens192) на свое реальное.

router_id patroni1 - будет, соответственно patroni2 - на втором узле, patroni3 - на третьем узле

После того, как нак всех узлах будет настроен keepalived.conf, разрешаем и стартуем службу:

systemctl enable keepalived; systemctl start keepalived

Haproxy

Ставим haproxy:

sudo apt-get update; sudo apt-get -y install haproxy

Разрешаем привязку нелокальных IP-адресов

echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf; sysctl -p

haproxy.conf

Создаем файл конфигурации /etc/haproxy/haproxy.conf

global
    log 127.0.0.1 local2
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user _haproxy
    group _haproxy
    daemon
    maxconn 300

defaults
    log global
    mode tcp
    retries 2
    mode http
    option http-server-close
    timeout client 50s
    timeout server 50s
    timeout connect 8s
    timeout check 5s

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /

frontend patroni_front
    bind *:5433
    default_backend patroni_backend

backend patroni_backend
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 2

    server patroni1 patroni1:5432 maxconn 100 check port 8008
    server patroni2 patroni2:5432 maxconn 100 check port 8008
    server patroni3 patroni3:5432 maxconn 100 check port 8008

В соответствии с этой конфигурацией haproxy слушает порт 5433, и отправляет трафик с него на мастер сервер.

Проверка статуса происходит с интервалом в 1 секунду, для перевода сервера в даун требуется 3 неудачных ответа (код 500), для переключения сервера назад -2 удачных ответа (с кодом 200). В любой момент времени можно обратиться непосредственно на любой haproxy, и он корректно запроксирует трафик на мастер сервер.

Также в комплекте с Patroni есть шаблон для настройки демона confd, и пример его интеграции с etcd, что позволяет динамически менять конфиг haproxy при удалении или добавлении новых серверов. Посмотреть можно здесь: https://github.com/zalando/patroni/blob/master/extras/confd

Корректируем права и запускаем:

chown -R _haproxy:_haproxy /etc/haproxy
systemctl enable haproxy; systemctl start haproxy

Смотирим состояние и статистику кластера:

haproxy-stat

Cluster usage

После настройки high avalibility сервиса, мы будем подключаться к базе через виртуальный IP-адрес

psql -U replicator -h 172.29.11.200 -p 5433 postgres

В ответ на запрос пароля вводим пароль из файла /etc/patroni/patroni.yml: N29b6R4HKO1I

Добавим тестовую базу данных:

CREATE USER sani WITH PASSWORD 'Passw@rd';
create database customers encoding 'utf8' owner sani;

Переподключимся к новой БД:

psql -U sani -h 172.29.11.200 -p 5433 customers

Смена настроек кластера

Смена настроек делается синхронно на всех серверах кластера, где установлен patroni, корректировкой файла /etc/patroni/patroni.yml в секции “parameters:”

После смены запускаем скрипт:

for node in patroni1 patroni2 patroni3; do systemctl reload patroni $node; systemctl restart patroni $node; done

Выполнение запроса в БД

Найдем максимальный номер клиента:

patronictl query --format yaml -U sani --password -d customers edir_cluster -c "select max(user_id) as max_user_id from accounts"
- max_user_id: 14733

В данном запросе еще можно задать через ключ “-m” конкретный узел кластера, или через ключ “-r” - роль узла (master,replica,any).

Таким образом, для выполнения запроса на вставку данных необходим такой запрос:

patronictl query -U sani --password -d customers edir_cluster -r master -c "INSERT into accounts (user_id, username, password, email) VALUES ('14734', 'timofey', 'T1m0feY_pass', 'timofey@yahoo.com');"

Проверим:

customers=> select * from accounts where user_id in (select max(user_id) from accounts);
 user_id | username |   password   |       email       |         created_on
---------+----------+--------------+-------------------+----------------------------
   14734 | timofey  | T1m0feY_pass | timofey@yahoo.com | 2021-08-03 10:45:39.160541
(1 строка)

Тестовый скрипт

Скрипт заполняет случайными данными таблицу accounts в БД customers

#!/bin/bash
## first (start) user_id
FID=$1

function usage() {
echo "Usage: "$0" start user_id"
}

if [[ $# -ne 1 ]]; then usage
   else
         for (( count=$FID; count<20000; count++ )); do
            echo $count
            echo "INSERT into accounts (user_id, username, password, email) VALUES ('$count', '$(openssl rand -base64 6)', '$(openssl rand -base64 15)', '$(openssl rand -base64 6)@yahoo.com');" |  psql -U sani -h 172.29.11.200 -p 5433 customers
        done
fi

Кроме того, это скрипт демонстрирует, что запись данных проходит “через два раза”, т.к. установленный нами механизм балансировки haproxy (roundrobin) направляет запрос на случайный узел. Когда запрос insert попадает на Leader (master) то он успешно отрабатывает, а когда на Replica (slave) - то запрос отвергается:

ОШИБКА:  в транзакции в режиме "только чтение" нельзя выполнить INSERT

Таким образом бэкенд работы с кластером должен содержать какой-то дополнительный механизм, распределяющий запросы на чтение и запись соответственно ролям членов кластера.
Одним из простых способов может быть использование механизма балансировки по заголовкам - HDR.

В этом механизме сервер выбирается на основе заголовка HTTP запроса. Если искомое значение отсутствует в заголовке, то используется принцип Round Robin.
Например, при такой записи в /etc/haproxy/haproxy.cfg:

balance hdr(User-Agent: Mozilla/5.0)

HAProxy будет искать в заголовке запроса запись User-Agent: Mozilla/5.0 и направлять его на заданный сервер.

В рекомендациях от Google Cloud (https://cloud.google.com/architecture/architectures-high-availability-postgresql-clusters-compute-engine#query_routing_2)
четко сказано, что pg_auto_failure не предоставляет никаких возможностей маршрутизации запросов на стороне сервера.
Вместо этого pg_auto_failure полагается на маршрутизацию запросов на стороне клиента, которая использует официальный драйвер клиента PostgreSQL libpq.
Когда вы определяете URI подключения, драйвер может принимать несколько хостов в своем пареметре host, разделенных запятой.

Подробнее можно посмотреть здесь: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS

Клиентская библиотека, которую использует ваше приложение, должна либо заключать в оболочку libpq, либо реализовывать возможность предоставления нескольких хостов для архитектуры для поддержки полностью автоматизированного аварийного переключения.

Схема компонентов отказоустойчивого кластера на базе ПО patroni

architectures-patroni

Troubleshooting

localhost:5432 - не принимает подключения

Остановить службу patroni и удалить все содержимое каталога /data/patroni/

После запуска службы все будет хорошо.

Sources

  1. Построение отказоустойчивого кластера PostgreSQL. Настройка внешней синхронизации на PostgreSQL для механизма копий баз данных: https://its.1c.ru/db/metod8dev/content/5971/hdoc
  2. Patroni - setting up a highly available postgresql cluster: https://www.cybertec-postgresql.com/en/patroni-setting-up-a-highly-available-postgresql-cluster/
  3. Управление высокодоступными PostgreSQL кластерами с помощью Patroni. А.Клюкин, А.Кукушкин: https://habr.com/ru/post/504044/
  4. Официальное руководство по patroni REST API: https://patroni.readthedocs.io/en/latest/rest_api.html
  5. Исходный код и документация: https://github.com/zalando/patroni
  6. Конфигурирование haproxy: http://cbonte.github.io/haproxy-dconv/2.5/configuration.html
  7. Доклад по внедрению Partoni в компании Miro на видеоконференции Siberia 2019: https://www.youtube.com/watch?v=ZvLw9PU--_w
  8. Доклад Ильи Космодемьянского на тему “Зачем нужен мультимастер” https://www.youtube.com/watch?v=bcRo--JlR14

last update: 13.08.2022