Percona. AlmaLinux 8. Percona XtraDB Cluster. Установка кластера + Балансировщик HAproxy
Описание
Кластер, состоящий из нескольких машин, на которых работает служба MySQL и синхронизация данных между ними.
По сути, это репликация MASTER-MASTER
Входные данные
192.168.199.183 - PXC1 - первая нода
192.168.199.178 - PXC2 - вторая нода
192.168.199.x - PXCx - последующие ноды
Настройка
Первая нода
PXC1 - первая нода
Имя хоста
hostnamectl set-hostname pxc1
Синхронизация времени
dnf install -y chrony
systemctl enable --now chronyd
Файрвол
firewall-cmd --permanent --add-port={3306,4444,4567,4568}/tcp
firewall-cmd --reload
Отключаем Selinux
sed -i 's/^SELINUX=permissive$/SELINUX=disabled/' /etc/selinux/config
Устанавливаем и настраиваем Percona
dnf install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
dnf module disable -y mysql
dnf install -y Percona-XtraDB-Cluster-full-57.x86_64
systemctl enable --now mysql.service
Смотрим и запоминаем временный пароль root
grep 'temporary password' /var/log/mysqld.log
... 2022-09-04T11:58:24.263495Z 1 [Note] A temporary password is generated for root@localhost: <fH7yuN#AUxn ...
Меняем пароль root
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'P@ssWord';
flush privileges;
exit
Перед первым запуском systemctl start mysql@bootstrap.service для того, чтобы к этой ноде могли подключиться новые ноды, необходимо остановить mysql
systemctl stop mysql.service
Пправим конфиг кластера
nano /etc/percona-xtradb-cluster.conf.d/wsrep.cnf
...
wsrep_cluster_address=gcomm://192.168.199.183,<СЮДА_МОЖНО_СРАЗУ_ДОБАВИТЬ_IP_НОВОЙ_ВТОРОЙ_НОДЫ> wsrep_node_address=192.168.199.183 wsrep_node_name=pxc1 wsrep_sst_auth="sstuser:PassWord" ...
Запускаем службу mysql@bootstrap.service для того, чтобы к этой ноде могли подключиться новые ноды
systemctl start mysql@bootstrap.service
Смотрим статус кластера с пока что одной нодой
mysql -u root -p
show status like 'wsrep_cluster%';
... +--------------------------+--------------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------------+ | wsrep_cluster_weight | 1 | | wsrep_cluster_conf_id | 1 | | wsrep_cluster_size | 1 | | wsrep_cluster_state_uuid | e6ac5722-2c48-11ed-b46a-3bcbaae4955b | | wsrep_cluster_status | Primary | +--------------------------+--------------------------------------+ 5 rows in set (0.01 sec) ...
Добавление дополнительных нод
На ВСЕХ уже существующих старых нодах
nano /etc/percona-xtradb-cluster.conf.d/wsrep.cnf
... wsrep_cluster_address=gcomm://192.168.199.183,...,<IP_НОВОЙ_НОДЫ> ...
systemctl restart mysql.service
PXC2 - новая нода (далее таким же образом добавляем PXC3, PXC4 и т.д.)
hostnamectl set-hostname pxc2
dnf install -y chrony
systemctl enable --now chronyd
firewall-cmd --permanent --add-port={3306,4444,4567,4568}/tcp
firewall-cmd --reload
sed -i 's/^SELINUX=permissive$/SELINUX=disabled/' /etc/selinux/config
dnf install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
dnf module disable -y mysql
dnf install -y Percona-XtraDB-Cluster-full-57.x86_64
Правим конфиг кластера
nano /etc/percona-xtradb-cluster.conf.d/wsrep.cnf
... wsrep_cluster_address=gcomm://192.168.199.183,192.168.199.178,<IP_НОВОЙ_НОДЫ> wsrep_node_address=<IP_НОВОЙ_НОДЫ> wsrep_node_name=pxc2 wsrep_sst_auth="sstuser:PassWord" ...
systemctl enable --now mysql.service
Смотрим статус кластера - ноды уже 2
mysql -u root -p
show status like 'wsrep_cluster%';
... +--------------------------+--------------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------------+ | wsrep_cluster_weight | 2 | | wsrep_cluster_conf_id | 2 | | wsrep_cluster_size | 2 | | wsrep_cluster_state_uuid | e6ac5722-2c48-11ed-b46a-3bcbaae4955b | | wsrep_cluster_status | Primary | +--------------------------+--------------------------------------+ 5 rows in set (0.00 sec) ...
Тестирование
! Важное замечание по логике работы кластера !
Ноды можно по-отдельности перезапускать, выключать/включать, добавлять, удалять.
Пока будет оставаться хотя бы одна работающая онлайн нода всё будет хорошо.
Но, если ВСЕ ноды будут выключены или уйдут в офлайн, а потом запущены, по одной или вместе - mysql ни на одной ноде кластера НЕ запустится.Чтобы вновь запустить кластер, необходимы следующие действия.
Если жива нода, на которой изначально запускался mysql@bootstrap.service
Просто запускаем на ней mysql@bootstrap.service
systemctl start mysql@bootstrap.service
А затем просто стартуем остальные ноды.
Если нода, на которой изначально запускался mysql@bootstrap.service неизвестна, или вышла из строя
Предположим что мы не помним или не знаем на какой ноде изначально запускался mysql@bootstrap.service (что более похоже на жизненную ситуацию), тогда делаем следующие шаги
На любой ноде кластера при запуске systemctl start mysql@bootstrap.service (если это не изначальная нода, на которой уже запускался mysql@bootstrap.service) увидим в логах:
tail -50 /var/log/mysqld.log
... 2022-09-05T08:18:02.170136Z 0 [ERROR] WSREP: It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates. To force cluster bootstrap with this node, edit the grastate.dat file manually and set safe_to_bootstrap to 1 ...
Поэтому надо сделать так:
nano /var/lib/mysql/grastate.dat
... safe_to_bootstrap: 1 ...
systemctl start mysql@bootstrap.service
Таким образом мы разрешим этой ноде быть начальной нодой, с которой остальные ноды заново потянут данные целиком.
Надо понимать что все данные, записанные на любую другую ноду, в том случае если она была онлайн и туда писались данные, ПРОПАДУТ, потому что теперь мы как бы создаем кластер заново, и ВСЕ данные будут скопированы с ноды, на которой запускается mysql@bootstrap.service.Далее можно запускать остальные ноды, снова перезапускать mysql на любой ноде, даже остановить mysql@bootstrap.service на которой запускали данный сервис и запустить штатно mysql (или просто перезапустить эту ноду).
Как и ранее, все ноды будут корректно синхрониться пока хотя бы одна нода остается онлайн.
Тестируем синхронизацию
PXC1
Создаем БД и наполняем её
mysql -u root -p
CREATE DATABASE test CHARACTER SET UTF8;
GRANT ALL PRIVILEGES ON test.* TO test@localhost IDENTIFIED BY 'test';
USE test;
CREATE TABLE pet (id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20), owner VARCHAR(20), species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);
SHOW TABLES;
DESCRIBE pet;
INSERT INTO `pet` SET `name` = 'Barsik', `species` = 'Cat' ;
PXC2
Проверяем что данные, записанные на первой ноде, появились на второй.
А также пишем данные в БД на второй ноде:
mysql -u root -p
USE test;
SELECT * FROM pet;
+----+--------+-------+---------+------+-------+-------+ | id | name | owner | species | sex | birth | death | +----+--------+-------+---------+------+-------+-------+ | 2 | Barsik | NULL | Cat | NULL | NULL | NULL | +----+--------+-------+---------+------+-------+-------+ 1 row in set (0.00 sec)
INSERT INTO `pet` SET `name` = 'Pushok', `species` = 'Cat' ;
PXC1
Проверяем что данные, записанные на второй ноде, появились на первой.
mysql -u root -p
SELECT * FROM pet;
... +----+--------+-------+---------+------+-------+-------+ | id | name | owner | species | sex | birth | death | +----+--------+-------+---------+------+-------+-------+ | 2 | Barsik | NULL | Cat | NULL | NULL | NULL | | 3 | Pushok | NULL | Cat | NULL | NULL | NULL | +----+--------+-------+---------+------+-------+-------+ 2 rows in set (0.00 sec) ...
Нетрудно видеть, что данные, записанные на одной ноде, синхронятся на все остальные
Балансировщик HAProxy
Описание
На машине pxc1 (как пример, на самом деле для балансировщика можно выделить и отдельную машину с AlmaLinux 8) установим балансировщик HAProxy.
Балансировщик будет принимать соединения от приложений, которым нужно получить данные из кластера MySQL, на порт 3307.
Подключаясь приложением к haproxy по сокету 192.168.199.183:3307, балансировщик будет перенаправлять соединение с типом балансировки ROUND-ROBIN на одну из 3-х машин кластера PXC, опираясь на доступность нод кластера по порту 9200 (подробности ниже).
Настройка
На всех нодах, входящих в кластер PXC
Следующие скрипты должны уже пристутствовать в системе (устанавливаются вместе с PXC):
Если они находятся по другим путям - скоипируйте их по необходимым путям
/usr/local/bin/clustercheck
/etc/xinetd.d/mysqlchk
Добавьте следующую строку в конец файла:
nano /etc/services
... mysqlchk 9200/tcp # mysqlchk
Установка и запуск xinetd, а в нем - mysqlchk
dnf install -y xinetd
systemctl enable --now xinetd
Проверка
netstat -luptn
... tcp6 0 0 :::9200 :::* LISTEN 3906/xinetd ...
На машине с HAProxy
Установка haproxy
dnf install -y haproxy
Конфигурация haproxy
nano /etc/haproxy/haproxy.cfg
global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 4096 uid 99 gid 99 #daemon debug #quiet defaults log global mode http option tcplog option dontlognull retries 3 redispatch maxconn 2000 contimeout 5000 clitimeout 50000 srvtimeout 50000 listen pxc_cluster bind 0.0.0.0:3307 balance roundrobin mode tcp option tcpka server pxc1 192.168.199.183:3306 check port 9200 inter 12000 rise 3 fall 3 server pxc2 192.168.199.178:3306 check port 9200 inter 12000 rise 3 fall 3 server pxc3 192.168.199.179:3306 check port 9200 inter 12000 rise 3 fall 3
Запуск haproxy
systemctl enable --now xinetd
Тестирование
На одной из нод кластера, например PXC1 (остальные синхронизируют данную настройку)
Создадим пользователя mysql для получения данных через удаленный запрос
mysql -u root -p
GRANT ALL PRIVILEGES ON test.* TO cless@"%" IDENTIFIED BY 'PassWord';
На любой сторонней машине Linux
Подключаемся через сокет HAProxy на машину HAProxy
mysql -h192.168.199.183 --port=3307 -N -ucless -pPassWord test -e "SELECT * FROM pet"
... | 2 | Barsik | NULL | Cat | NULL | NULL | NULL | ...
Готово. Приложение получает данные из кластера по запросу через HAProxy
Дополнительно
Советы от опытного товарища, приведу прямые цитаты и/или краткий пересказ: https://habr.com/ru/post/158377/
Предположим что у нас 3 ноды в кластере - A, B, C
- Для каждой ноды использовать отдельный физический сервер (или, если ноды на виртуальных машинах, создавать каждую на отдельном физическом сервере)
- Node A используется в качестве Reference (Backup) Node, которую не будут загружать запросами наши приложения. При этом она будет полноправным членом кластера, и участвовать в репликации. Это делается в связи с тем, что в случае сбоя в кластере или нарушения целостности данных мы будем иметь ноду, которая почти наверняка содержит наиболее консистентные данные, которые приложения просто не могли порушить из-за отсутствия доступа. Возможно, это выглядит расточительным расходованием ресурсов, но для нас 99% надежность данных все же важнее чем доступность 24/7. Именно эту ноду мы будем использовать для SST — State Snapshot Transfer — автоматического слива дампа на присоединяемую к кластеру новую ноду или поднимаемую после сбоя. Кроме того, Node A — отличный кандидат для сервера, откуда мы будем снимать стандартные периодические резервные копии.
- Node B и Node C это рабочие лошадки, которые держат нагрузку, но при этом операции записи берет на себя только одна из них. Такова рекомендация многих специалистов, и ниже я остановлюсь на этом вопросе подробно.
- Использовать HAProxy, который балансирует нагрузку записи между B и C
- Почему рекомендуется писать на одну ноду из всех доступных в кластере? Ведь казалось бы, это противоречит идее мульти-мастер репликации. Когда я впервые увидел эту рекомендацию, то очень расстроился. Я представлял себе multi-master таким образом, что можно ни о чем не заботясь писать на любой узел, и изменения гарантированно применятся синхронно на всех нодах. Но суровые реалии жизни таковы, что при таком подходе возможно повление cluster-wide deadlocks. Особенно велика вероятность в случае параллельного изменения одних и тех же данных в длинных транзакциях. Т.к. я не являюсь пока экспертом в этом вопросе, не могу объяснить этот процесс на пальцах. Но есть хорошая статья, где эта проблема освещена подробнейшим образом: Percona XtraDB Cluster: Multi-node writing and Unexpected deadlocks. Мои собственные тесты показали, что при агрессивной записи на все ноды они ложились одна за другой, оставляя рабочей только Reference Node, т.е. по факту можно сказать, что кластер прекращал работу. Это, безусловно минус такой конфигурации, ведь третий узел мог бы в этом случае взять нагрузку на себя, но зато мы уверены, что данные там в целости и сохранности и в самом крайнем случае мы можем вручную запустить его в работу в режиме одиночного сервера.