MariaDB Galera Cluster в Docker Compose на трёх серверах

Автор George, Окт. 13, 2026, 09:13

« назад - далее »

George

MariaDB Galera Cluster в Docker Compose на трёх серверах

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

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

Код: bash
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.

Проверяем:

Код: bash
docker --version
docker compose version

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

Код: bash
mkdir -p /srv/docker/mariadb/conf.d
mkdir -p /srv/docker/mariadb/data
cd /srv/docker/mariadb

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

Код: bash
/srv/docker/mariadb/
├── compose.yml
├── .env
├── conf.d/
│   └── galera.cnf
└── data/

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

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

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

Код: bash
/srv/docker/mariadb/conf.d/galera.cnf

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

Код: bash
[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:

Код: bash
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:

Код: bash
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:

Код: bash
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 хоста внутри контейнера, появляется ошибка:

Код: bash
Failed to open IST listener
Cannot assign requested address

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

3. Файл .env

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

Код: bash
/srv/docker/mariadb/.env

Содержимое:

Код: bash
MARIADB_ROOT_PASSWORD=ЗДЕСЬ_НАДЁЖНЫЙ_ПАРОЛЬ_ROOT

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

Код: bash
chmod 600 /srv/docker/mariadb/.env

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

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

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

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

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

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

4. Docker Compose

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

Код: bash
/srv/docker/mariadb/compose.yml

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

Код: bash
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 соответствующего сервера:

  • web-01: 10.10.1.41
  • web-02: 10.10.1.45

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

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

Код: bash
cd /srv/docker/mariadb
docker compose -f compose.yml config -q
docker compose -f compose.yml up -d

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

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

Код: bash
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 временно заменяем:

Код: bash
command: ["mariadbd"]

на:

Код: bash
command: ["mariadbd", "--wsrep-new-cluster"]

Запускаем:

Код: bash
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

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

Код: bash
command: ["mariadbd"]

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

Код: bash
docker compose -f compose.yml up -d

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

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

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

Код: bash
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. Проверка состояния кластера

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

Код: bash
docker exec -it mariadb-galera mariadb -uroot -p

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

Код: bash
SHOW GLOBAL STATUS
WHERE Variable_name IN (
  'wsrep_cluster_status',
  'wsrep_cluster_size',
  'wsrep_local_state_comment',
  'wsrep_ready',
  'wsrep_connected'
);

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

Код: bash
wsrep_cluster_status        Primary
wsrep_cluster_size          3
wsrep_local_state_comment   Synced
wsrep_ready                 ON
wsrep_connected             ON

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

Код: bash
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, затем пересоздать контейнер:

Код: bash
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. Остановить контейнер на восстанавливаемом узле:

Код: bash
cd /srv/docker/mariadb
docker compose -f compose.yml down

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

Код: bash
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:

Код: bash
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. Полезные команды

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

Код: bash
docker inspect mariadb-galera 
  --format 'Network={{.HostConfig.NetworkMode}} Status={{.State.Status}}'

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

Код: bash
docker logs --tail 100 mariadb-galera

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

Код: bash
docker compose -f compose.yml restart

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

Код: bash
docker compose -f compose.yml config -q

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

Код: bash
docker port mariadb-galera

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

  • Три узла MariaDB 10.11 с Galera.
  • Docker Compose и стандартная bridge-сеть на каждом сервере.
  • Постоянное хранение данных в /srv/docker/mariadb/data.
  • Отдельные настройки wsrep_node_address и wsrep_node_name на каждом узле.
  • Файл .env с MARIADB_ROOT_PASSWORD на каждом сервере.
  • Порты 3306, 4444, 4567/tcp, 4567/udp и 4568 опубликованы на IP соответствующего хоста.
  • Первый узел нового кластера запускается через bootstrap только один раз; остальные узлы присоединяются обычным запуском.
  • Работоспособность подтверждается состоянием Primary, размером кластера 3 и статусом Synced на каждом участнике.

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

MariaDB Galera Cluster — установка и первоначальный запуск

MariaDB — параметры wsrep_provider_options

MariaDB — восстановление кластера после потери Primary Component
  •  

George

13. Восстановление всего кластера после отключения питания

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

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

Код: bash
isp      — 10.10.1.43
web-01   — 10.10.1.41
web-02   — 10.10.1.45

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

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

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

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

Код: bash
cd /srv/docker/mariadb
docker compose -f compose.yml up -d
docker logs --tail 100 mariadb-galera

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

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

Код: bash
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 на всех трёх узлах

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

Код: bash
cd /srv/docker/mariadb
docker compose -f compose.yml down

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

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

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

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

Код: bash
cd /srv/docker/mariadb
cat data/grastate.dat

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

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

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

  • seqno — номер последней известной транзакции.
  • safe_to_bootstrap — признак узла, который безопасно использовать для bootstrap после штатной остановки кластера.

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

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

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

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

На isp:

Код: bash
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 и не должна запускать обычный рабочий экземпляр базы.

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

Код: bash
WSREP: Recovered position: b42f59d4-c70d-11f1-8971-9a96395e2946:12345

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

Код: bash
cat /tmp/wsrep-recover.log

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

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

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

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

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

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

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

Код: bash
cd /srv/docker/mariadb
cp -a data/grastate.dat data/grastate.dat.before-bootstrap

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

Код: bash
vi data/grastate.dat

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

Код: bash
safe_to_bootstrap: 1

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

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

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

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

Код: bash
command: ["mariadbd"]

на:

Код: bash
command: ["mariadbd", "--wsrep-new-cluster"]

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

Код: bash
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 и готов принимать соединения.

Проверяем:

Код: bash
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'
);"

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

Код: bash
wsrep_cluster_status        Primary
wsrep_cluster_size          1
wsrep_local_state_comment   Synced
wsrep_ready                 ON

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

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

Код: bash
command: ["mariadbd"]

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

Код: bash
docker compose -f compose.yml up -d

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

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

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

На isp и web-02:

Код: bash
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. Проверяем итоговое состояние

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

Код: bash
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'
);"

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

Код: bash
wsrep_cluster_status        Primary
wsrep_cluster_size          3
wsrep_local_state_comment   Synced
wsrep_ready                 ON
wsrep_connected             ON

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

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

  • Все три узла выключились — сначала пробуем обычный запуск и автоматическое восстановление.
  • Если Primary Component не восстановился, останавливаем все три контейнера.
  • Проверяем grastate.dat и, если требуется, выполняем wsrep-recover на каждом узле.
  • Сравниваем UUID и seqno, выбираем узел с наиболее актуальными данными.
  • Только на выбранном узле выставляем safe_to_bootstrap: 1 и выполняем bootstrap.
  • Два оставшихся узла запускаем обычным способом и ждём синхронизации.
  • Не удаляем данные и не выполняем bootstrap одновременно на нескольких узлах.

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

Resetting the Quorum (Cluster Bootstrap)

Understanding Quorum, Monitoring, and Recovery
  •  

🡱 🡳

Отметьте интересные вам фрагменты текста и они станут доступны по уникальной ссылке в адресной строке браузера.