MariaDB – Réplication asynchrone (Master → Slave) sous Docker




Docker : MariaDB – HA par réplication
Besoin de haute disponibilité et continuité de service : j’ai conçu, déployé et validé une réplication asynchrone MariaDB (Master→Slave) avec binlog ROW, utilisateur dédié, persistance et réseau isolé — le tout Dockerisé pour être simple, reproductible et portable.
- Périmètre : 2 services (master & slave) + validation fonctionnelle
- Livrables : compose, .env.example, confs my.cnf, HOWTO
- Sécurité : user de réplication dédié, read_only sur le slave
Contexte : besoin de HA/continuité (panne du principal, maintenance, lectures intensives).
Objectif pédagogique : démontrer la conception, le déploiement et la validation d’une réplication asynchrone MariaDB.
Contraintes : environnement simple, reproductible, portable → Docker + Docker Compose sur Debian 12.
Périmètre : 2 conteneurs (mariadb_master
, mariadb_slave
), binlog ROW, validation par création/lecture d’une table test, doc pas-à-pas & bonnes pratiques.
[Docker network: dbnet]
┌───────────────────────┐
│ mariadb_master │
│ - server-id: 1 │
writes → │ - log_bin ON (ROW) │
binlog ---> │ - user 'repl' │
└─────────┬─────────────┘
│ CHANGE MASTER TO…
▼
┌───────────────────────┐
│ mariadb_slave │
│ - server-id: 2 │
│ - read_only ON │
│ - Slave_IO/SQL YES │
└───────────────────────┘
- Réseau : bridge Docker dédié
dbnet
(isolation, DNS interne). - Volumes : data persistants côté master & slave.
mariadb-ha-replication/
├─ docker-compose.yml # Orchestration des 2 services
├─ .env.example # Variables (mdp root, DB démo, user réplication…)
├─ .gitignore # Ignore volumes/data/dumps/secrets
├─ master/
│ └─ my.cnf # conf master (binlog, ROW, server-id)
├─ slave/
│ └─ my.cnf # conf slave (read_only, server-id)
└─ docs/
└─ HOWTO.md # Procédure détaillée & troubleshooting
Pourquoi Compose ? Démarrer/arrêter la stack en un bloc, déclarer volumes, réseau, healthchecks & dépendances.
version: "3.9"
networks:
dbnet:
volumes:
master-data:
slave-data:
services:
mariadb_master:
image: mariadb:11
container_name: mariadb_master
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MARIADB_DATABASE=${DEMO_DB}
ports:
- "3307:3306" # (optionnel) accès depuis l'hôte
networks: [dbnet]
volumes:
- master-data:/var/lib/mysql
- ./master/my.cnf:/etc/mysql/conf.d/master.cnf:ro
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -uroot -p${MYSQL_ROOT_PASSWORD} --silent"]
interval: 5s
timeout: 3s
retries: 20
mariadb_slave:
image: mariadb:11
container_name: mariadb_slave
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
networks: [dbnet]
depends_on:
mariadb_master:
condition: service_healthy
volumes:
- slave-data:/var/lib/mysql
- ./slave/my.cnf:/etc/mysql/conf.d/slave.cnf:ro
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -uroot -p${MYSQL_ROOT_PASSWORD} --silent"]
interval: 5s
timeout: 3s
retries: 20
Points clés : image mariadb:11
, volumes persistants, healthchecks, réseau isolé, confs injectées en read-only, depends_on
pour séquencer.
master/my.cnf :
[mysqld]
server-id=1
log_bin=mysql-bin
binlog_format=ROW
Pourquoi ROW ? Journalise les lignes modifiées (pas seulement la requête) → réplication plus fiable.
slave/my.cnf :
[mysqld]
server-id=2
read_only=ON
relay_log=relay-bin
read_only=ON
limite les écritures accidentelles côté slave (hors root local).
MYSQL_ROOT_PASSWORD=ChangeMeStrong!
DEMO_DB=demo
REPL_USER=repl
REPL_PASSWORD=ChangeMeRepl!
Pourquoi .env ? Centraliser secrets/variables sans les versionner. Fournir .env.example
pour guider.
.gitignore : ignorer **/data/**
, *.sql
, .env
pour ne pas exposer de données/secrets.
# 4.1 Lancer l'infra
cp .env.example .env
docker compose up -d
docker ps
# 4.2 Préparer la réplication côté master (user dédié)
docker exec -it mariadb_master mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "
CREATE USER '${REPL_USER}'@'%' IDENTIFIED BY '${REPL_PASSWORD}';
GRANT REPLICATION SLAVE ON *.* TO '${REPL_USER}'@'%';
FLUSH PRIVILEGES;"
# Verrouiller brièvement & relever FILE/POS
docker exec -it mariadb_master mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;"
# (Optionnel) déverrouiller
docker exec -it mariadb_master mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "UNLOCK TABLES;"
# 4.3 Attacher le slave (adapter FILE/POS relevés)
docker exec -it mariadb_slave mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "
CHANGE MASTER TO
MASTER_HOST='mariadb_master',
MASTER_USER='${REPL_USER}',
MASTER_PASSWORD='${REPL_PASSWORD}',
MASTER_LOG_FILE='mysql-bin.000002',
MASTER_LOG_POS=631;
START SLAVE;"
# 4.4 Vérifier l'état de réplication
docker exec -it mariadb_slave mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW SLAVE STATUS\\G"
# → Slave_IO_Running: Yes / Slave_SQL_Running: Yes
# 4.5 Test fonctionnel (preuve)
docker exec -it mariadb_master mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "
USE ${DEMO_DB};
CREATE TABLE IF NOT EXISTS test_ecf (id INT AUTO_INCREMENT PRIMARY KEY, stamp VARCHAR(64));
INSERT INTO test_ecf(stamp) VALUES('2025');
SELECT * FROM test_ecf;"
docker exec -it mariadb_slave mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e "
USE ${DEMO_DB};
SELECT * FROM test_ecf;"
# → la ligne '2025' apparaît côté slave
- Docker/Compose : reproductible, portable, isolement clair.
- Binlog=ROW : fiabilité (triggers, fonctions non déterministes).
- User ‘repl’ dédié : moindre privilège.
- Volumes persistants : pas de perte au redéploiement.
- Réseau dédié : isolation + DNS interne simple.
- Ne versionne pas
.env
; fournis.env.example
. read_only=ON
côté slave pour réduire le risque.- Sauvegardes : la réplication ≠ backup. Ajouter des dumps testés en restauration.
- Surveillance : alerte si Slave_IO/SQL_Running passent à No.
- GTID (optionnel) : facilite les bascules futures.
- IO/SQL = No : vérifier host/user/mdp, FILE/POS, réseau (
docker exec mariadb_slave ping mariadb_master
),SHOW SLAVE STATUS\G
(Last_IO_Error / Last_SQL_Error). - Pas de données : confirmer
log_bin
actif,binlog_format=ROW
, générer une nouvelle écriture sur le master. - Permissions :
GRANT REPLICATION SLAVE
+FLUSH PRIVILEGES;
.