Percona. AlmaLinux 8. Percona XtraDB Cluster. Установка кластера + Балансировщик HAproxy

Материал из Wiki
Перейти к навигации Перейти к поиску


Описание

Кластер, состоящий из нескольких машин, на которых работает служба 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, т.е. по факту можно сказать, что кластер прекращал работу. Это, безусловно минус такой конфигурации, ведь третий узел мог бы в этом случае взять нагрузку на себя, но зато мы уверены, что данные там в целости и сохранности и в самом крайнем случае мы можем вручную запустить его в работу в режиме одиночного сервера.