Monday, September 5, 2016

Bucardo: Multi-Master PostgreSQL Cluster!
на примере Zabbix




     Создаем мульти-мастер PostgreSQL кластер на примере двух нод PostgreSQL сервера в FreeBSD Jail's. Кластеризовать будем базу данных Zabbix мониторинга, который будет также жить на двух разных хостах внутри FreeBSD Jail's.
     Итак, разберемся, что у нас что, и зачем мы это делаем...
1. Bucardo - грубо говоря, это скрипт на Perl, реализующий потоковую репликацию между PostgreSQL серверами, и дарящий нам счастливую возможность иметь 48 мастер нод PostgreSQL. Единственная тонкость, что все реплицируемые таблицы должны иметь Primary key, иначе таблицу не удастся включить в кластерную репликацию: Bucardo home site
     Собственно, реализации multi-master репликаций для PostgreSQL без Primary key существуют, например BDR, но он еще не дошел до стабильной стадии, так что пока я его не пробовал. Но в целом, весьма интересная разработка. Когда BDR появится в портах, я его обязательно попробую ;)
2. FreeBSD и Jails - ну а как иначе... Все сервисы должны жить изолированно из соображений безопасности и просто удобства. FreeBSD со своими Jails подходит для этого идеально. Опять же, ZFS пока в нормальном виде только во FreeBSD, в Linux ZFS еще не на такой стадии интеграции, хотя я и на Linux уже активно использую ZFS.
3. Zabbix - его я выбрал чисто для примера. Это не обязательно делать для Zabbix. Вы можете таким образом кластеризовать что угодно! Например можно кластеризовать Asterisk, заставив его хранить экстеншены и все основные настройки в PgSQL. Но и Zabbix, если у вас огромная сеть с кучей устройств для мониторинга,  будет не лишним кластеризовать в multi-master варианте :) Разнеся его по разным серверам и сегментам сети.

     Создаем свой первый jail для PostgreSQL:

     Я использую для управления джейлами QJail. Вполне удобная утилита, "очеловечивающая" работу с джейлами.

     Итак, поехали (все операции выполняются парно на двух серверах!!!):

1. Если джейлы вы раньше не использовали, но у вас есть ZFS, то первым делом перед инициализацией джейлов надо создать датасет для них:

     zfs create -o compression=lz4 system/usr/jails

     По поводу компрессии: lz4 очень быстрый алгоритм, потому его можно использовать везде по-умолчанию. Потерь по производительности нет, а всякие текстовые файлы и логи сжимаются практически в два раза. Учитывая то, что данные перед записью на диск сжимаются - растет скорость записи на диск - в общем одни плюсы.

2. Инициализируем jails' при помощи qjail:

     qjail install

3. Создаем датасет для jail с PostgreSQL:

     zfs create system/usr/jails/pgsql0X (pgsql 01 на первой ноде, pgsql02 на второй ноде)

4. А теперь самый важный датасет! Датасет для данных PostgreSQL.

     zfs create -p -o mountpoint=/usr/jails/pgsql0X/usr/local/pgsql -o recordsize=8K system/usr/jails/pgsql0X/pgsql (X=1 для первой ноды и X=2 для второй)

     Параметр recordsize=8K задан для того, что бы размер блока файловой системы и размер блока который пишет PostgreSQL совпадали. PostgreSQL пишет как раз блоками по 8Kb.

5. Создаем Jail:

     qjail create -4 em0|192.168.0.X,lo1|127.0.1.1 pgsql0X (X=1 для первой ноды и X=2 для второй)

     Интерфейс можно использовать основной, к которому у вас подключена локальная сеть, тогда вам не придется ничего больше делать, только открыть доступ в файрволле. Или можно использовать LoopBack - это более секьюрно, но тогда вам потребуется настроить NAT в PF или IPFW, для доступа к джейлу.
     Так же создаем LoopBack для самого джейла, что бы нам было проще взаимодействовать с Bucardo и PostgreSQL: lo1.
     Создать lo1 в системе очень просто, для этого нужно в файл /etc/rc.conf добавить: cloned_interfaces="lo1"
     И сделать: service netif cloneup
     После чего данный интерфейс появится в системе.

6. Джейл создали, теперь идет в /usr/local/etc/qjail.config, ищем там файл с конфигурацией нашего jail (pgsql01 или pgsql02) и вписываем туда параметр "allow.sysvipc;". Без этой опции PostgreSQL не сможет стартовать с ругательствами на невозможность выделения shared memory.

7. После этого запускаем jail:

     qjail start pgsql0X

8. После старта джейла можем действовать двумя путями.Через jexec (это правильнее) или через qjail console (что удобнее на первом этапе):

     jexec pgsql0X pkg update
     jexec pgsql0X pkg install postgresql94-server postgresql94-client postgresql94-plperl p5-Bucardo

9. Если вы планируете несколько PostgreSQL в нескольких джейлах в пределах одного сервера, то нужно перед запуском PgSQL сменить UID его пользователя на другой. UID пользователей pgsql должен различаться во всех джейлах, иначе у вас будут проблемы.

     vipw
     ee /etc/groups

10. После чего переписать права на все, что принадлежало этому юзеру:

     chown -R /usr/local/etc/pgsql

          или

     find / -uid 70 -exec sh -c 'chown pgsql {} \+'
     find / -gid 70 -exec sh -c 'chown :pgsql {} \+'

11. Активируем запуск PostgreSQL и Bucardo:

     sysrc postgresql_enable=YES
     sysrc bucardo_enable=YES (Только на одном из серверов, на втором Bucardo должен быть выключен до тех пор, пока на первой ноде будет работать Bucardo)

12. Далее инициализируем DB:

     service postgresql initdb
   
13. Меняем параметр отвечающий за прослушиваемые интерфейсы в postgresql.conf:

     listen_addresses = '*'

14. Меняем по вкусу pg_hba.conf, но не забываем разрешить полный доступ ко всем базам для пользователя bucardo c IP первого и второго джейлов:
  • # TYPE  DATABASE        USER            ADDRESS                 METHOD
    local   all             all                                     trust
    host    all             all             127.0.1.1/32            trust
    host    all             all             ::1/128                 trust
    host    all             bucardo         127.0.1.1/32            trust
    host    all             bucardo         <ip pg jail 01>/32      trust
    host    all             bucardo         <ip pg jail 02>/32      trust
15. Стартуем PostgreSQL:

      service postgresql start

16. Если PostgreSQL стартовал, подключаемся к нему по адресу LoopBack:

     psql -U pgsql -h 127.0.1.1 postgres

17. Меняем пароль для юзера pgsql и создаем все необходимое для Bucardo:
ALTER USER pgsql WITH PASSWORD 'PASSWORD';
CREATE USER bucardo WITH PASSWORD 'PASSWORD' SUPERUSER;
CREATE DATABASE bucardo WITH OWNER bucardo;
\q
18. Наконец-то переходим к Bucardo:

     Запускаем bucardo install и задаем все нужные параметры:
Current connection settings:
1. Host:           127.0.1.1
2. Port:           5432
3. User:           pgsql
4. Database:       bucardo
5. PID directory:  /var/run/bucardo
Enter a number to change it, P to proceed, or Q to quit: 
19. Жмем 'P' to proceed и все готово! Bucardo наполнит свою базу и можно переходить к конфигурированию Bucardo и базы Zabbix.

20. Создаем юзера и базу для zabbix:
CREATE USER zabbix WITH PASSWORD 'PASSWORD';
CREATE DATABASE zabbix OWNER zabbix;
21. Добавляем в pg_hba.conf записи разрешающие коннект к базе zabbix юзера zabbix из джейлов с заббиксом:
  • host    zabbix          zabbix    <ip zabbix jail 01>/32  md5
    host    zabbix          zabbix    <ip zabbix jail 02>/32  md5
  service postgresql reload

22. Наполняем базы zabbix. *.sql файлы берем из соседнего джейла с zabbix, который вы создали самостоятельно:
cat schema.sql | psql -U zabbix -h <PgSQL.HOST> zabbix
cat images.sql | psql -U zabbix -h <PgSQL.HOST> zabbix
cat data.sql | psql -U zabbix -h <PgSQL.HOST> zabbix
23. И вот тут выплывает мое упоминание про Primary key для всех реплицируемых таблиц... Дело в том, что в схеме заббикса некоторые таблицы не имеют Primary key, и тут нам надо решить, что мы собираемся делать. Тут у нас два варианта:

     A. Сделать, что бы всегда работал только один Zabbix, тогда нам нужно создать primary key для таблиц которые его не имеют:
ALTER TABLE history_uint ADD PRIMARY KEY (itemid,clock);
ALTER TABLE history_str ADD PRIMARY KEY (itemid,clock);
ALTER TABLE history ADD PRIMARY KEY (itemid,clock);
     B. Сделать два работающих мастера Zabbbix, тогда нам нужно исключить таблицы куда пишется статистика и всякое разное заббиксом на основе данных от мониторимых узлов. Вот список этих таблиц:
alerts
escalations
events
history
history_log
history_str
history_text
history_uint
trends
trends_uint

     Итак, конфигурим bucardo! Все что мы делали до этого, мы делали на двух нодах. Теперь же делаем все на первой!

25. Добавляем наши базы в Bucardo:
bucardo add database zabbix_pg01 dbname=zabbix dbhost=127.0.1.1 dbuser=bucardo dbpass=PASSWORD
bucardo add database zabbix_pg02 dbname=zabbix dbhost=<IP pgsql02> dbuser=bucardo dbpass=PASSWORD
bucardo list database
# Database: zabbix_pg01  Status: active  Conn: psql -U bucardo -d zabbix -h 127.0.1.1
# Database: zabbix_pg02  Status: active  Conn: psql -U bucardo -d zabbix -h <ip pgsql02>

25. Создаем группу баз. Source означает, что из этой базы будут браться измененные данные. Если вы напишете target, то в этом случае получите master-slave кластер.
bucardo add dbgroup zabbix_servers zabbix_pg01:source zabbix_pg02:source
# Created dbgroup "zabbix_servers"
# Added database "zabbix_pg01" to dbgroup "zabbix_servers" as source
# Added database "zabbix_pg02" to dbgroup "zabbix_servers" as source
26. Добавляем к репликации все:
bucardo add table all db=zabbix_pg01 relgroup=zabbix_herd
bucardo add sequences all db=zabbix_pg01 relgroup=zabbix_herd
27. А вот теперь, если вы выбрали второй вариант, с двума работающими Zabbix, нужно выкинуть из репликации некоторые таблицы, чтоб избежать конфликтов:

     bucardo remove table public.xxx

     bucardo remove table public.alerts
     bucardo remove table public.escalations
     bucardo remove table public.events
     bucardo remove table public.history
     bucardo remove table public.history_log
     bucardo remove table public.history_str
     bucardo remove table public.history_text
     bucardo remove table public.history_uint
     bucardo remove table public.trends
     bucardo remove table public.trends_uint

И независима от вашего варианта, исключаем базу dbversion, так как она не имеет ключа, и в целом нам не нужна ;)

     bucardo remove table public.dbversion


28. Добавляем теперь собственно синхронизацию:
bucardo add sync zabbix_sync herd=zabbix_herd dbs=zabbix_servers
29. Все, теперь у нас больше не будет конфликтов. Перезапускаем, или просто запускаем Bucardo. Предварительно убедившись, что оба PostgreSQL работают :)

     service bucardo start

30. Смотрим статус Bucardo:
bucardo status zabbix_sync
======================================================================
Last good                       : Aug 31, 2016 19:43:04 (time to run: 1s)
Rows deleted/inserted/conflicts : 1,864 / 1,864 / 1,140
Last bad                        : Aug 31, 2016 19:43:03 (time until fail: 1s)
Sync name                       : zabbix_sync
Current state                   : Good
Source relgroup/database        : zabbix_herd / zabbix_pg01
Tables in sync                  : 105
Status                          : Stalled
Check time                      : None
Overdue time                    : 00:00:00
Expired time                    : 00:00:00
Stayalive/Kidsalive             : Yes / Yes
Rebuild index                   : No
Autokick                        : Yes
Onetimecopy                     : No
Post-copy analyze               : Yes
Last error:                     : 
======================================================================
31. Тестируем! Можно попробовать менять содержимое таблицы users. На одной ноде что-то изменили, тут же поглядели на второй - измениться должно и там. Вот например:

  • Node1 psql -U zabbix -h 127.0.1.1 zabbix:
zabbix=> select userid,alias,surname from users;
 userid | alias |    surname
--------+-------+---------------
      1 | Admin | Administrator
      2 | guest |
(2 rows)

zabbix=> insert into users (userid,alias) values (3,'test');
INSERT 0 1

zabbix=> select userid,alias,surname from users;
 userid | alias |    surname
--------+-------+---------------
      1 | Admin | Administrator
      2 | guest |
      3 | test  |
(3 rows)
  • Node2 psql -U zabbix -h 127.0.1.1 zabbix:
zabbix=> select userid,alias,surname from users;
 userid | alias |    surname
--------+-------+---------------
      1 | Admin | Administrator
      2 | guest |
      3 | test  |
(3 rows)

zabbix=> delete from users where userid=3;
DELETE 1
  • Node1 psql -U zabbix -h 127.0.1.1 zabbix:
zabbix=> select userid,alias,surname from users;
 userid | alias |    surname
--------+-------+---------------
      1 | Admin | Administrator
      2 | guest |
(2 rows)
Все, если вы видели изменения на обеих нодах, то у вас все получилось !;)

Главное, не запускать два Bucardo на двух нодах одновременно! Сделайте, что бы 
Bucardo работал всегда на одной, а на второй (если у вас всего две ноды) он и не нужен будет. Если у вас больше двух нод, придумайте, как поднимать второй в случае падения первой. Я для себя придумал использовать UP/DOWN скрипты на CARP интерфейсе. Если пришел карп на другую ноду, то запускается Bucardo.

     Ну собственно все ;) По-скольку статья про Bucardo и PostgreSQL, то настройка заббикса будет вашим домашним заданием ;) Опишу лишь несколько тонкостей работы:

1. Вносить новые хосты и изменять все что угодно можно на двух заббиксах.
2. Агенты должны коннектиться к двум серверам одновременно, так как статистика не реплицируется, и ее нужно откуда-то брать. В целом это fault-tolerance на уровне приложения, за что zabbix честь и хвала!

Пример конфига zabbix клиента:

     Hostname=server1
     Include=/usr/local/etc/zabbix3/zabbix_agentd.d/
     LogFile=/var/log/zabbix/zabbix_agentd.log
     PidFile=/var/run/zabbix/zabbix_agentd.pid
     Server=zabbix01.example.com,zabbix02.example.com
     ServerActive=zabbix01.example.com,zabbix02.example.com
     LogFileSize=0
     Timeout=30

3. History, events и т.д. не реплицируются, так что конфликтов репликации не будет. Из-за этого на каждом хосте будут видны только свои события. Но это не беда, так как агенты все равно все будут дублировать в оба заббикса ;)

     Ну вот и все тонкости. Не забудьте засунуть zabbix так же в джейлы, а для джейла сделать датасет, только тюнинг датасету под заббикс не нужен.
     В конфиге джейла для zabbix нужно будет включить опцию: allow.raw_sockets = "1";
В целом это негативно сказывается на безопасности, но без нее zabbix не сможет посылать ICMP.

     Обращается каждый заббикс к своему PgSQL. Т.е. у нас получаются эдакие полностью независимые серверы, при этом реплицирующиеся между собой.
     Баланс входящих между двумя заббиксами я бы сделал при помощи двух CARP IP. Один IP мастер на одном сервере, второй на другом. Если один сервер падает, то IP уходит на запасной, и тогда zabbix все равно работает незаметно для пользователя.
     Естественно DNS имя должно ссылаться на оба CARP IP сразу.
     Баланс HTTP трафика можно сделать при помощи двух Nginx с двумя upstreams или при помощи RelayD. Смысл в том, что Nginx либо RelayD имеют два дестинейшена, и даже если CARP не переехал на другой хост, а Zabbix упал, то Nginx поймет это и отфорвардит вас на другой хост. RelayD для этого имеет отличную функцию "check http", которая может не только потыкаться в порт, но и запросить URL, тем самым вы можете проверить одним запросом не только живость HTTP, но и всей системы вместе с БД.

     В целом настройка CARP и RelayD чрезвычайно проста, подробнее о настройке лоад баланса в Nginx можно найти в поисковике, а по RelayD есть отличный пример на сайте: calomel.org

No comments :

Post a Comment