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 соответствующего сервера:
- web-01: 10.10.1.41
- web-02: 10.10.1.45
Путь к конфигурации и каталогу данных остаётся одинаковым.
Проверяем синтаксис и запускаем:
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 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 — установка и первоначальный запуск (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)
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
Здесь важны два поля:
- seqno — номер последней известной транзакции.
- safe_to_bootstrap — признак узла, который безопасно использовать для bootstrap после штатной остановки кластера.
Если кластер остановился штатно, ищите узел с
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.
Краткая памятка- Все три узла выключились — сначала пробуем обычный запуск и автоматическое восстановление.
- Если Primary Component не восстановился, останавливаем все три контейнера.
- Проверяем grastate.dat и, если требуется, выполняем wsrep-recover на каждом узле.
- Сравниваем UUID и seqno, выбираем узел с наиболее актуальными данными.
- Только на выбранном узле выставляем safe_to_bootstrap: 1 и выполняем bootstrap.
- Два оставшихся узла запускаем обычным способом и ждём синхронизации.
- Не удаляем данные и не выполняем bootstrap одновременно на нескольких узлах.
Официальная документация: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)