Всем привет!

Как-то раз появилась по работе задача — есть вроде как настроенный тестовый innoDB cluster на нескольких виртуалках с centos 7.5, надо его поизучать и добавить еще пару нод. Можно ломать и издеваться как угодно. Звучит заманчиво.

До этого у меня опыта работы с этим кластером не было, ну да гугл в помощь.
За несколькими исключениями, все ссылки что в нем, что в яндексе вели либо на dev.mysql.com, либо на эту статью на Хабре. Вроде как по ней и был настроен кластер из двух нод.

Чтож, статью я прочитал, несколько удивился сложности добавления нод и отсутствию многих подробностей, ну да ладно. Добавил с грехом пополам новую ноду (часть команд не пригодилась, часть вообще все ломала), после чего начал экспериментировать с перезапуском нод и т.д.

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

При создании нового кластера к сожалению возникла не меньшая куча проблем и нестыковок. Возможно дело в версиях программ, я пробовал mysql 5.7. Возможно в дистрибутиве. В итоге я прекратил бездумные потуги делать все по бумажке и стал настраивать его методом тыка. И гугла.

Пара приятных вечеров и ночей и кластер собрался и даже отказался рушиться.
При этом способ его создания заметно отличался от предыдущих попыток и мне захотелось им поделиться, т.к. на просторах интернета других актуальных подробных понятных инструкций по настройке inndoDB cluster я не нашел.

Итак, имеем три одинаковых виртуалки со свежеустановленным Centos 7.5 minimal 1804 и отключенными selinux и firewalld:

1.1.1.1
1.1.1.2
1.1.1.3

По работе я использовал mysql 5.7, так что ее и используем. Начнем с 1.1.1.1:

1. Установим репозиторий mysql-community:

rpm -i https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm

Выключим репозиторий для 8, включим для 5.7 и проверим — если все ок, то устанавливаем mysql:

yum install yum-utils
yum-config-manager --disable mysql80-community
yum-config-manager --enable mysql57-community
yum repolist
yum install mysql-community-server mysql-shell

2. Приводим /etc/my.cnf к такому виду:

[mysqld]

datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock

symbolic-links=0

log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

bind-address=0.0.0.0
port=3301
# Replication part
server_id=1
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
plugin-load = group_replication.so
# Group replication part
transaction_write_set_extraction=XXHASH64
loose-group_replication_start_on_boot = OFF
loose-group_replication_local_address = 1.1.1.1:33011
loose-group_replication_bootstrap_group = OFF
report_port = 3301
report_host = 1.1.1.1

Здесь 3301 это порт, на котором будет слушать mysql, а 33011 порт, на котором ноды общаются между собой.

3. Запустим mysql и выполним предварительную настройку:

systemctl start mysqld
grep 'password' /var/log/mysqld.log
mysql_secure_installation

4. Ну и создадим кластер, а так же отдельного пользователя для управления им. Если вы знаете заранее ip-адреса нод, то можно их сразу перечислить в ipWhitelist. Сделаем вид, что мы пока не знаем про 1.1.1.2. и 1.1.1.3:

mysqlsh
> \c 127.0.0.1:3301
> dba.configureLocalInstance("127.0.0.1:3301", {mycnfPath: "/etc/my.cnf", clusterAdmin: "cladmin", clusterAdminPassword: "SomePassword!123"})
> \c cladmin@1.1.1.1:3301
> dba.checkInstanceConfiguration()
> cl=dba.createCluster('TestCluster', {ipWhitelist: '1.1.1.1'})
> dba.configureLocalInstance()
> cl.status()

Готово! cl.status должен вывести что-то типа этого:

{
    "clusterName": "TestCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "1.1.1.1:3301",
        "ssl": "REQUIRED",
        "status": "OK_NO_TOLERANCE",
        "statusText": "Cluster is NOT tolerant to any failures.",
        "topology": {
            "1.1.1.1:3301": {
                "address": "1.1.1.1:3301",
                "mode": "R/W",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            }
        }
    },
    "groupInformationSourceMember": "mysql://cladmin@1.1.1.1:3301"
}

При изменении кластера надо будет выполнять на всех нодах локально команду dba.configureLocalInstance() для сохранения изменений:

WARNING: On instance '1.1.1.1:3301' membership change cannot be persisted since MySQL version 5.7.23 does not support the SET PERSIST command (MySQL version >= 8.0.11 required). Please use the <Dba>.configureLocalInstance command locally to persist the changes.

Т.к. мы собираемся добавить еще парочку нод, то не закрываем связь с сервером 1.1.1.1, он нам еще пригодится.

Теперь попробуем добавить в кластер ноду 1.1.1.2. Для этого выполняем на ней все те же команды до 3 шага включительно, не забывая поменять server_id, loose-group_replication_local_address и report_host.

4. Выполняем на 1.1.1.2:

mysql -p
> set GLOBAL group_replication_allow_local_disjoint_gtids_join=ON;

Я пытался задавать эту переменную через mysqlsh, переключаясь в режим sql, но действия там никак не влияли на нее в mysql. Далее:

mysqlsh
> \c 127.0.0.1:3301
> dba.configureLocalInstance("127.0.0.1:3301", {mycnfPath: "/etc/my.cnf", clusterAdmin: "cladmin", clusterAdminPassword: "SomePassword!123"})
> \c cladmin@1.1.1.2:3301
> dba.checkInstanceConfiguration()


5. Возвращаемся к первой ноде 1.1.1.1. Если закрыли соединение, то можно быстро подключиться к кластеру так:

mysqlsh --uri cladmin@1.1.1.1:3301 --cluster
> \sql
> STOP GROUP_REPLICATION;
> SET GLOBAL group_replication_ip_whitelist="1.1.1.1,1.1.1.2";
> START GROUP_REPLICATION;
> \js
> cluster.addInstance('cladmin@1.1.1.2:3301', {ipWhitelist: '1.1.1.1,1.1.1.2'})
> cluster.status()

Почему-то при добавлении ноды без опции ipWhitelist он ей не передается автоматически, поэтому указываем вручную.
Если whitelist у вас изначально настроен на все ноды или подсеть, то команды в sql режиме можно пропустить.

Не забываем выполнить dba.configureLocalInstance() на всех нодах для сохранения конфигурации.

Получился кластер их двух нод:

{
    "clusterName": "TestCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "1.1.1.1:3301",
        "ssl": "REQUIRED",
        "status": "OK_NO_TOLERANCE",
        "statusText": "Cluster is NOT tolerant to any failures.",
        "topology": {
            "1.1.1.1:3301": {
                "address": "1.1.1.1:3301",
                "mode": "R/W",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "1.1.1.2:3301": {
                "address": "1.1.1.2:3301",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            }
        }
    },
    "groupInformationSourceMember": "mysql://cladmin@1.1.1.1:3301"
}

Чтож, кластер из двух нод есть, но в режиме «Cluster is NOT tolerant to any failures.»
Добавим третью, алгоритм в принципе не отличается от добавления второй.

Если вам опять требуется поменять whitelist, то выполнять команды надо на r/w ноде, т.к. на r/o нодах это ни к чему вроде не приводит. При этом r/o ноды отвалятся и их надо будет пересоединить, попутно сообщив новый whitelist.
В нашем случае:

> cluster.rejoinInstance('cladmin@1.1.1.2:3301', {ipWhitelist: '1.1.1.1,1.1.1.2,1.1.1.3'})

Ну и опять же не забываем выполнить dba.configureLocalInstance() на всех нодах для сохранения конфигурации.

Кластер из трех нод выглядит так:

{
    "clusterName": "TestCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "1.1.1.1:3301",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "1.1.1.1:3301": {
                "address": "1.1.1.1:3301",
                "mode": "R/W",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "1.1.1.2:3301": {
                "address": "1.1.1.2:3301",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "1.1.1.3:3301": {
                "address": "1.1.1.3:3301",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            }
        }
    },
    "groupInformationSourceMember": "mysql://cladmin@1.1.1.1:3301"
}

В случае, если кластер развалился до состояния одной ноды, его надо будет запускать с параметром loose-group_replication_bootstrap_group = ON в /etc/my.cnf
После запуска параметр надо будет выключить обратно, иначе эта нода всегда будет отделяться от кластера и поддерживать свой.

Установка mysql-router хорошо описана тут, так что дублировать смысла не вижу.

На этом все вроде все, надеюсь кому-то мой опыт пригодится.

Спасибо за внимание.

Комментарии (13)


  1. SergeyMax
    17.09.2018 19:56

    1.1.1.1 — это IP-адрес публичного DNS-сервера CloudFlare. Просто вспомнилось.


  1. kolu4iy
    17.09.2018 20:25

    Знаете, нам очень зашёл galera cluster на mariadb. Минусы, конечно, есть: master-master обязан считать транзакцию завершенной только тогда, когда ее совершили все ноды, однако конкретно у нас сеть (основной источник латентности) в порядке, и потому проблем не доставляет. Зато все остальное… Голова совершенно не болит. Никто не мешает остальных мастеров использовать как read only, не внося при этом конфликтов данных уровня кластера. Вот maxscale для балансировки вносил проблемы, было дело. Сейчас этот кластер собран на банальном keepalived и одну реальную аварию (не тесты) прекрасно пережил. Рекомендую, если у вас условия позволяют. Как готовить — статьи на хабре есть.


    1. SergeyMax
      17.09.2018 21:04

      master-master обязан считать транзакцию завершенной только тогда, когда ее совершили все ноды

      А если одна из нод выйдет из строя, то что в этом случае произойдёт?


      1. kolu4iy
        18.09.2018 10:40

        Ничего не произойдёт. Если keepalived публиковал её как мастера, то адрес переедет на другую ноду. Они все мастер. Когда нода поднимется, произойдёт full state transfer (если остановка была длительной), либо incremental state transfer, если остановка была недолгой (патч-корд по ошибке выдернули). А потом keepalived вернёт адрес на место.
        Основное уловие: в работающем состоянии — нечётное число нод, либо «наблюдатель» при чётном числе нод, во избежании возникновения т.н. split brain состояния. Наблюдатель — galera arbitrator, ресурсов для работы практически не требует.

        Если вопрос был про транзакцию — она откатится, вернув ошибку на уровень приложения, которое должно будет её повторить. Также есть настройка, сколько раз пытаться закоммитить транзакцию (3 по-умолчанию). Как правило, до ошибок уровня приложения не доходит: со второй попытки транзакция падает в «усечённый» кластер.


        1. SergeyMax
          18.09.2018 11:13

          Всё-таки не совсем понятно. Будут ли комититься транзакции в базе, если одна из нод выключена?


          1. n1nj4p0w3r
            18.09.2018 13:09

            Будут, а на чтение база будет доступна до последней ноды


            1. kolu4iy
              18.09.2018 15:42

              Не только на чтение. На запись тоже.


              1. n1nj4p0w3r
                19.09.2018 00:01

                На запись только в случае наличия большинства голосов кворума, либо если отключить саму галеру.


                1. kolu4iy
                  19.09.2018 10:46

                  Вы правы, однако в данном случае разговор шёл про одну ноду. Исходя из чего предполагаем наличие кворума — у нас же больше двух нод, либо две + арбитратор.


    1. larrabee
      18.09.2018 16:51

      master-master обязан считать транзакцию завершенной только тогда, когда ее совершили все ноды

      Я не уверен как реализовано в MariaDB Galera, но в классическом Galera Cluster и Percona GC это не так. Каждая транзакция валидируется на всех нодах при коммите на отсутствие конфликтов, а репликация самих изменений выполняется уже после коммита.


      1. kolu4iy
        19.09.2018 10:45

        Да, вы правы.


    1. nikitych
      19.09.2018 22:33

      Про сравнение galera cluster и InnoDB Cluster рекомендую прочитать комментарии под постом lefred.be/content/mysql-group-replication-is-sweet-but-can-be-sour-if-you-misunderstand-it. Если кратко, то они немного про разное. galera пытается сделать вид что она синхронная и это почти всегда удается, кроме некоторых крайних случаев, за счет производительности. mysql говорит, раз формальной синхронности нет, не будем и пытаться зато выдадим производительность, как следствие при неудачных обстоятельствах ноды могут разбежаться на тысячи транзакций. И ваше приложение должно быть к этому готово, т.е. данные всегда консистентны, но не всегда свежи.
      Если прямо нужен честный, межнодовый read_your_own_writes можно посмотреть на MySQL NDB Cluster и список его ограничений появившихся не на пустом месте.
      dev.mysql.com/doc/refman/5.7/en/mysql-cluster-limitations.html


      1. kolu4iy
        20.09.2018 09:18

        Потому я и писал, что «конкретно нам». И мы её рассматриваем как синхронный мастер-слейв, дабы не переписывать вагон легаси, а просадку производительности нивелируем хорошей сетью и симметричным железом. Спасибо за уточнение — это действительно не очевидно из моего комментария.