пятница, 15 июля 2016 г.

Кластер postgresql с pgpool

Небольшая хаутушечка по настройке pgpool-кластера с steaming replication postgres.

1. Подготовка посгреса.

Необходимо настроить потоковую репликацию.
Первым делом необходимо настроить авторизацию юзера postgres на машинах кластера по ssh-ключам без пароля (причем, не забыть про коннект по ssh к самому себе). ssh-keygen и ssh-copy-id в помощь.

Конфиг postgresql.conf лучше иметь одинаковый (за исключением директивы archive_command), что на мастере, что на слейве - так удобнее и проще, тоже самое касается каталогов с базой и архивлогами.

Добавляем директивы:

hot_standby = on // указывает что слейв будет в одноименном режиме, т.е. с него возможны селекты; на мастере она игнорируется
wal_level = hot_standby     // директива для мастера, указывает режим репликации; на слейве игнорируется
max_wal_senders = 4    // число процессов репликации, должно быть не меньше числа слейвов
wal_keep_segments = 12    // сколько хранить wal-логов, если число будет слишком маленьким - слейв не сможет засинхронизироваться после длительного оффлайна
archive_mode = on    // дополнение к потоковой репликации, ускоряет синхронизацию после длительного оффлайна
archive_command = 'rsync -aq %p <slave-name>:/somedir/archive/%f' // команда для отправки файликов архив-логов. единственный параметр, который будет различаться на мастере и слейве (мастер указывает на слейв, слейв на мастер)



Кроме того, listen_address не должен быть localhost, иначе слейв не сможет подключиться (извращения со слейвом на том же хосте опустим), а так же необходимо настроить pg_hba.conf для разрешения подключения replication-юзера, что-то вроде такого:

host    replication    postgres    192.168.1.0/24    trust

После этого запускаем мастер-сервер. Готовим слейв.
Проще всего сделать pg_basebackup с работающего мастера, указать в archive_command хостнейм мастера и добавить файлик recovery.conf:

standby_mode='on'
primary_conninfo='host=master port=5432 user=postgres' // реквизиты подключения к мастеру
trigger_file='/somedir/trigger_file' // путь до триггер-файла. при его появлении посгрес будет считать что мастер сдох и нужно самому становиться мастером
restore_command='cp /somedir/archive/%f %p' // команда для применения архивлогов
archive_cleanup_command='/usr/pgsql-9.2/bin/pg_archivecleanup /somedir/archive %r' // очистка примененных архивлогов

После этого можно можно запускать слейва, он должен успешно подключиться к мастеру и войти в режим hot_standby.

2. Настройка pgpool

Поставить pgpool под rhel/centos можно из пакетов, так что про традиционное для подобных хаутушечек configure&&make&&make install можно забыть.
Правим pgpool.conf:

backend_hostname0 = 'hostname'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/opt/pgsql/9.2/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'

Такими директивами (с увеличением порядкового номера) описываются все ноды кластера.
Включаем авторизацию через pgpool:

enable_pool_hba = on
authentication_timeout = 60
pool_passwd = 'pool_passwd'     #имя файлика с юзерами/паролями в каталоге /etc/pgpool-II

Для авторизации по паролям через pgpool необходимо чтобы юзер и пароль совпадали, что в pgpool-е, что в посгресе, тип авторизации в посгресе должен быть md5. Например, создаем юзера "root" с паролем "qwerty":

createuser -lP root

в pg_hba.conf добавляем

host    all    root    pgpool-host-ip    md5
pg_md5 -u root -p

вывод добавляем в pool_passwd, права на файл должны позволять юзеру postgres его прочитать.

Возвращаемся к pgpool.conf:

load_balance_mode = on        # включаем балансировку нагрузки - селекты смогут выполняться в т.ч. на слейве

master_slave_mode = on
master_slave_sub_mode = 'stream' # включаем streaming replication

sr_check_period = 10        # период проверки репликации
sr_check_user = 'root'        # реквизиты для проверки репликации
sr_check_password = 'qwerty'

health_check_period = 5        # проверка доступности нод
health_check_timeout = 10
health_check_user = 'root'    # реквизиты для проверки
health_check_password = 'qwerty'

failover_command = '/opt/pgsql/failover.sh %d %P %H %R' # самое важное - команда для файловера, будет исполняться при падении какой-либо из нод

Скрипт файловера довольно простой: если упал слейв - не делать ничего, если мастер - создать триггер-файл на слейве. А чтобы все это получилось - ssh-ключики и создавались.

#!/bin/bash
failed_id=$1
old_primary_id=$2
new_master_host_name=$3
new_database_path=$4
trigger=trigger_file

if [ $failed_id = $old_primary_id ];then    # master failed
    ssh $new_master_host_name touch $new_database_path/$trigger    # let standby take over
fi

На этом минимум - закончен. pgpool сможет обслуживать соединения, отслеживать состояния нод и промоутить слейва в случае падения мастера.
Остается решить вопрос с рекавери - восстановлением работы упавшей ноды. Для этого есть 2 варианта:

восстанавливать ноду ручками (т.е. без участия pgpool-а) и давать pgpool-у команду на присоединение ноды

настроить online-recovery в pgpool-е, но все равно восстанавливать ручками :D хотя бы потому, что команду на восстановление придется все же давать руками (через консоль или модный pgpoolAdmin)

Первый вариант довольно прост и по-сути полностью повторяет официальный посгресовый гайд: сделать бейсбэкап с работающего мастера, создать recovery.conf и запустить ноду как слейв. Это все можно оформить в удобный скрипт:

#!/bin/bash

cluster_master=$1
recovery_host=$2
recovery_dir=$3


psql -h $cluster_master -c "SELECT pg_start_backup('Streaming Replication', true)" postgres

rsync -C -a -c --delete --exclude postgresql.conf --exclude postmaster.pid \
--exclude postmaster.opts --exclude pg_log \
--exclude recovery.conf --exclude recovery.done \
--exclude pg_xlog \
$recovery_dir/ $recovery_host:$recovery_dir/
psql -h $cluster_master -c "SELECT pg_stop_backup()" postgres

ssh $recovery_host "mkdir $recovery_dir/pg_xlog; chmod 700 $recovery_dir/pg_xlog"
rec=`mktemp`
cat > $rec << EOF
standby_mode='on'
primary_conninfo='host=$cluster_master port=5432 user=postgres'
trigger_file='$recovery_dir/trigger_file'
restore_command='cp /opt/pgsql/archive/%f %p'
archive_cleanup_command='/usr/pgsql-9.2/bin/pg_archivecleanup /opt/pgsql/archive %r'
EOF
scp $rec $recovery_host:$recovery_dir/recovery.conf
rm -f $rec

ssh $recovery_host sed -i "s/slave-hostname/$cluster_master/g" $recovery_dir/postgresql.conf

Соответственно алгоритм восстановления по этому варианту таков:

Запустить скрипт с работающего мастера, указать хостнеймы мастера, восстанавливаемого слейва и каталог pg_data. Он подготовит слейв

Запустить слейв. Если нигде ничего не напутано - он успешно подключится к мастеру и начнет работать хот-стендбаем.

Дать команду пгпулу на присоединение ноды: pcp_attach_node timeout hostname port# username password nodeID. nodeID восстанавливаемой ноды можно увидеть из вывода pcp_pool_status.
После этого нода будет вновь доступна в пгпуле.

Второй вариант - online-recovery в pgpool.

Механизм работы пгпула в этом варианте несколько неочевиден. В отличие от fileover и прочих функций - рекавери запускается не напрямую скриптом, а через вызов посгресовых функций pgpool_recovery и pgpool_remote_start, которые уже вызывают непосредственные скрипты (нафига такой изврат??!). Соответственно, чтобы все это работало - в посгрес должны быть загружены эти функции (в базу postgres и template), а для загрузки этих функций необходима шаред-библиотека под посгрес pgpool-recovery.so.

После добавления пгпуловских фукнций в посгрес нужно создать 2 скрипта в каталоге $pg_data: pgpool_remote_start (именно с таким названием!) и recover-online (тут уже название произвольное).

Первый скрипт, как видно из названия - скрипт удаленного запуска посгреса. Я предлагаю юзать системные инит-скрипты и судо, скрипт такого вида:

#!/bin/bash

if [ $# -ne 2 ]
then
    echo "pgpool_remote_start remote_host remote_datadir"
    exit 1
fi

DEST=$1
DESTDIR=$2
PGCTL=/usr/pgsql-9.2/bin/pg_ctl

ssh -T $DEST "sudo service postgresql-9.2 start"
Естественно, для его работы в судоерс необходимо разрешить юзеру посгрес выполнять "service postgresql-9.2 start" без запроса пароля.
Второй скрипт - собственно процедура рекавери. Скрипт такой:
#!/bin/bash

if [ $2 = "slave.hostname" ]
        then
                cluster_master="master.hostname"
        else
                cluster_master="slave.hostname"
fi

recovery_host=$2
recovery_dir=$3


psql -c "SELECT pg_start_backup('Streaming Replication', true)" postgres

rsync -C -a -c --delete --exclude postmaster.pid \
--exclude postmaster.opts --exclude pg_log \
--exclude recovery.conf --exclude recovery.done \
$recovery_dir/ $recovery_host:$recovery_dir/
psql -c "SELECT pg_stop_backup()" postgres

rec=`mktemp`
cat > $rec << EOF
standby_mode='on'
primary_conninfo='host=$cluster_master port=5432 user=postgres'
trigger_file='$recovery_dir/trigger_file'
restore_command='cp /opt/pgsql/archive/%f %p'
archive_cleanup_command='/usr/pgsql-9.2/bin/pg_archivecleanup /opt/pgsql/archive/%r'
EOF
scp $rec $recovery_host:$recovery_dir/recovery.conf
rm -f $rec

ssh -T $recovery_host sed -i "s/hostname/$cluster_master/g" $recovery_dir/postgresql.conf

Скрипт похож на "ручной" скрипт рекавери, но слегка изменен, т.к. приходится определять хостнейм текущего мастера (функция pgpool_recovery не передает имя мастера).
Когда все скрипты на месте, функции в базу загружены - можно переходить к конфигу пгпула.

Править нужно раздел "online recovery":

recovery_user = 'postgres'     # юзер (посгресовый)
recovery_password = 'pass'     # пароль
recovery_1st_stage_command = 'recover-online.sh' # имя рекавери-скрипта

Остальные параметры можно не трогать. Перезапускаем пгпул и можно пробовать - механизм должен работать.
Тут и подходим к самому главному - нафига было столько геморроя? Ибо пгпул не будет пытаться самостоятельно восстанавливать ноду после падения - он будет терпиливо ждать команды pcp_recovery_node (или нажатия кнопки в pgpool-admin-е) и только после получения ее с нужными параметрами соизволит восстановить ноду.
А раз ручками лезть все равно надо - по-моему проще "ручным" скриптом восстановить ноду и жмакнуть pcp_attach_node.

3. Устраняем единую точку отказа.

Pgpool в единственном числе - общая точка отказа. Для устранения этой проблемы - необходим еще один (или более) пгпул и настроенный watchdog. В случае с 2-мя хостами посгреса, master/slave - очевидным вариантом будет установка пгпула на каждый из хостов.
Конфиги пгпула должны быть одинаковые (включая скрипты файловера, рекавери и т.п.), различия есть только в разделе watchdog в параметрах.

use_watchdog = on #включаем вотчдог
trusted_servers = '192.168.222.2' #лучше указать один/несколько гарантированно доступных хостов - для исключения split-brain ситуации
wd_hostname = 'hostname' # имя хоста, на котором этот пгпул запущен; свое для каждого хоста
wd_port = 9000 # порт должен быть открыт на фаерволле
wd_authkey = 'qwerty' # ключик авторизации вотчдога, должен быть одинаковый. можно вообще без него
delegate_IP = '192.168.222.220'  # виртуальный айпи, по которому будет доступен кластер. именно на него нужно натравливать приложение, желающее соединиться с базой
wd_lifecheck_method = 'heartbeat' #метод работы вотчдога, heartbeat появился с версии 3.3. query у меня не заработал (как и вотчдог в 3.2 вообще)
wd_interval = 5
wd_heartbeat_port = 9694 #порт для хартбита. должен быть открыт в фаерволле
wd_heartbeat_keepalive = 2
wd_heartbeat_deadtime = 15
heartbeat_destination0 = 'hostname2' #имя другого хоста
heartbeat_destination_port0 = 9694
other_pgpool_hostname0 = 'hostname2' #имя другого хоста
other_pgpool_port0 = 5432
other_wd_port0 = 9000

Таким образом, конфиги различаются только параметрами wd_hostname, heartbeat_destination0 и other_pgpool_hostname0. Еще один важный момент - если пгпул запускается не от рута (а это так, если ставился из пакетов; да и вообще - нефиг), то для поднятия/опускания сетевых алиасов необходимо поставить suid-бит на /sbin/ifconfig и /usr/sbin/arping.
Если не ошиблись с хостнеймами и портами, то при запуске пулов они друг-друга увидят, один станет мастером и поднимет алиас, другой будет в стендбае, на случай падения мастера.

4. Результат

В целом, как отказоустойчивый кластер - pgpool справляется со своей задачей. Ослеживание доступности посгресов, файловер, рекавери, балансировка селектов, без единой точки отказа - это все есть и работает. Но, на мой взгляд - слишком много неочевидных мелочей в настройке и общее впечатление "нестабильности" всего этого хозяйства. Кроме того, хоть название и предполагает пулер соединений, но по-факту клиентские соединения разрываются при файловере. Поэтому использовать подобный кластер можно только в том случае, если клиенсткое приложение спокойно переваривает обрывы соединений и недоступность базы в течении 1-10сек (при срабатывании вотчдога и переключении на резервный пгпул).
По скорости работы: плохая новость - даже в режиме асинхронной репликации скорость посгреса на апдейтах/инсертах проседает раза в 3-4, по сравнению с одиночным посгресом (мерил pgbench-ем при дефолтном postgresql.conf, наличие/отсутствие пгпула на результат не влияет); хорошая новость - с включенным load-balancing-ом и memory cache скорость селектов в 2-4 раза выше, чем одиночный посгрес (опять же pgbench).


Взято отсюда.