Пользователь
0,0
рейтинг
28 июля 2013 в 22:19

Разработка → Отказоустойчивый кластер Master-Slave на PostgreSQL из песочницы

Приветствую, хаброжители!
В этой статье я хочу поделиться опытом развертывания кластера Master-slave на СУБД PostgreSQL. Отказоустойчивость достигается с помощью возможностей pgpool-II (failover, online recovery).
pgpool — это прекрасное средство для масштабирования и распределения нагрузки между серверами и, думаю, немногие знают о возможностях автоматического создания failover на ведомом сервере при отказе ведущего и как добавить новые мощности в уже работающий кластер без отключения всего кластера.


Схема кластера и требования к машинам

На рисунке представлена типичная схема кластера Master-slave.
image
Кластер должен содержать 1 ведущий сервер (Master), хотя бы 1 ведомый (Slave), 1 узел масштабирования (Balancer).
При на каждый из серверов должен быть установлен Linux-дистрибутив (у меня поставлен Red Hat 6.1), на узле масштабирования должен установлен компилятор gcc.
Версия PostgreSQL — 9.0.1, pgpool-II 3.0.5. Можно использовать и другие версии СУБД и pgpool. При этом обратитесь к документации.

Настройка удаленного соединения между серверами кластера

Online recovery и failover требуют настройки удаленного соединения по протоколу SSH без пароля. Для этого нужно создать SSH-ключи пользователя postgres и разослать их пользователям postgres каждому из серверов.
Важный момент! Для online recovery необходимо, чтобы при открытии удаленной сессии можно было перейти в еще одну удаленную сессию (т.е. можно было реализовать следующий механизм перехода по SSH без пароля: узел масштабирования — ведущий сервер — ведомый сервер и узел масштабирования — ведомый сервер — ведущий сервер).
Для failover необходимо создать SSH-ключ пользователя root на узле масштабирования и переслать пользователям postgres ведущего и ведомого сервера.
Этот шаг является важным при настройке, поэтому убедитесь, что возможно подключение из удаленной сессии одного из серверов к другому.

Настройка потоковой репликации

Предварительно нужно открыть прием/передачу данных по порту 5432 (стандартный порт PostgreSQL) в iptables.
Отредактируйте конфигурационный файл $PGDATA/postgresql.conf ведущего сервера следующим образом:
listen_addresses = '*'
wal_level = hot_standby
max_wal_senders = 2
wal_keep_segments = 32
#hot_standby = on

Отмечу важность последней строки. Дело в том, что она будет использована в скрипте восстановления ведомого узла, поэтому ее нужно обязательно изменить так, как написано выше.
Далее добавляем строчки для репликации в $PGDATA/pg_hba.conf:
host        replication	postgres	 192.168.100.2/32	trust
host        replication	postgres	 192.168.100.3/32	trust

postgres — это администратор базы, который будет проводить репликацию и прочие админские хитрости. С помощью этих строк мы разрешили производить репликацию как ведомого, так и ведущего сервера.
После чего перегружаем ведущий сервер:
# service postgresql restart

Останавливаем ведомый сервер (если был запущен ранее):
# service postgresql stop

Теперь можно приступать к репликации.
На ведущем сервере пользователем postgres создаем backup базы пересылаем ведомому серверу:
$ psql -c "SELECT pg_start_backup('stream');"
$ rsync -a /var/lib/pgsql/data/ 192.168.100.3:/var/lib/pgsql/data/ --exclude postmaster.pid
$ psql -c "SELECT pg_stop_backup();"

После чего на ведомом создаем конфиг репликации $PGDATA/recovery.conf:
standby_mode = 'on'
primary_conninfo = 'host=192.168.100.2 port=5432 user=postgres'
trigger_file = 'failover'

Параметр trigger_file отвечает за путь, по которому PostgreSQL ищет файл, чтобы переключиться в режим ведущего. В данном случае PostgreSQL ищет файл по пути $PGDATA/failover.
Далее нужно включить режим «горячего резерва» на ведомом сервере:
$ sed -i 's/#hot_standby = on/hot_standby = on/' /var/lib/pgsql/data/postgresql.conf

После чего нужно запустить ведомый сервер:
# service postgresql start


Активность репликации можно проверить следующим образом:
На ведущем сервере выполнить команду
$ ps aux | grep sender

Она должна вывести приблизительно следующее:
2561 ? Ss 0:00 postgres: wal sender process postgres 192.168.100.3(33341) streaming 0/2031D28

Аналогично на ведомом сервере:
$ ps aux | grep receiver

Она выдаст следующее:
1524 ? Ss 0:00 postgres: wal reciever process streaming 0/2031D28


Общая настройка узла масштабирования

Меняем конфигурационный файл /etc/pgpool-II/pgpool.conf:
# Устанавливаем весь диапазон прослушиваемых адресов
listen_addresses = '*'                                                                                           
# Параметры подключения к базе на ведомом сервере
backend_hostname0 = '192.168.100.3'                                    
backend_port0 = 5432                                               
backend_weight0 = 1                                                      
backend_data_directory0 = '/var/lib/pgsql/data'                            

# Параметры подключения к базе на ведущем сервере
backend_hostname1 = '192.168.100.2'                                       
backend_port1 = 5432 
backend_weight1 = 1
backend_data_directory1 = '/var/lib/pgsql/data' 

# Используем pool_hba.conf для авторизации клиентов
enable_pool_hba = true                      

Далее в /etc/pgpool-II/pool_hba.conf добавляем информацию об авторизации клиентов:
host	all	all	127.0.0.1/32		trust	
host	all	all	192.168.100.2/32	trust
host	all	all	192.168.100.3/32	trust

Перезагружаем pgpool:
# service pgpool restart


Настройка автоматического failover

Механизм создания автоматического failover следующий:
  1. На рабочих (ведущем и ведомом) серверах выполняется процедура pgpool-walrecrunning(), определяющая, какой из серверов является ведущим, какой ведомый.
  2. pgpool удаленно подключается к рабочим серверам и проверяет активность процессов СУБД. Если нет, то pgpool вызывает скрипт, который создает failover на ведомом узле в случае отказа ведущего сервера.
  3. После чего pgpool отключается от упавшего узла и перезапускает все клиентские приложения, подключенные к нему.

А теперь настройка:
На узле масштабирования меняем конфиг pgpool /etc/pgpool-II/pgpool.conf:
# Скрипт, вызываемый при падении сервера 
failover_command = '/etc/pgpool-II/failover.sh %d %H /var/lib/pgsql/data/failover'
# Пользователь, проверяющий статус сервера
health_check_user = 'postgres'                                             

# Ставим активность режима "Ведущий-ведомый"
master_slave_mode = true 
# У нас настроена потоковая репликация, ставим режим работы 'потоковая'                                               
master_slave_sub_mode = 'stream'                                           

# Не разрешаем pgpool отправлять копию запроса на узлы кластера, у нас это произойдет автоматически благодаря репликации
replication_mode = false                                                  
# Включаем балансировку серверов, чтобы запросы могли равномерно распределяться между серверами
load_balance_mode = true 

Расскажу немного подробнее про параметр failover_command. Скрипту, указанному в этой строке, передаются параметры %d — идентификатор упавшего узла (согласно backend_hostname в pgpool.conf), %H — IP нового ведущего сервера.
Собственно сам скрипт failover.sh:
#! /bin/bash

# ID упавшего узла
FAILED_NODE=$1
# IP нового мастера
NEW_MASTER=$2
# Путь к триггерному файлу
TRIGGER_FILE=$3

if [ $FAILED_NODE = 1 ]; 
then
	echo "Ведомый сервер вышел из строя"
	exit 1
fi

echo "Ведущий сервер вышел из строя"
echo "Новый ведущий сервер: $NEW_MASTER"

ssh -T postgres@$NEW_MASTER touch $TRIGGER_FILE
exit 0

Этот скрипт нужно создать в каталоге pgpool /etc/pgpool-II/ и выдать права 755.
Теперь нужно скомпилировать процедуры pgpool. В src пакета pgpool в каталоге sql/pgpool-walrecrunning содержится исходный код нужной нам процедуры. Для ее компилирования нужны заголовочные файлы PostgreSQL, после чего можно воспользоваться командой make и получить pgpool-walrecrunning.so и SQL-запрос загрузки этой процедуры pgpool-walrecrunning.sql.
Процедуру нужно скопировать в каталог на каждом рабочем сервере /usr/lib64/pgsql/, который именуется $libdir, sql-файл в /usr/share/pgsql/.
Загружаем в базу на ведущем сервере:
psql -f /usr/share/pgsql/pgpool-walrecrunning.sql -d postgres

Загрузку в базу этой процедуры на ведомом сервере нет необходимости: она будет доступна благодаря настроенной ранее репликации.
Вот и все.

Статус серверов можно определить с помощью запроса
SHOW pool_nodes;
предварительно зайдя в клиент psql на узле масштабирования.
Пример вывода запроса:
   hostname    | port | status | lb_weight
-----------------------------------------------------
 192.168.100.3 | 5432 | 2      | 0.500000
 192.168.100.2 | 5432 | 2      | 0.500000
(2 rows)

Статус сервера 2 означает, что сервер активен и доступен для запросов. В случае отказа одного из серверов статус изменится на 3.

Протестировать механизм автоматического failover можно следующим образом:
  1. Отключить ведущий сервер
  2. Выполнить запрос SHOW pool_nodes; на узле масштабирования
  3. Смотреть логи pgpool на предмет выполнения скрипта
  4. Убедиться в том, что ведомый сервер после выполнения скрипта может принимать запросы на запись


Online recovery

Наверное, этот механизм является наиболее сложным в плане дебага, но при этом является мощным инструментом при администрировании базы. Работа этого механизма заключается в следующем: есть рабочий кластер, мы хотим включить упавший ранее ведомый сервер, но данные, хранящиеся на нем, не соответствуют данным в кластере. Этот механизм позволяет нам добавить в реальном времени еще один ведомый сервер без остановки кластера и проведении каких-либо дополнительных действий при его настройке.
Online recovery работает следующим образом:
  1. На узле масштабирования запускается процедура восстановления ведомого сервера
  2. Эта процедура на ведущем сервере запускает скрипт, выполняющий автоматическую репликацию между ведущим и ведомым сервером
  3. После успешного выполнения репликации, база на ведущем сервере удаленно запускается с помощью стандартной утилиты PostgreSQL PGCTL
  4. pgpool перезапускается, обнаруживает ведомый сервер и включает его в кластер

Переходим к настройке.
Добавить следующие строчки в /etc/pgpool-II/pgpool.conf:
# Пользователь, проводящий восстановление
recovery_user = 'postgres'    
# Пароль этого пользователя
recovery_password = '123456' 
# Скрипт, запускающийся на ведущем скрипте из каталога $PGDATA        
recovery_1st_stage_command = 'basebackup.sh'                

Добавить хэш пароля postgres:
# pg_md5 123456 >> /etc/pgpool-II/pcp.conf

123456 — это пароль postgres в открытом виде. При этом дополнительно перед хэшем пароля нужно указать имя пользователя, кому этот хэш принадлежит, т.е. в файле должно быть строка postgres:enrypted_password.
На ведущем узле создать скрипт basebackup.sh следующего содержания:
#!/bin/bash

# Путь к каталогу $PGDATA на ведущем сервере
PRIMARY_DATA=$1
# IP-адрес ведомого сервера, который включается в кластер
SLAVE_IP=$2
# Путь к каталогу $PGDATA на ведомом сервере
SLAVE_DATA=$3

# Определяем IP ведущего сервера для включения в конфиг recovery.conf
PRIMARY_IP=$(ifconfig eth0| sed -n '2 {s/^.*inet addr:\([0-9.]*\) .*/\1/;p}')
# Директория для хранения конфигов для ведомого сервера
TMP_DIR=/var/lib/pgsql/tmp

# На ведущем сервере удаляем старые конфиги от репликации (если ведущий сервер был когда-то ведомым)
cd $PRIMARY_DATA
rm -f recovery.* failover 
# Проверяем, активен ли режим горячего резерва на ведущем сервере
cat postgresql.conf | grep '#hot_standby = on'

# Если активен, то выключаем его
if [ $? = 1 ] 
then       
    sed -i 's/hot_standby = on/#hot_standby = on/' postgresql.conf 
    # Перезапускаем ведущий сервер
    /usr/bin/pg_ctl restart -D $PGDIR 	
fi

#Удаленно останавливаем ведомый сервер
ssh -T postgres@$SLAVE_IP "/usr/bin/pg_ctl stop -D $SLAVE_DATA"
# Создаем backup базы на ведущем сервере
psql -c "SELECT pg_start_backup('Streaming Replication', true)" postgres
# Пересылаем его ведомому
rsync -a $PRIMARY_DATA/ $SLAVE_IP:$SLAVE_DATA/ --exclude postmaster.pid --exclude postmaster.opts 

# Создаем временную папку конфигов для ведомого сервера
mkdir $TMP_DIR
cd $TMP_DIR
# Копируем конфиг postgresql.conf и включаем hot_standby
cp $PRIMARY_DATA/postgresql.conf $TMP_DIR/
sed -i 's/#hot_standby = on/hot_standby = on/' postgresql.conf 

# Создаем конфиг recovery.conf
echo "standby_mode = 'on'" > recovery.conf
echo "primary_conninfo = 'host=$PRIMARY_IP port=5432 user=postgres'" >> recovery.conf
echo "trigger_file = 'failover'" >> recovery.conf

# Удаляем с ведомого сервера старые конфиги репликации
ssh -T postgres@$SLAVE_IP rm -f $SLAVE_DATA/recovery.*
# Копируем новые конфиги
scp postgresql.conf postgres@$SLAVE_IP:$SLAVE_DATA/postgresql.conf
scp recovery.conf postgres@$SLAVE_IP:$SLAVE_DATA/recovery.conf

#Завершаем процесс backup
psql -c "SELECT pg_stop_backup()" postgres

# Удаляем временную папку с конифгами
cd ..
rm -fr $TMP_DIR

Подчеркиваю, этот скрипт должен быть в каталоге $PGDATA. Скрипту назначить права 755.
На ведомом и ведущем сервере в каталоге $PGDATA создать скрипт pgpool_remote_start (Именно под таким именем!) со следующим содержанием:
#! /bin/bash

if [ $# -ne 2 ]
then
    echo "Мало аргументов, переданных скрипту"
    exit 1
fi

SLAVE_IP=$1
SLAVE_DIR=$2
PGCTL=/usr/bin/pg_ctl

ssh -T $SLAVE_IP $PGCTL -w -D $SLAVE_DIR start 2>/dev/null 1>/dev/null < /dev/null &

Он позволит удаленно запускать процессы СУБД.
Далее на узле масштабирования нужно скомпилировать хранимую процедуру pgpool-recovery.so, расположенную по пути sql/pgpool-recovery src пакета pgpool. Аналогичным образом переслать ее рабочим серверам и выполнить загрузку процедуру в базу:
$ psql -f /usr/share/pgsql/pgpool-recovery.sql -d template1

На этом настройка online recovery закончена.

Для включения нового ведомого сервера в кластер необходимо выполнить следующие действия:
  1. Запустить базу на новом ведущем узле
  2. На узле масштабирования выполнить команду по восстановлению сервера: pcp_recovery_node 20 192.168.100.4 9898 postgres 123456 1

Подробнее о pcp_recovery_node. Эта команда реализует восстановление сервера в кластер. 20 — это число попыток подключения к ведомому серверу, 192.168.100.4 — IP узла масштабирования, 9898 — порт pcp команд узла масштабирования, postgres — имя пользователя, производящего восстановления, 123456 — его пароль, 1 — ID восстанавливаемого узла.
На этом закончена настройка online recovery.

Можно произвести тестирование этих двух механизмов по следующему плану:
  1. Создать тестовую базу данных на ведущем сервере. Убедиться, что она реплицировалась на ведомый
  2. Имитировать отказ ведущего сервера, отключив его
  3. Убедиться, что сработал failover и ведомый сервер стал новым ведущим
  4. Внести изменения в базу данных на ведомом сервере
  5. Запустить упавший ведущий сервер и сделать его ведомым, проведя online recovery


Таким образом, описанные выше механизмы позволяют обезопасить кластер «Ведущий-ведомый» и упростить работу администратора базы данных при ее восстановлении.

P.S. Надеюсь, данный пост помог кому-нибудь. Комментарии и дополнения приветствуются! Благодарю за внимание.
Дмитрий Воронин @dimv36
карма
15,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (18)

  • +1
    Скорость работы этой «прослойки» измеряли?
    Я делал все то же самое, потом замерил скорость с pgpool и без него.
    Получилось что в среднем pgpool ЗАМЕДЛЯЕТ скорость примерно в 3 раза.
    В связке pgbouncer-pgpool-postgres проигрыш (!!!) в сравнении с чистым postgres у меня получился 20-30 процентов.
    Самая быстрая комбинация — pgbouncer+postgres
    На ней я и остановился. У меня pgpool следит за failover-ами и осуществляет репликацию. При failover помимо «дергания» триггера новому мастеру еще назначается выделенный дополнительный IP (у бывшего мастера этот IP удаляется). Все запросы обрабатывает pgbouncer и переадресовывает эти запросы по этому самому выделенному IP.
    • +1
      > У меня pgpool следит за failover-ами и осуществляет репликацию.

      Вот поэтому «pgpool ЗАМЕДЛЯЕТ скорость примерно в 3 раза.» У pgpool синхронная репликация, из-за этого и тормоза.
    • 0
      Моя задача была в исследовании принципа построения такого кластера, а не проверка скорости работы pgpool.
    • 0
      А как вы скорость меряли, кстати?

      У нас примерно такая же схема, только репликация нативная и синхронная. Pgbouncer при каком-то пороге нагрузки начал не работать, а скорее тупить. У pgpool2 можно включить все кэши и будет все быстро.
    • 0
      Производительность конечно будет проседать, так как при синхронной репликации мы вынужденны ждать отклика от всех узлов кластера, для завершения каждой транзакции. То что проседает в три раза — это думаю еще не предел, но все равно, в качестве бонуса мы получаем гарантированно синхронные копии БД, без отставания по времени. Все равно есть класс задач, где БД по сути хранит состояние некой распределенной системы. В такой ситуации база данных выступает в качестве единой точки отказа. Хотя порой развертывать отказоустойчивый кластер на трех (а с резервированием балансировщика и четырех) серверах — это для многих проектов роскошь. Думаю отказоустойчивый самовостанавливающийся Мастер-Мастер на двух — это более востребованное решение.
  • +1
    У вас вместо одной точки отказа стало две.
    Ставьте вместо него два прокси с возможностью на них переключатся с мастера на слейв PostgreSQL и наоборот.
    • 0
      Вариант использовать haproxy+keepalive.
      Хотя у меня реализовано с помощью pgbouncer.
    • 0
      Можно просто сделать вот так:

      image

      При отказе active pgpool, включится standby pgpool.
  • 0
    Я однажды исследовал возможности pgpool, в конфигурацию были включены аж два pgpool сервера с heartbeat для отказоустойчивости. Всё бы хорошо, отказоустойчивость без слабого звена, но меня остановило то, что есть функции, которые нельзя исполнить на двух серверах и получить одинаковый результат (простая now(), к примеру, исполняется всегда только на одном из серверов) ). Такие функции теоретически помещаются в белый список, но это накладывает ограничения на приложения, которые могут работать с сервером. Пришлось остановиться на стандартной постгресовской репликации. Получаем отказоустойчивость без проблем с производительностью, с простой конфигурацией.
  • 0
    Адъ. Имхо пока сами разработчики постгреса не озаботятся тем, чтобы все из коробки ставилось само на несколько машин кластера, само поддерживало failover и онлайн-добавление новых машин в кластер (в том числе инкапсулируя в себе pg_start_backup и rsync), так и будет продолжаться эта вакханалия. И так и будут их продолжать игнорировать всякие амазоны сотоварищи (я имею в виду amazon rds), предпочитая mysql постгресу.
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Нет, но mysql гораздо более популярен, это перевешивает. Можно «перевешивать» либо популярностью, либо богатством возможностей.
        • 0
          Это все стереотипы для тех, кто застрял на 4 верcии mysql и 7 postgres. Postgres уже давно стал проще, веселее, быстрее и даже появился под венду. И что касается geo, тут ему равных вообще нет. Плюс он дикими темпами развивается. В версии 9.3 они столько всего сделали, что уже очень хочется его в бой.
          А mysql мы уже отовсюду почти выпилили — какой-то он непредсказуемый порой.
          • 0
            Я-то с вами согласен, но расскажите это Амазону и еще огромной армии проектов, начинающих писаться на mysql.
      • 0
        К мускулю в коробке поставляется двести китайцев кодеров и один плющевый фрик чтобы все допилить под ваши нужды. По цене выходит раза в три дороже чем ДБ2 и во столько же раз тормознее, зато запускается на всем у чего есть процессор и оперативная память. В общем кому то нравится.
      • 0
        Percona mysql, по крайней мере, умеет приличную master-master репликацию из коробки.
        Т.е. вы можете перемещать IP адрес между нодами как вам удобно — они идентичны и реплицируются в две (три ) стороны.

        А для добавления нового сервера — да, там тоже нужно восстановить его с мастера.
        • 0
          У перконы (имеется ввиду Galera) все очень невесело с автовосстановлением после того, как кластер развалился.
  • 0
    Вот эту строчку подредактируйте пожалуйста: ssh -T postgres$NEW_MASTER touch $TRIGGER_FILE, вместо неё надо ssh -T postgres@$NEW_MASTER touch $TRIGGER_FILE, без @ он ругается:(

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.