Моя шпаргалка :)

Мануалы и настройки => Настройки *nix (почта, web, система etc) => Тема начата: George от Окт. 13, 2026, 09:13

Название: MariaDB Galera Cluster в Docker Compose на трёх серверах
Отправлено: George от Окт. 13, 2026, 09:13
MariaDB Galera Cluster в Docker Compose на трёх серверах

Платформа: MariaDB 10.11, Galera 4, Docker Compose, Linux.

Схема кластера:

isp      — 10.10.1.43
web-01   — 10.10.1.41
web-02   — 10.10.1.45

На каждом сервере работает отдельный контейнер mariadb-galera. Данные хранятся в локальном каталоге хоста. Контейнеры используют обычную Docker-сеть bridge, а необходимые порты публикуются на IP соответствующего сервера.

Все три узла участвуют в одном кластере ispconfig_galera. При подключении нового или повторно инициализированного узла Galera автоматически передаёт ему данные с действующего участника через SST.

1. Подготовка серверов

На каждом сервере должны быть установлены Docker Engine и Docker Compose Plugin.

Проверяем:

docker --version
docker compose version

Подготавливаем каталоги на каждом сервере:

mkdir -p /srv/docker/mariadb/conf.d
mkdir -p /srv/docker/mariadb/data
cd /srv/docker/mariadb

Используем одинаковые пути на всех узлах:

/srv/docker/mariadb/
├── compose.yml
├── .env
├── conf.d/
│   └── galera.cnf
└── data/

Каталог data содержит файлы базы MariaDB. Не удалять и не очищать его при обычном перезапуске контейнера.

2. Конфигурация Galera

На каждом сервере создаём файл:

/srv/docker/mariadb/conf.d/galera.cnf

Общая конфигурация для всех узлов:

[mysqld]
binlog_format=ROW
default-storage-engine=InnoDB
innodb_autoinc_lock_mode=2
bind-address=0.0.0.0

wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name=ispconfig_galera
wsrep_cluster_address=gcomm://10.10.1.43,10.10.1.41,10.10.1.45

wsrep_sst_auth=sstuser:CHANGE_ME
wsrep_sst_method=mariabackup

Вместо CHANGE_ME указывается фактический пароль пользователя SST. Он должен совпадать с паролем пользователя sstuser, созданного в MariaDB. Не публикуйте реальные пароли в открытых инструкциях.

Для каждого сервера добавляем собственные параметры.

Сервер isp — 10.10.1.43:

wsrep_node_address=10.10.1.43
wsrep_node_name=isp
wsrep_provider_options="ist.recv_addr=10.10.1.43:4568;ist.recv_bind=0.0.0.0:4568"
wsrep_sst_receive_address=10.10.1.43:4444

Сервер web-01 — 10.10.1.41:

wsrep_node_address=10.10.1.41
wsrep_node_name=web-01
wsrep_provider_options="ist.recv_addr=10.10.1.41:4568;ist.recv_bind=0.0.0.0:4568"
wsrep_sst_receive_address=10.10.1.41:4444

Сервер web-02 — 10.10.1.45:

wsrep_node_address=10.10.1.45
wsrep_node_name=web-02
wsrep_provider_options="ist.recv_addr=10.10.1.45:4568;ist.recv_bind=0.0.0.0:4568"
wsrep_sst_receive_address=10.10.1.45:4444

Почему важен параметр ist.recv_bind:

При использовании Docker bridge IP хоста не назначен непосредственно сетевому интерфейсу контейнера. Если Galera пытается слушать порт IST на IP хоста внутри контейнера, появляется ошибка:

Failed to open IST listener
Cannot assign requested address

Параметр ist.recv_addr указывает адрес узла, по которому донор должен передавать IST. Параметр ist.recv_bind задаёт адрес прослушивания внутри контейнера. Прослушивание на 0.0.0.0 позволяет использовать опубликованный Docker-порт, сохраняя доступность узла по IP хоста.

3. Файл .env

На каждом сервере создаём файл:

/srv/docker/mariadb/.env

Содержимое:

MARIADB_ROOT_PASSWORD=ЗДЕСЬ_НАДЁЖНЫЙ_ПАРОЛЬ_ROOT

Ограничиваем доступ к файлу:

chmod 600 /srv/docker/mariadb/.env

Переменная MARIADB_ROOT_PASSWORD нужна официальному образу MariaDB при первоначальной инициализации пустого каталога данных. На уже инициализированной базе изменение этой переменной само по себе не меняет существующий SQL-пароль root.

Если контейнер уже настроен и пароль хранится в его переменных окружения, его можно перенести в .env, не выводя пароль на экран:

cd /srv/docker/mariadb
umask 077
docker inspect mariadb-galera
  --format '{{range .Config.Env}}{{println .}}{{end}}'
  | grep '^MARIADB_ROOT_PASSWORD=' > .env
chmod 600 .env

Проверка наличия переменной без раскрытия секрета:

grep -q '^MARIADB_ROOT_PASSWORD=.' .env
  && echo "Пароль сохранён"
  || echo "Пароль не найден"

Если пароль содержит специальные символы, перед запуском нужно убедиться, что он корректно интерпретируется форматом env-файла Compose.

4. Docker Compose

На каждом сервере создаём файл:

/srv/docker/mariadb/compose.yml

Конфигурация для isp:

services:
  mariadb-galera:
    image: mariadb:10.11
    container_name: mariadb-galera
    restart: unless-stopped
    command: ["mariadbd"]
    env_file:
      - .env
    ports:
      - "10.10.1.43:3306:3306"
      - "10.10.1.43:4444:4444"
      - "10.10.1.43:4567:4567/tcp"
      - "10.10.1.43:4567:4567/udp"
      - "10.10.1.43:4568:4568"
    volumes:
      - /srv/docker/mariadb/conf.d:/etc/mysql/conf.d:ro
      - /srv/docker/mariadb/data:/var/lib/mysql

Для остальных узлов используется тот же файл, но IP в секции ports меняется на IP соответствующего сервера:


Путь к конфигурации и каталогу данных остаётся одинаковым.

Проверяем синтаксис и запускаем:

cd /srv/docker/mariadb
docker compose -f compose.yml config -q
docker compose -f compose.yml up -d

5. Сетевые порты

Между тремя серверами необходимо разрешить соединения:

3306/tcp — клиентские подключения MariaDB
4444/tcp — SST, полная передача состояния
4567/tcp — Galera replication
4567/udp — Galera replication
4568/tcp — IST, инкрементальная передача состояния

Порты должны быть доступны между IP узлов. Не следует без необходимости открывать их для всей внешней сети. Доступ к 3306 также лучше ограничить доверенными адресами.

6. Первоначальный запуск кластера

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

Шаг 1. Выбираем первый узел.

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

Шаг 2. Однократно запускаем первый узел с параметром bootstrap.

В compose.yml временно заменяем:

command: ["mariadbd"]

на:

command: ["mariadbd", "--wsrep-new-cluster"]

Запускаем:

docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

После формирования нового Primary Component возвращаем обычную команду:

command: ["mariadbd"]

и применяем Compose:

docker compose -f compose.yml up -d

Внимание: параметр --wsrep-new-cluster нельзя использовать при обычном перезапуске существующего кластера или на каждом узле. Он создаёт новый кластер, а не присоединяет узел к уже работающему.

Шаг 3. Подключаем второй и третий узлы.

Убедившись, что первый узел работает, запускаем на web-01 и web-02 обычный Compose:

cd /srv/docker/mariadb
docker compose -f compose.yml config -q
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

Пустой каталог данных на присоединяемом узле должен быть инициализирован образом MariaDB. После этого Galera сможет запросить SST у действующего участника и получить актуальные данные.

Если узел не пустой, Galera сама определит, можно ли использовать IST, или потребуется SST.

7. Проверка состояния кластера

На любом узле выполняем:

docker exec -it mariadb-galera mariadb -uroot -p

После ввода пароля выполняем SQL-запрос:

SHOW GLOBAL STATUS
WHERE Variable_name IN (
  'wsrep_cluster_status',
  'wsrep_cluster_size',
  'wsrep_local_state_comment',
  'wsrep_ready',
  'wsrep_connected'
);

Для исправного трёхузлового кластера ожидаются значения:

wsrep_cluster_status        Primary
wsrep_cluster_size          3
wsrep_local_state_comment   Synced
wsrep_ready                 ON
wsrep_connected             ON

Также можно проверить логи:

docker logs --tail 100 mariadb-galera

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

8. Перевод уже работающего контейнера с host на bridge

Если MariaDB ранее запускалась с network_mode host или через docker run, её можно перевести на Compose без переинициализации базы.

Шаг 1. Сохранить текущий Compose и конфигурацию, проверить пароль root и текущие переменные контейнера.

Шаг 2. Подготовить compose.yml с env_file, публикацией портов и bind-mount существующего каталога данных.

Шаг 3. Проверить корректность адресов в galera.cnf. Для bridge нельзя оставлять IST listener, который пытается привязаться к IP хоста внутри контейнера.

Шаг 4. Проверить Compose, затем пересоздать контейнер:

docker compose -f compose.yml config -q
docker compose -f compose.yml down
docker compose -f compose.yml up -d

При этом нельзя удалять каталог data. Команда docker compose down без -v не удаляет bind-mounted каталог базы, однако перед изменением сетевой конфигурации всё равно рекомендуется сделать резервную копию.

Шаг 5. Проверить логи и статус Galera. Сам факт запуска контейнера ещё не означает, что узел синхронизирован.

9. Повторное подключение узла с чистого состояния

Эта процедура подходит только тогда, когда необходимо заново присоединить один узел, а остальные участники образуют исправный Primary Component.

Нельзя выполнять её на узле, который содержит единственную актуальную копию данных.

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

Шаг 2. Остановить контейнер на восстанавливаемом узле:

cd /srv/docker/mariadb
docker compose -f compose.yml down

Шаг 3. Сохранить старый каталог данных целиком и создать новый пустой каталог:

mv data "data.backup-$(date +%Y%m%d-%H%M%S)"
mkdir data

Команда mv должна выполняться только после остановки контейнера. Резервную копию не удалять до проверки полного восстановления.

Шаг 4. Убедиться, что .env существует и содержит MARIADB_ROOT_PASSWORD. Проверить конфигурацию Galera, особенно wsrep_node_address, wsrep_node_name, wsrep_sst_receive_address и wsrep_provider_options.

Шаг 5. Запустить контейнер обычным способом, без bootstrap:

docker compose -f compose.yml config -q
docker compose -f compose.yml up -d
docker logs --tail 150 mariadb-galera

Шаг 6. Дождаться завершения SST/IST и проверить wsrep_local_state_comment=Synced, wsrep_ready=ON и размер кластера 3.

Важный случай из практики: после неудачного SST в каталоге данных могут остаться маркеры, например sst_in_progress и wsrep_sst.pid. Повторный запуск иногда приводит к сообщению Previous SST is not completed. Если процесс SST действительно завершился аварийно, безопаснее остановить контейнер, сохранить весь старый каталог и повторно присоединить узел с пустым data, как описано выше. Не следует просто удалять маркеры, пока SST ещё работает.

10. Если все узлы кластера остановились

Если остановились все три сервера, нельзя без проверки запускать каждый из них с --wsrep-new-cluster.

Сначала нужно определить узел с наиболее актуальным состоянием данных. При штатной остановке проверяют safe_to_bootstrap в grastate.dat. После аварии может потребоваться восстановление позиции через wsrep_recover и сравнение seqno.

Только после выбора правильного узла выполняют bootstrap на нём. Остальные узлы запускают обычным способом и дают им присоединиться к восстановленному кластеру.

Нельзя одновременно создавать независимые Primary Component на разных узлах: это может привести к расхождению данных.

11. Полезные команды

Статус контейнера:

docker inspect mariadb-galera
  --format 'Network={{.HostConfig.NetworkMode}} Status={{.State.Status}}'

Последние сообщения MariaDB:

docker logs --tail 100 mariadb-galera

Перезапуск контейнера через Compose:

docker compose -f compose.yml restart

Проверка конфигурации Compose:

docker compose -f compose.yml config -q

Проверка опубликованных портов:

docker port mariadb-galera

12. Итоговая схема


Документация:

MariaDB Galera Cluster — установка и первоначальный запуск (https://mariadb.com/docs/galera-cluster/galera-management/installation-and-deployment/getting-started-with-mariadb-galera-cluster)

MariaDB — параметры wsrep_provider_options (https://mariadb.com/docs/galera-cluster/reference/wsrep-variable-details/wsrep_provider_options)

MariaDB — восстановление кластера после потери Primary Component (https://mariadb.com/docs/galera-cluster/high-availability/resetting-the-quorum-cluster-bootstrap)
Название: От: MariaDB Galera Cluster в Docker Compose на трёх серверах
Отправлено: George от Окт. 13, 2026, 09:18
13. Восстановление всего кластера после отключения питания

Этот сценарий предназначен для ситуации, когда одновременно выключились все три сервера MariaDB Galera, например после пропадания электропитания.

Узлы кластера:

isp      — 10.10.1.43
web-01   — 10.10.1.41
web-02   — 10.10.1.45

Важное правило: нельзя сразу запускать bootstrap на всех узлах или произвольно выбирать первый сервер. Сначала нужно убедиться, что Galera не восстановила кластер автоматически, а затем определить узел с наиболее актуальным состоянием базы.

Шаг 1. Запускаем серверы и проверяем автоматическое восстановление

После восстановления электропитания убедитесь, что Docker запущен на всех трёх серверах.

На каждом сервере:

cd /srv/docker/mariadb
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

Подождите, пока узлы попытаются соединиться друг с другом.

Проверьте состояние Galera:

docker exec mariadb-galera mariadb -uroot -p -e "
SHOW GLOBAL STATUS
WHERE Variable_name IN (
'wsrep_cluster_status',
'wsrep_cluster_size',
'wsrep_local_state_comment',
'wsrep_ready',
'wsrep_connected'
);"

Если все три узла самостоятельно восстановили Primary Component, размер кластера равен 3, а каждый узел находится в состоянии Synced, ручной bootstrap не нужен.

Если все контейнеры работают, но кластер не восстановился и узлы не могут сформировать Primary Component, переходите к следующему шагу.

Шаг 2. Останавливаем MariaDB на всех трёх узлах

На каждом сервере:

cd /srv/docker/mariadb
docker compose -f compose.yml down

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

Не удаляйте каталоги data, не очищайте grastate.dat и не удаляйте файлы gvwstate.dat.

Шаг 3. Проверяем состояние каждого узла

На каждом сервере выполняем:

cd /srv/docker/mariadb
cat data/grastate.dat

Пример содержимого:

# GALERA saved state
version: 2.1
uuid:    b42f59d4-c70d-11f1-8971-9a96395e2946
seqno:   12345
safe_to_bootstrap: 0

Здесь важны два поля:


Если кластер остановился штатно, ищите узел с safe_to_bootstrap: 1.

При внезапном отключении питания у всех узлов этот параметр может быть равен 0, а seqno — -1. В таком случае по одному файлу grastate.dat определить самый актуальный узел не получится. Нужно выполнить восстановление позиции.

Шаг 4. Восстанавливаем последнюю позицию транзакций

Команду необходимо выполнить отдельно на каждом сервере, пока основной контейнер остановлен.

На isp:

cd /srv/docker/mariadb
docker compose -f compose.yml run --rm --no-deps mariadb-galera mariadbd --wsrep-recover 2>&1 | tee /tmp/wsrep-recover.log

На web-01 и web-02 выполняется та же команда.

Она запускает MariaDB для восстановления последней известной позиции Galera, используя существующий каталог данных. Это не команда bootstrap и не должна запускать обычный рабочий экземпляр базы.

В выводе найдите строку вида:

WSREP: Recovered position: b42f59d4-c70d-11f1-8971-9a96395e2946:12345

Если вывод не сохранился в терминале, посмотрите файл:

cat /tmp/wsrep-recover.log

Сравните восстановленные позиции со всех трёх узлов.

Выбирать нужно узел с наибольшим seqno в рамках одного и того же UUID состояния Galera.

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

Шаг 5. Разрешаем bootstrap только на выбранном узле

Предположим, что по результатам проверки наиболее актуальным оказался web-01.

На остальных двух узлах ничего не меняем. Их контейнеры должны оставаться остановленными.

На web-01 сначала сохраняем резервную копию файла состояния:

cd /srv/docker/mariadb
cp -a data/grastate.dat data/grastate.dat.before-bootstrap

Затем открываем файл:

vi data/grastate.dat

В нём выставляем:

safe_to_bootstrap: 1

Другие поля, включая UUID и seqno, не изменяем.

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

Шаг 6. Выполняем bootstrap выбранного узла

На web-01 временно изменяем в compose.yml:

command: ["mariadbd"]

на:

command: ["mariadbd", "--wsrep-new-cluster"]

Запускаем только этот узел:

cd /srv/docker/mariadb
docker compose -f compose.yml config -q
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

Убедитесь, что web-01 сформировал новый Primary Component и готов принимать соединения.

Проверяем:

docker exec mariadb-galera mariadb -uroot -p -e "
SHOW GLOBAL STATUS
WHERE Variable_name IN (
'wsrep_cluster_status',
'wsrep_cluster_size',
'wsrep_local_state_comment',
'wsrep_ready'
);"

На этом этапе ожидается:

wsrep_cluster_status        Primary
wsrep_cluster_size          1
wsrep_local_state_comment   Synced
wsrep_ready                 ON

Размер 1 на данном этапе нормален: два остальных участника пока остановлены.

После успешного bootstrap возвращаем в compose.yml обычную команду:

command: ["mariadbd"]

Применяем конфигурацию:

docker compose -f compose.yml up -d

Теперь узел работает с обычной командой запуска.

Шаг 7. Подключаем два оставшихся узла

Только после того, как выбранный узел сформировал Primary Component, запускаем остальные участники.

На isp и web-02:

cd /srv/docker/mariadb
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

Они должны подключиться к существующему кластеру через wsrep_cluster_address и синхронизировать данные. В зависимости от состояния локальных данных Galera использует IST или полную передачу SST.

Не запускайте bootstrap на этих узлах. Не очищайте их каталоги data без отдельной необходимости.

Шаг 8. Проверяем итоговое состояние

На каждом из трёх узлов выполняем:

docker exec mariadb-galera mariadb -uroot -p -e "
SHOW GLOBAL STATUS
WHERE Variable_name IN (
'wsrep_cluster_status',
'wsrep_cluster_size',
'wsrep_local_state_comment',
'wsrep_ready',
'wsrep_connected'
);"

После завершения синхронизации на всех узлах ожидаются:

wsrep_cluster_status        Primary
wsrep_cluster_size          3
wsrep_local_state_comment   Synced
wsrep_ready                 ON
wsrep_connected             ON

Пока какой-либо узел выполняет SST или IST, не считайте восстановление завершённым. Дождитесь состояния Synced и проверьте, что размер кластера равен 3.

Краткая памятка


Официальная документация:

Resetting the Quorum (Cluster Bootstrap) (https://mariadb.com/docs/galera-cluster/high-availability/resetting-the-quorum-cluster-bootstrap)

Understanding Quorum, Monitoring, and Recovery (https://mariadb.com/docs/galera-cluster/high-availability/understanding-quorum-monitoring-and-recovery)