PostgreSQL - это мощная и очень развитая база данных, функциональная и дружелюбная. В комплект входит надежный и очень удобный механизм потоковой репликации (я писал о нем здесь). Не смотря на мощь и удобство – этот инструмент сложен в настройке и не всегда понятен, особенно, если серверов баз много. Все становится еще хуже, если у вас сложная схема репликации с каскадами (master > slave > slave of slave). Чтобы облегчить жизнь DBA в таких ситуациях – известные специалисты по консалтингу Postgres, компания 2ndQuadrant придумали repmgr – специальный инструмент для управления настройками репликации для PostgreSQL.

Repmgr может:

  • облегчить создание новых серверов
  • облегчить переключение на другой сервер (promote)
  • автоматизировать переключение на новый сервер при отказе старого (failover)
  • вести аудит событий репликации в кластере (event flow)
  • официально repmgr поддерживает мультимастер с помощью механизма bidirectional replication. Это жуткий грязный хак, и я очень не рекомендую его использовать

Repmgr требует (ограничения):

  • postgresql 9 или 10 (так как только в 9 версии появилась потоковая репликаця)
  • доступ с каждого узла кластера на каждый узел кластера по протоколам SSH (непривелигированый) и postgresql (5432)
  • одинаковую версию Postgres (только major)
  • одинаковую архитектуру сервера (вы не сможете реплицировать данные с ARM на AMD64)
  • одинаковую версию repmgr на всех узлах кластера (одинаковость должна быть полной)
  • пользователя с правами SUPERUSER, от имени которого repmgr будет проводить операции. Пароль от этого пользователя хранится в файловой системе кластера в открытом виде.

Установка

Repmgr поставляется в виде пакета и входит в стандартный для всех пользователей postgresql репозиторий PGDG. В данной статье я продемонстрирую настройку для debian, но для centos он настраивается практически так же – меняются только пути доступа к файлам. Добавим репозиторий:

sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
apt-get update

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

apt-get install postgresql-10 postgresql-10-repmgr -y

Для repmgr важно, чтобы все хосты видели друг друга по hostname. Я рекомендую иметь внутренний DNS для решения этой задачи, но если у вас по какой-то причине его нет – придется добавить имена и адреса серверов кластера в /etc/hosts. Например:

192.168.0.17    pg1.lab.office    pg1
192.168.0.18    pg2.lab.office    pg2
192.168.0.19    pg3.lab.office    pg3

Для того, что бы repmgr мог копировать базу с одного сервера на другой – пользователю repmgrd нужен доступ по ssh без ввода пароля. Для этого на каждом из серверов нужно сгенерировать ssh public key. Этот public key нужно положить на все остальные сервера кластера в файл ~/.ssh/authorized_keys. Это нужно сделать для пользователя, от которого будет запущен repmgr. Обычно это тот же пользователь, от имени которого запущен postgres. Создадим ключи на каждом сервере:

sudo -u postgres ssh-keygen -t rsa

Generating public/private rsa key pair.
Enter file in which to save the key (/var/lib/postgresql/.ssh/id_rsa):
Created directory '/var/lib/postgresql/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/lib/postgresql/.ssh/id_rsa.
Your public key has been saved in /var/lib/postgresql/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:oqFJ3/Gr8oeBgQpAsKSlImbUOLroa/YaFbJ8AxlsBng [email protected]

Публичный ключ из файла /var/lib/postgresql/.ssh/id_rsa.pub надо добавить на все остальные сервера кластера, в файл ~/.ssh/authorized_keys. Заодно поправим права:

#на сервере pg1
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCoavDuAETBMZBD0NwRvSEDYL1avCIkSLzxMG50L6b7nIeasrfv90AGjARxID9THkUXDNkdKhfRIu+WGFYxlgZ6zqPQCyZyQvKjcJr325pbo9it474LpLpeHuPrXdeMSzSilxvAKvYX/ml7L9KtOnYMDusFK1XdGeV25qcj2OSLWBY168riW5vvGWFYTCdU6q9eQ+JN2zCpoZzXKNqhh+dpItt1QiKRw84u7EtUW6U02tw1V5nmO+HGyG2A50S5/JNS7lbj/7IYAXwIgtlBrf3mzCPCIoHbjlSny/V6sp3S7QWNrxynpkI7o+oMvJq5frAEpn0syiUmtOz56Qnw67GP [email protected]" >> /var/lib/postgresql/.ssh/authorized_keys
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfl8Rg47u97kGUPf7OwF4jeGhcIxVtWEVDk1AKJt5o3Y65v5jzrjoI5F0YrboEzr+oPu5BV24M6dOI5u3ysRBX/osQI2fBlt+hotAIWXPiP8UUy9CgIdQH59h/MJcp3jPH0KYQTwF8WJDr1skUcUzKGswuofBaElm5TpME+Oz2vygXEl2vL9Pfo5kfdsk9ov58cUJNlDGtxTo/Rzw9XFRnkBimzwvem/gmdpYBFb45ulsbLVmdBcv+QTU7PQ+knqIyERboTecS8wBYoKnlCTA0LZscvyeHjKwILSl9ZFfir3CRdYtxNqx4Zk/hMphx4Bt7hn96KUXRiMf3ODpd2yp1 [email protected]" >> /var/lib/postgresql/.ssh/authorized_keys
chown postgres /var/lib/postgresql/.ssh/authorized_keys
chmod 600 /var/lib/postgresql/.ssh/authorized_keys

Убедимся, что доступ есть:

[email protected]:~# sudo -u postgres ssh pg2.lab.office -x 'w'
09:04:07 up 3 days, 23:48,  1 user,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             [email protected]   IDLE   JCPU   PCPU WHAT
logan    pts/0    192.168.0.70     14:28    1:19   0.22s  0.02s sshd: logan [priv]

Для остальных серверов делаем по аналогии.

Для того, чтобы процесс repmgr мог сам перезапускать постгрес - ему нужны соответствующие права. Чтобы их дать - создадим файл /etc/sudoers.d/repmgr и впишем туда:

Cmnd_Alias PGRE = /bin/systemctl status postgresql, \
    /bin/systemctl start postgresql, \
    /bin/systemctl stop postgresql, \
    /bin/systemctl restart postgresql, \
    /bin/systemctl reload postgresql

postgres ALL=(ALL) NOPASSWD: PGRE

Это позволит пользователю postgres перезапускать процесс сервера postgres.

Теперь нам надо настроить сам postgresql. Обязательный минимум:

  • доступность из сети (директива listen_addresses)
  • репликация (wal_level, archive_mode)
  • лимит max_wal_senders - как минимум на 1 больше количества серверов в кластере
  • hot_standby - для серверов в режиме slave.
  • права на репликацию в hba

Если вы строите кластер на debian - настройки надо скопировать на все сервера кластера. Для centos это не обязательно, так как файл настроек лежит прямо в data directory и при клонировании repmgr вытащит файлы.

Пример настроек - /etc/postgresql/10/main/postgresql.conf:

# тут приведены не все настройки, а только то, что я поменял
# часть настроек в файле закомментирована, а в части указаны другие значения.
# Пользуйтесь поиском.
listen_addresses = '*'

wal_level = replica

archive_mode = on
archive_command = 'cp %p /var/lib/pg-arch/%f'

max_wal_senders = 4

hot_standby = on

В данном примере мы не удаляем архивированные сегменты, а перемещаем их папку /var/lib/pg-arch/. Это позволит восстановить “отставший” slave. Подробнее я писал здесь. Эту папку нужно создать (владелец - postgres, права доступа - 700). Папку нужно периодически чистить – postgres сам не очищает архивы. В упомянутой выше статье вы найдете детальное описание.

Пример настроек hba. В данном примере пользователь БД называется repmgr. Служебная база repmgr - repmgrdb:

# Не менять! сломаются локальные операции!
local   all             postgres                                peer

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             all                                     md5
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5

# replication settings
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            md5
host    replication     all             ::1/128                 md5
host    repmgrdb        repmgr          192.168.0.0/24          md5
host    replication     repmgr          192.168.0.0/24          md5

#remote access to server cluster. any DB, any user, any host, password required
host    all             all             0.0.0.0/0               md5

С настройкой postgres закончили, перезапускаем сервер и создаем служебную базу и пользователя. Пароль для пользователя лучше сделать посложнее:

service postgresql status
sudo -u postgres psql

psql (10.4 (Debian 10.4-2.pgdg90+1))
Type "help" for help.

postgres=# create user repmgr with superuser;
postgres=# alter role repmgr with password 'Ahn7yaechie6hoe0av8eF0ei';
postgres=# create database repmgrdb owner repmgr;
postgres=# \q

Чтобы repmgr мог обращаться к серверу БД и ему не требовалось вводить пароль – нужно создать файл /var/lib/postgresql/.pgpass. Владельцем файла должен быть пользователь postgres, права - 0600 (иначе он игнорируется). Структура файла - IP:port:DB:user:password. * означает “любое”. К сожалению pgpass не в состоянии работать с CIDR, то есть задать адрес как 192.168.0.* можно, а 192.168.0.0/24 – нельзя. Пример файла:

*:5432:repmgrdb:repmgr:Ahn7yaechie6hoe0av8eF0ei
*:5432:replication:repmgr:Ahn7yaechie6hoe0av8eF0ei

Вторая запись нужна для самого процесса репликации.

Теперь настроим repmgr. Его настройки в debian лежат в файле /etc/repmgr.conf. Пример с комментариями:

# ID узла (сервера). В рамках кластера обязательно уникальный
node_id=1
# hostname. Остальные узлы должны иметь возможность найти этот именно по этому имени.
node_name='pg1.lab.office'
# строка подключения к БД
conninfo='host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2'
# директория с данными postgres.
data_directory='/var/lib/postgresql/10/main/'
# режим репликации. Пока что поддерживается только этот
replication_type=physical

# log file. Не забудьте создать папку для него.
log_file='/var/log/repmgr/repmgr.log'
# записывать статус каждые 5 минут (300 секунд)
log_status_interval=300

# где находятся bin-файлы postgres.
pg_bindir='/usr/lib/postgresql/10/bin/'

# не использовать password из conninfo (строки выше)
# мы храним пароль в .pgpass, это безопаснее. Потому - false
use_primary_conninfo_password=false
ssh_options='-q -o ConnectTimeout=10'

# режим failover
failover=manual
# очередность выборов мастера в случае отказа
# эта настройка и последующие применимы только если failover - auto
priority=100
reconnect_attempts=3
reconnect_interval=5
promote_command='/usr/bin/repmgr -f /etc/repmgr.conf standby promote --log-to-file'
follow_command='/usr/bin/repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n'

# команды запуска, остановки и перезапуска сервиса. Должны соответствовать тому, что мы вписали в sudo.
service_start_command = 'sudo -n /bin/systemctl start postgresql'
service_stop_command =  'sudo -n /bin/systemctl stop postgresql'
service_restart_command = 'sudo -n /bin/systemctl restart postgresql'
service_reload_command = 'sudo -n /bin/systemctl reload postgresql'

Теперь можно зарегистрировать первый сервер в кластере:

sudo -u postgres repmgr -f /etc/repmgr.conf primary register

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

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status    | Upstream       | Location | Connection string
----+----------------+---------+-----------+----------------+----------+-------------------------------------------------------------------
 1  | pg1.lab.office | primary | * running |                | default  | host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

Все ок. Теперь настроим остальные два сервера. Они настраиваются по аналогии с первым сервером. Только в конфиге repmgr.conf нужно поменять node_id, node_name и conninfo. На 2 и 3 серверах запускать postgres не нужно.

Удалим существующий data-dir и склонируем базу с мастера:

[email protected]:~# service postgresql stop
[email protected]:~# rm -rf /var/lib/postgresql/10/main/*
[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf -h pg1.lab.office -U repmgr -d repmgrdb standby clone

NOTICE: destination directory "/var/lib/postgresql/10/main/" provided
NOTICE: starting backup (using pg_basebackup)...
HINT: this may take some time; consider using the -c/--fast-checkpoint option
NOTICE: standby clone (using pg_basebackup) complete
NOTICE: you can now start your PostgreSQL server
HINT: for example: sudo -n /bin/systemctl start postgresql
HINT: after starting the server, you need to register this standby with "repmgr standby register"

Запускаем сервер, регистрируемся:

[email protected]:~# service postgresql start
[email protected]:~# sudo -u postgres /usr/bin/repmgr standby register
NOTICE: standby node "pg2.lab.office" (id: 2) successfully registered

Проверяем, что изменилось:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status    | Upstream       | Location | Connection string
----+----------------+---------+-----------+----------------+----------+-------------------------------------------------------------------
 1  | pg1.lab.office | primary | * running |                | default  | host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 2  | pg2.lab.office | standby |   running | pg1.lab.office | default  | host=pg2.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

Третий сервер запускаем по аналогии со вторым.

Failover

Итак, мастер сломался и надо переключится на slave:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status        | Upstream       | Location | Connection string
----+----------------+---------+---------------+----------------+----------+-------------------------------------------------------------------
 1  | pg1.lab.office | primary | ? unreachable |                | default  | host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 2  | pg2.lab.office | standby |   running     | pg1.lab.office | default  | host=pg2.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 3  | pg3.lab.office | standby |   running     | pg1.lab.office | default  | host=pg3.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

WARNING: following issues were detected
  - when attempting to connect to node "pg1.lab.office" (ID: 1), following error encountered :
"could not connect to server: Connection refused
    Is the server running on host "pg1.lab.office" (192.168.0.17) and accepting
    TCP/IP connections on port 5432?"
  - node "pg1.lab.office" (ID: 1) is registered as an active primary but is unreachable

Прежде чем продолжить – обязательно убедитесь, что старый мастер (pg1) отключен и не “оживет” в самый неподходящий момент. Repmgr не умеет работать с fencing-ом и вы можете потерять часть данных.

Повышаем pg2 до мастера:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf standby promote
NOTICE: promoting standby to primary
DETAIL: promoting server "pg2.lab.office" (ID: 2) using "/usr/lib/postgresql/10/bin/pg_ctl  -w -D '/var/lib/postgresql/10/main/' promote"
    waiting for server to promote.... done
server promoted
NOTICE: STANDBY PROMOTE successful
DETAIL: server "pg2.lab.office" (ID: 2) was successfully promoted to primary

Проверяем статус:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status    | Upstream       | Location | Connection string
----+----------------+---------+-----------+----------------+----------+-------------------------------------------------------------------
 1  | pg1.lab.office | primary | - failed  |                | default  | host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 2  | pg2.lab.office | primary | * running |                | default  | host=pg2.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 3  | pg3.lab.office | standby |   running | pg1.lab.office | default  | host=pg3.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

pg3 по-прежнему ждет данных от pg1. Его нужно переключить на новый мастер:

[email protected]:~# sudo -u postgres /usr/bin/repmgr standby follow -f /etc/repmgr.conf --upstream-node-id=2
NOTICE: setting node 3's primary to node 2
NOTICE: restarting server using "sudo -n /bin/systemctl restart postgresql"
NOTICE: STANDBY FOLLOW successful
DETAIL: node 3 is now attached to node 2

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

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status    | Upstream       | Location | Connection string
----+----------------+---------+-----------+----------------+----------+-------------------------------------------------------------------
 1  | pg1.lab.office | primary | - failed  |                | default  | host=pg1.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 2  | pg2.lab.office | primary | * running |                | default  | host=pg2.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 3  | pg3.lab.office | standby |   running | pg2.lab.office | default  | host=pg3.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

WARNING: following issues were detected
  - when attempting to connect to node "pg1.lab.office" (ID: 1), following error encountered :
"could not connect to server: Connection refused
    Is the server running on host "pg1.lab.office" (192.168.0.17) and accepting
    TCP/IP connections on port 5432?"

Теперь мы можем убрать старый сервер из конфигурации:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf primary unregister --node-id=1

Проверяем еще раз:

[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf cluster show
 ID | Name           | Role    | Status    | Upstream       | Location | Connection string
----+----------------+---------+-----------+----------------+----------+-------------------------------------------------------------------
 2  | pg2.lab.office | primary | * running |                | default  | host=pg2.lab.office user=repmgr dbname=repmgrdb connect_timeout=2
 3  | pg3.lab.office | standby |   running | pg2.lab.office | default  | host=pg3.lab.office user=repmgr dbname=repmgrdb connect_timeout=2

Теперь можно спокойно чинить pg1, без риска что он внезапно вернется в сеть и будет конфликт записи в slave.

Возврат мастера

Самый простой способ вернуть старый сервер - удалить его datadir и зарегистрировать заново как slave:

[email protected]:~# rm -rf /var/lib/postgresql/10/main/*
[email protected]:~# sudo -u postgres repmgr -f /etc/repmgr.conf -h pg2.lab.office -U repmgr -d repmgrdb standby clone
NOTICE: destination directory "/var/lib/postgresql/10/main/" provided
NOTICE: starting backup (using pg_basebackup)...
HINT: this may take some time; consider using the -c/--fast-checkpoint option
NOTICE: standby clone (using pg_basebackup) complete
NOTICE: you can now start your PostgreSQL server
HINT: for example: sudo -n /bin/systemctl start postgresql
HINT: after starting the server, you need to register this standby with "repmgr standby register"
[email protected]:~# service postgresql start
[email protected]:~# sudo -u postgres /usr/bin/repmgr standby register
WARNING: --upstream-node-id not supplied, assuming upstream node is primary (node ID 2)
NOTICE: standby node "pg1.lab.office" (id: 1) successfully registered

Заключение

Repmgr - простой, понятный инструмент, который сильно облегчает операции в master-slave конфигурациях. Он не позволит полностью автоматизировать защиту от отказов (для этого нужны другие инструменты), но поможет в простых конфигурациях. Я очень рекомендую использовать его – самостоятельно или в сочетании с pgpool-II. В таком варианте pgpool отвечает за балансировку запросов, фенсинг и инициирует failover, когда это необходимо. Repmgr отвечает за сам низкоуровневый процесс failover (и это намного лучше, чем рекомендуемый pgpool набор жутковатых скриптов!).

При этом я очень не рекомендую:

  • использовать автоматический failover средствами repmgr. Строго говоря – он не работает. Для работы repmgr нужен работающий сервер postgresql, и если postgresql master упал – repmgr не в состоянии самостоятельно переключится (из-за упавшего мастера)
  • использовать bidirectional multimaster. Эта функция заявлена в описании, но работает крайне плохо - сервера теряют связь друг с другом и часто трут конфликтующие данные. Проверено до версии 4.0.5