Докеризация высокодоступного Postgres кластера

Автор: admin от 31-07-2017, 10:55, посмотрело: 237

Докеризация высокодоступного Postgres кластера


Пару месяцев назад мы переехали из Амазон на свои выделенные сервера(Hetzner), одна из причин тому была высокая стоимость RDS. Встала задача настроить и запустить master-slave кластер на выделенных серверах. После гугления и прочтения официальной документации, было принято решение собрать свое собственное решение высокодоступного асинхронного кластера Postgres.





Цели


  • Использовать как можно меньше инструментов и зависимостей.

  • Стремится к прозрачности, никакой магии!

  • Не использовать комбайны all-included типа pg-pool, stolon etc.

  • Использовать докер и его плюшки.



Итак, начнём,

Собственно нам понадобится сам Postgres и такой замечательный инструмент как repmgr, который занимается управлением репликаций и мониторингом кластера.



Проект называется pg-dock, состоит из 3 частей, каждая часть лежит на гитхабе, их можно брать и видоизменять как заблагорассудится.




  • pg-dock-config готовый набор файлов конфигурации, сейчас там прописано 2 нода, мастер-слейв.

  • pg-dock занимается упаковкой конфигов и доставкой их на ноды, в нужном виде и в нужное место.

  • pg-dock-base это базовый докер образ который и будет запускаться на нодах.





Давайте детально разберем каждую часть:



pg-dock-config

Конфигурация кластера, имеет следующую структуру






Докеризация высокодоступного Postgres кластера
В репозитории уже прописаны два нода(n1, n2), если нод у Вас больше, то просто создаем еще одну папку с названием новой ноды. Для каждый ноды свои файлы конфигурации. Мне кажется тут всё довольно просто, например папка env это переменные окружения которые будут подхватываться docker-compose'ом, папка postgres соответственно конфиги постгреса и.т.д.



Например файл pg-dock-conf/n1/env/main

POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=testdb
PGDATA=/var/lib/postgresql/data

HETZNER_USER=****
HETZNER_PASS=****
HETZNER_FAILOVER_IP=1.2.3.4
HETZNER_ACTIVE_SERVER_IP=5.6.7.8


Говорит нам о том что при первичной инициализации постгреса будет создан юзер postgres и база testdb. Так же тут прописаны переменные для failover-ip скрипта который меняет ip на новую мастер ноду в случае если старая стала не доступной.



pg-dock-conf/n1/env/backup

Переменные окружения для интервального бекапа базы на s3, подхватывается docker-compose'ом при старте сервиса.



Если у нас есть общие файлы конфигурации, то что бы не дублировать их по нодам, будем класть их в папку shared.

Пройдемся по ее структуре


  • failover

    В моем случае там скрипт для Hetzner failover-ip, который меняет ip на новый мастер. В Вашем случае это может быть скрипт keepalived или еще что то подобное.

  • initdb

    Все инициализирующие sql запросы надо положить в эту папку.

  • ssh

    Тут лежат ключи подключения к другому ноду, в нашем примере, ключи на всех нодах одни и те же, поэтому они лежат в папке shared. Ssh нужен repmgr что бы делать такие манипуляции как switchover и.т.п

  • sshd

    Файл конфигурации ssh сервера, ssh у нас будет работать на порту 2222 что бы не пересекаться с дефолтным портом на хосте(22)



pg-dock

Тут собственно происходит упаковка конфигурации для каждой ноды.

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






Докеризация высокодоступного Postgres кластера
Для работы есть базовые операции, создать билд конфига(build.sh), обновить конфиг на ноде(update.sh) и запустить сам кластер(docker-compose.yml)




  • helpers

    Вспомогательные файлы для работы кластера

  • manage

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





При запуске

PG_DOCK_NODE=n1  PG_DOCK_CONF_IMAGE=n1v1 ./build.sh
docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
n1v1                latest              712e6b2ace1a        6 minutes ago       1.17MB


Конфигурация pg-dock-conf/n1 скопируется в папку pg-dock/pg-dock-conf-n1, затем запустится docker build со всеми зависимостями, на выходе получаем образ с именем n1v1 в котором хранится наша конфигурация для нода n1.



При запуске

PG_DOCK_CONF_IMAGE=n1v1 ./update.sh


Запустится контейнер, который обновит все файлы конфигурации на хосте. Таким образом мы можем иметь несколько образов конфигурации, делать rollback на разные версии и.т.п



pg-docker-base

Базовый docker образ в котором установлены все пакеты для работы кластера: repmgr, rsync, openssh-server, supervisor(Dockerfile). Сам образ базируется на последней версии postgres 9.6.3, но можно использовать любой другой билд. Компоненты запускаются supervisor'ом из под юзера postgres. Этот образ мы и будем запускать на наших серверах(rsync, openssh-server требуются для работы repmgr).



Давайте запустим кластер!

Для удобства, в этой статье все манипуляции будут происходит при помощи docker-machine.



Клонируем проекты pg-dock и pg-dock-conf в рабочую папку(для примера lab)

mkdir ~/lab && cd ~/lab
git clone https://github.com/xcrezd/pg-dock
git clone https://github.com/xcrezd/pg-dock-conf


Создаем ноды, группу и пользователя postgres(uid, gid должен быть 5432 на хосте и в контейнере)

docker-machine create n1
docker-machine ssh n1 sudo addgroup postgres --gid 5432
docker-machine ssh n1 sudo adduser -u 5432 -h /home/postgres --shell /bin/sh -D -G postgres postgres

#для debian/ubuntu
#sudo adduser --uid 5432 --home /home/postgres --shell /bin/bash --ingroup postgres --disabled-password postgres

docker-machine create n2
docker-machine ssh n2 sudo addgroup postgres -g 5432
docker-machine ssh n2 sudo adduser -u 5432 -h /home/postgres --shell /bin/sh -D -G postgres postgres


Добавляем ip нод в /etc/hosts

docker-machine ip n1
#192.168.99.100
docker-machine ip n2
#192.168.99.101

# в ноду n1
docker-machine ssh n1 "sudo sh -c 'echo 192.168.99.100 n1  /etc/hosts'"
docker-machine ssh n1 "sudo sh -c 'echo 192.168.99.101 n2  /etc/hosts'"

# в ноду n2
docker-machine ssh n2 "sudo sh -c 'echo 192.168.99.100 n1  /etc/hosts'"
docker-machine ssh n2 "sudo sh -c 'echo 192.168.99.101 n2  /etc/hosts'"


Если IP ваших машин отличаются от IP в статье, то дополнительно надо добавить их в


  • pg-dock-config/n1/postgres/pg_hba.conf

  • pg-dock-config/n2/postgres/pg_hba.conf



Создаем образы конфигураций и сразу обновляем их на нодах

cd pg-dock
docker-machine use n1
PG_DOCK_NODE=n1 PG_DOCK_CONF_IMAGE=n1v1 ./build.sh
PG_DOCK_CONF_IMAGE=n1v1 ./update.sh

docker-machine use n2
PG_DOCK_NODE=n2 PG_DOCK_CONF_IMAGE=n2v1 ./build.sh
PG_DOCK_CONF_IMAGE=n2v1 ./update.sh


Обратите внимание на команду docker-machine use(как сделать), при каждом ее применении, мы меняем контекст докер клиента, то есть в первом случае все манипуляции с докером будут на ноде n1 а потом на n2.



Запускаем контейнеры

docker-machine use n1
PG_DOCK_NODE=n1 docker-compose up -d

docker-machine use n2
PG_DOCK_NODE=n2 docker-compose up -d


docker-compose так же запустит контейнер pg-dock-backup, который будет делать переодический бекап на s3.

Теперь посмотрим, где хранятся нужные нам файлы:
























Файлы

Хост

Контейнер

БД

/opt/pg-dock/data

/var/lib/postgresql/data

Логи

/opt/pg-dock/logs

/var/log/supervisor

Конфигурация и скрипты

/opt/pg-dock/scripts

** изучите docker-compose.yml



Идем дальше, настраиваем кластер

docker-machine use n1
#Регестрируем как мастер ноду
docker exec -it -u postgres pg-dock repmgr master register

docker-machine use n2
#Клонируем данные из нода n1
docker exec -it -u postgres -e PG_DOCK_FROM=n1 pg-dock manage/repmgr_clone_standby.sh
#Регестрируем ноду как слейв
docker exec -it -u postgres pg-dock repmgr standby register


Вот и все, кластер готов



docker exec -it -u postgres pg-dock repmgr cluster show
Role      | Name | Upstream | Connection String
----------+------|----------|--------------------------------------------
* master  | n1   |          | host=n1 port=5432 user=repmgr dbname=repmgr
  standby | n2   | n1       | host=n2 port=5432 user=repmgr dbname=repmgr




Давайте проверим его роботоспособность. В папке pg-dock-config/shared/tests у нас есть такие вот заготовки для тестирования нашего кластера

#Создает тестовую таблицу
cat tests/prepare.sh
CREATE TABLE IF NOT EXISTS testtable (id serial, data text);
GRANT ALL PRIVILEGES ON TABLE testtable TO postgres;

#Добавляет 100000 записей
cat tests/insert.sh
insert into testtable select nextval('testtable_id_seq'::regclass), md5(generate_series(1,1000000)::text);

#Считает сколько записей в таблице
cat tests/select.sh
select count(*) from testtable;




Cоздаем тестовую таблицу, набиваем ее данными и проверяем если они есть на слейве

docker-machine use n1
#Создаем тестовую таблицу для проверки репликации
docker exec -it -u postgres pg-dock config/tests/prepare.sh
#Добавляем записи для проверки
docker exec -it -u postgres pg-dock config/tests/insert.sh
INSERT 0 1000000

docker-machine use n2
#Проверяем что записи находятся на n2(репликация)
docker exec -it -u postgres pg-dock config/tests/select.sh
  count
---------
 1000000
(1 row)


Профит!



Теперь давайте рассмотрим сценарий падения мастера

#Останавливаем мастер ноду
docker-machine use n1
docker stop pg-dock
#Смотрим логи repmgr у слейва
docker-machine use n2
docker exec -it pg-dock tailf /var/log/supervisor/repmgr-stderr.log
#NOTICE: STANDBY PROMOTE successful




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

docker exec -it -u postgres pg-dock repmgr cluster show
Role     | Name | Upstream | Connection String
---------+------|----------|--------------------------------------------
  FAILED | n1   |          | host=n1 port=5432 user=repmgr dbname=repmgr
* master | n2   |          | host=n2 port=5432 user=repmgr dbname=repmgr


Теперь новый мастер у нас n2, failover ip тоже указывает на него.

Теперь давайте вернем старый мастер уже как новый слейв

docker-machine use n1
#Поднимаем контейнеры
PG_DOCK_NODE=n1 docker-compose up -d #как демон
#Клонируем данные из ноды n2
docker exec -it -u postgres -e PG_DOCK_FROM=n2 pg-dock manage/repmgr_clone_standby.sh
#Регестрируем ноду как слейв
docker exec -it -u postgres pg-dock repmgr standby register -F


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

docker exec -it -u postgres pg-dock repmgr cluster show
Role     | Name | Upstream  | Connection String
---------+------|-----------|--------------------------------------------
* master | n2   |           | host=n2 port=5432 user=repmgr dbname=repmgr
  standby| n1   | n2                  | host=n1 port=5432 user=repmgr dbname=repmgr


Готово! И вот, что у нас получилось сделать; Мы уронили мастер, сработало автоматическое назначение слейва новым мастером, поменялся failover IP. Система продолжает функционировать. Потом мы реанимировали ноду n1, сделали ее новым слейвом. Теперь уже ради интереса, мы сделаем swithover — то есть вручную сделаем n1 мастером а n2 слейвом, как было раньше. Вот как раз для этого repmgr и нужен ssh, слейв подключается по ssh к мастеру и скриптами делает нужные манипуляции.



switchover

docker-machine use n1
docker exec -it -u postgres pg-dock repmgr standby switchover
#NOTICE: switchover was successful




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

docker exec -it -u postgres pg-dock repmgr cluster show
Role      | Name | Upstream | Connection String
----------+------|----------|--------------------------------------------
  standby | n2   |          | host=n2 port=5432 user=repmgr dbname=repmgr
* master  | n1   |          | host=n1 port=5432 user=repmgr dbname=repmgr




Вот и все, в следующий раз когда нам надо обновить конфигурацию ноды, будь то конфиг postgres, repmgr или supervisor'a, мы просто пакуем ее и обновляем:



PG_DOCK_NODE=n1 PG_DOCK_CONF_IMAGE=n1v1 ./build.sh
PG_DOCK_CONF_IMAGE=n1v1 ./update.sh


После обновления новой конфигурации:

#Обновляем конфигурацию postgres
docker exec -it -u postgres pg-dock psql -c "SELECT pg_reload_conf();"
#Обновляем конфигурацию supervisor
docker exec -it -u postgres pg-dock supervisorctl reread
#перезапускаем отдельный процесс
docker exec -it -u postgres pg-dock supervisorctl restart foo:sshd


* Приятный бонус, supervisor имеет функцию ротацию логов, так что и за это нам не надо переживать.

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

* Рекомендую добавить уже существующие продакшен ноды в docker-machine, это сильно упростит Вам жизнь.



Теперь давайте коснемся темы балансировки запросов. Не хотелось усложнять(то есть использовать pg-pool, haproxy, stolon) поэтому балансировку мы будем делать на стороне приложения, тем самым снимая с себя обязанности по организации высокодоступности уже самого балансировщика. Наши бекенды написаны на руби, поэтому выбор пал на гем makara. Гем умеет разделять запросы на выборку и модификацию данных (insert/update/delete/alter), запросы на выборку можно балансировать между несколькими нодами (слейвами). В случае отказа одного из нод, гем умеет временно исключать его из пула.



Пример файла конфигурации database.yml

production:
  adapter: 'postgresql_makara'
  makara:
    # the following are default values
    blacklist_duration: 5
    master_ttl: 5
    master_strategy: failover
    sticky: true

    connections:
      - role: master
        database: mydb
        host: 123.123.123.123
        port: 6543
        weight: 3
        username: <%= ENV['DATABASE_USERNAME'] %>
        password: <%= ENV['DATABASE_PASSWORD'] %>
      - role: slave
        database: mydb
        host: 123.123.123.124
        port: 6543
        weight: 7
        username: <%= ENV['DATABASE_USERNAME'] %>
        password: <%= ENV['DATABASE_PASSWORD'] %>


Библиотеки на других языках/фреймворках

laravel laravel.com/docs/5.4/database#read-write-connections

Yii2 www.yiiframework.com/doc-2.0/guide-db-dao.html#read-write-splitting

Node.js www.codeday.top/2017/01/08/6521.html



Заключение



Итак, что мы получили в итоге:




  • Самодостаточный кластер master-standby готовый к бою.

  • Прозрачность всех компонент, легкая заменимость.

  • Автоматический failover в случае отказа мастера(repmgr)

  • Балансировка нагрузки на клиенте, тем самым снимая ответственность за доступность самого балансировщика

  • Отсутствие единой точки отказа, repmgr запустит скрипт который перенесет IP адрес на новую ноду, которая была повышена до мастера в случае отказа. В темплейте есть скрипт для hetzner, но ничего не мешает добавить keepalived, aws elasticIp, drdb, pacemaker, corosync.

  • Контроль версий, возможность делать rollback в случае неполадок / ab testing.

  • Возможность настроить систему под себя, добавлять ноды, repmgr witness, например, гибкость конфигурации и ее изменений.

  • Периодический бекап на S3



В следующей статье я расскажу, как на одной ноде разместить pg-dock и PgBouncer не теряя при этом в высокодоступности, всем спасибо за внимание!



Рекомендации к ознакомлению



Источник: Хабрахабр

Категория: Админитстрирование » Системное администрирование

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *