MySQL vs MariaDB dans Docker : pourquoi « ça marchait en local » échoue en production

Cet article vous a aidé ?

Le développement local est un charmant menteur. Votre portable n’a qu’un seul utilisateur, un NVMe rapide, un cache système de fichiers chaud et une pile Docker Desktop qui « aide » à lisser les aspérités. La production n’a rien de tout cela. En production il y a une vraie concurrence, une vraie latence d’E/S, de véritables mises à jour, et des modes de panne qui arrivent à 03:12 avec une invitation de calendrier intitulée « SEV-1 ».

Si vous exécutez MySQL ou MariaDB dans des conteneurs Docker, l’écart entre « ça démarre » et « ça survit » est là où la plupart des équipes saignent. Les moteurs se ressemblent — jusqu’à ce qu’ils ne se ressemblent plus. Les images semblent interchangeables — jusqu’à ce qu’une version mineure change un comportement par défaut. Le stockage est « juste un volume » — jusqu’à ce que fsync devienne votre nouvelle religion.

Ce qui casse en production (et pourquoi)

Quand quelqu’un dit « MySQL et MariaDB sont essentiellement identiques », il veut généralement dire : « mon application a exécuté quelques requêtes et n’a pas planté ». La production exige des garanties plus fortes : démarrage prévisible, paramètres par défaut stables, mises à jour sûres, performance cohérente sous charge, et capacité de récupération quand quelque chose déraille.

Les grandes catégories d’échec « ça marchait en local »

  • Authentification et compatibilité client : MySQL 8 a changé des valeurs par défaut que les clients plus anciens n’aiment pas. MariaDB a suivi sa propre voie.
  • Sémantique du stockage persistant : bind mounts vs volumes nommés vs volumes réseau se comportent différemment face à fsync, aux permissions et à la latence.
  • Limites de ressources : votre portable surréserve souvent mémoire et CPU. Les conteneurs de production ont des cgroups et une application stricte des limites.
  • Ordonnancement du démarrage : compose « depends_on » n’est pas une disponibilité. Votre application frappe la BDD alors qu’elle est encore en récupération après crash.
  • Dérive de configuration : en local on utilise les valeurs par défaut ; en prod on a un fichier de config ; en staging une variable d’environnement. Surprise : vous exécutez trois bases de données différentes.
  • Chemins de mise à jour : une simple montée de version d’image peut changer des valeurs par défaut, le format des journaux de redo, ou le comportement de réplication.
  • Différences de mode SQL / collation : des différences subtiles se manifestent par des « pourquoi la prod refuse cet insert ? »

Voici le thème : MySQL et MariaDB ne sont pas « juste des conteneurs ». Ce sont des systèmes à état avec des arêtes nettes en matière de durabilité et de performance. Docker les rend faciles à démarrer ; il ne les rend pas faciles à exploiter.

Une blague courte (1/2) : Exécuter une base de données dans un conteneur, c’est comme mettre un piano sur un skateboard — c’est possible, mais il vaut mieux prendre soin du sol.

Choisissez une position : ce que vous devriez faire

Si vous déployez en production :

  • Épinglez les versions. Tags explicites. Jamais latest. Jamais.
  • Choisissez le moteur intentionnellement. Si vous avez besoin des fonctionnalités Oracle MySQL ou d’une compatibilité stricte avec le comportement de MySQL 8, utilisez MySQL. Si vous avez besoin de fonctionnalités spécifiques à MariaDB ou de raisons opérationnelles (certaines distributions), utilisez MariaDB. Ne « changez pas plus tard ».
  • Concevez le stockage en premier. Décidez où les données résident, comment elles sont sauvegardées et comment elles sont restaurées avant de lancer une seule requête.
  • Mesurez la latence fsync. Pas le marketing IOPS. Le comportement réel de fsync sur votre chemin de stockage réel.

Faits historiques et contexte utiles (que vous utiliserez réellement)

Ce ne sont pas des anecdotes pour un quiz. Ils expliquent pourquoi les valeurs par défaut diffèrent, pourquoi la compatibilité n’est pas garantie, et pourquoi « remplacement direct » a une date d’expiration.

  1. MariaDB a été créé comme un fork de MySQL après les acquisitions Sun/Oracle, pour maintenir une voie gouvernée par la communauté. Cette histoire explique pourquoi les noms se ressemblent et pourquoi la divergence est… politique et technique.
  2. MySQL 8 a changé le plugin d’authentification par défaut vers caching_sha2_password, ce qui a cassé des clients et outils anciens qui attendaient mysql_native_password.
  3. MySQL 8 a supprimé le query cache (c’était un piège sous forte concurrence). Si vous comptiez dessus dans MySQL 5.7, vous comptiez sur un piège.
  4. L’optimiseur et l’écosystème des moteurs de stockage de MariaDB ont divergé au fil du temps (par ex. Aria, MyRocks dans certains contextes, et des lignées InnoDB/XtraDB différentes historiquement). L’effet net : « même requête » n’implique pas « même plan ».
  5. La réplication GTID existe dans les deux mondes, mais les détails d’implémentation diffèrent suffisamment pour que la réplication/migration inter-moteurs nécessite de la planification, pas de l’intuition.
  6. Les images officielles Docker sont des packaging opinionnés. Scripts d’entrypoint, utilisateurs par défaut, permissions et logique d’initialisation font partie du produit que vous exécutez.
  7. Les systèmes de fichiers et pilotes Linux comptent. Les systèmes overlay, les systèmes de fichiers réseau et les couches copy-on-write influencent le coût de fsync et le comportement des métadonnées.
  8. La durabilité est configurable. Des paramètres comme innodb_flush_log_at_trx_commit font le compromis sécurité des données / vitesse. Beaucoup de guides « rapides » pour conteneurs règlent discrètement la durabilité sur « optionnelle ».

MySQL vs MariaDB dans des conteneurs : différences qui mordent

1) Le comportement de l’image fait partie de votre BDD

Quand vous exécutez mysql:8.0 ou mariadb:11, vous ne choisissez pas seulement un moteur de base de données. Vous choisissez :

  • Comment l’initialisation fonctionne (/docker-entrypoint-initdb.d, valeurs par défaut d’encodage, gestion du mot de passe root).
  • À quoi ressemble la configuration par défaut et où elle est chargée.
  • Quel UID le service utilise, ce qui affecte les permissions des volumes.

En dev local, vous supprimez les volumes sans remords. En prod, vos scripts d’entrypoint ne devraient jamais être la seule chose entre « redémarrage » et « répertoire de données inutilisable ».

2) Authentification et TLS : ce n’est pas juste « un mot de passe »

La panne classique : l’app utilise un connecteur ancien. En local, vous aviez MariaDB ou MySQL 5.7. En prod, quelqu’un a choisi MySQL 8 « parce que c’est plus récent ». L’app commence à échouer aux connexions avec des erreurs sur les plugins d’authentification ou les clés RSA publiques.

Faites un choix : soit mettez à jour les bibliothèques clientes pour supporter les valeurs par défaut de MySQL 8, soit configurez explicitement MySQL 8 pour utiliser mysql_native_password pour cet utilisateur (en sachant les implications de sécurité). Pour MariaDB, la trajectoire d’authentification est suffisamment différente pour que vous validiez la compatibilité du connecteur tôt.

3) Modes SQL et collations : « ça marche » jusqu’à ce que quelqu’un insère des vraies données

Les jeux de données locaux sont propres. Les données de production sont une décharge avec de la valeur métier. Des différences dans :

  • sql_mode (strictesse autour des dates invalides, chaînes tronquées et conversions implicites)
  • jeu de caractères par défaut (par ex. utf8mb4 vs ancien utf8)
  • valeurs par défaut de collation (règles de tri et de comparaison)

…peuvent transformer un comportement silencieux en local en une rupture bruyante en production.

4) Performance : même requête, plan différent, douleur différente

MySQL et MariaDB partagent beaucoup d’ADN, mais peuvent choisir des plans d’exécution différents sous charge. Docker ajoute sa propre taxe : comportement de l’overlay filesystem, drivers de stockage, et E/S contraintes qui ne se manifestent pas sur un laptop de développeur.

Si vous êtes sérieux : traitez chaque moteur + version comme une plateforme distincte. Mesurez le workload réel que vous avez, pas un one-liner synthétique.

5) Réplication et migration : « assez compatible » n’est pas une stratégie

Un jour, une équipe veut migrer MariaDB vers MySQL, ou l’inverse, pour des raisons de contrat de support, d’offres managées, ou de fonctionnalités. La containerisation ne rend pas cela plus facile ; elle facilite les manipulations occasionnelles et donc dangereuses.

Les dumps logiques (mysqldump) sont lents mais portables. Les copies physiques de répertoires de données sont rapides mais fragiles entre versions et moteurs. La réplication peut être élégante mais exige une discipline de compatibilité.

Stockage Docker : où performance et durabilité se disputent

Les bases de données font deux choses toute la journée : lire et écrire. Le stockage Docker détermine si ces écritures sont rapides, durables et récupérables — ou « rapides jusqu’à ce qu’elles ne le soient plus », ce qui est une manière polie de décrire la corruption de données et des pannes prolongées.

Volumes nommés vs bind mounts : choisissez volontairement

  • Volume nommé : chemin géré par Docker, permissions généralement stables, moins de surprises. Plus agréable opérationnellement pour beaucoup de setups.
  • Bind mount : vous contrôlez le chemin exact sur l’hôte. Utile pour l’intégration de sauvegarde prévisible et le debug, mais facile à gâcher avec la propriété, les contextes SELinux/AppArmor et les choix de système de fichiers.

Si vous montez un répertoire d’un système de fichiers hôte avec des sémantiques inhabituelles (FS réseau, certains FS distribués, NFS mal configuré), vous pouvez subir des blocages sur fsync ou un risque de corruption manifeste.

La latence fsync est votre « taxe ça marchait en local »

En dev local, fsync est rapide et le cache est chaud. En production, fsync peut être lent à cause de :

  • stockage en réseau
  • politique de cache du contrôleur RAID
  • comportement du stockage bloc cloud
  • surcoût copy-on-write
  • contention au niveau hôte

Quand fsync est lent, les flushs de log InnoDB sont lents. Puis les commits sont lents. Puis votre app expire. Puis quelqu’un « règle » ça en diminuant la durabilité. Et maintenant vous jouez avec votre paie.

Une citation (idée paraphrasée) : L’idée paraphrasée de Werner Vogels : tout tombe en panne, tout le temps — concevez pour ça plutôt que d’espérer que ça n’arrive pas.

Limites mémoire du conteneur et comportement d’InnoDB

InnoDB veut de la mémoire pour le buffer pool, les log buffers et les caches. Les conteneurs ont des limites. Si vous ne dimensionnez pas innodb_buffer_pool_size et compagnons en tenant compte des cgroups, le noyau OOM killer finira par prendre une décision pour vous. Ce ne sera pas la décision que vous vouliez.

Vérifications de santé vs disponibilité

Un conteneur qui est « up » n’est pas nécessairement « prêt ». Après un crash ou un redémarrage brutal, MySQL/MariaDB peut effectuer une récupération après crash. Le processus accepte des connexions TCP tôt, mais les requêtes peuvent se bloquer ou échouer tant que la récupération n’est pas terminée. C’est là que l’orchestration naïve transforme un incident transitoire en panne en cascade.

Tâches pratiques : commandes, sorties et décisions à prendre

Ci‑dessous des tâches de qualité production que vous pouvez exécuter aujourd’hui. Chacune inclut : la commande, un exemple de sortie réaliste, ce que ça signifie, et la décision que vous en prenez. Faites-les avant de déclarer victoire.

Task 1: Confirm which engine and version you are actually running

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT @@version, @@version_comment;"
+-----------+----------------------------------+
| @@version | @@version_comment                |
+-----------+----------------------------------+
| 8.0.36    | MySQL Community Server - GPL     |
+-----------+----------------------------------+

Ce que cela signifie : Il s’agit d’Oracle MySQL 8.0.36, pas de MariaDB. Le commentaire de version compte ; MariaDB affiche son propre branding.

Décision : Utilisez la documentation spécifique au moteur et les notes de migration. N’appliquez pas les tuning ou hypothèses MariaDB à MySQL 8.

Task 2: Check the container image tag and digest (pinning)

cr0x@server:~$ docker inspect --format '{{.Config.Image}} {{index .RepoDigests 0}}' db
mysql:8.0 mysql@sha256:3a5f2c8e7f2c2c6f2d2f4c1c0b0b7c6d9e8f1a2b3c4d5e6f7a8b9c0d1e2f3a4

Ce que cela signifie : Vous exécutez un tag et il existe un digest spécifique. Les tags bougent ; les digests non.

Décision : Épinglez sur un digest pour les déploiements en production (ou au moins épinglez la version mineure et contrôlez les upgrades).

Task 3: See whether you’re using a named volume or bind mount for data

cr0x@server:~$ docker inspect db --format '{{json .Mounts}}'
[{"Type":"volume","Name":"prod-mysql-data","Source":"/var/lib/docker/volumes/prod-mysql-data/_data","Destination":"/var/lib/mysql","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

Ce que cela signifie : Un volume Docker nommé alimente /var/lib/mysql.

Décision : Assurez-vous que vos procédures de backup et restauration savent où est ce volume, et que le stockage hôte est adapté (système de fichiers, latence, monitoring).

Task 4: Verify filesystem type and mount options under the data directory

cr0x@server:~$ docker exec -it db bash -lc "df -T /var/lib/mysql && mount | grep ' /var/lib/mysql ' -n || true"
Filesystem     Type 1K-blocks      Used Available Use% Mounted on
/dev/nvme0n1p2 ext4  96143228  31287124  59921844  35% /var/lib/mysql

Ce que cela signifie : Les données sont sur ext4 ; bon point de départ. Si vous voyez overlayfs ou un montage NFS ici, arrêtez et reconsidérez.

Décision : Évitez de placer les données InnoDB sur des systèmes de fichiers réseau sauf si vous comprenez complètement la durabilité et les sémantiques de verrouillage.

Task 5: Confirm the data directory and critical InnoDB durability settings

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES WHERE Variable_name IN ('datadir','innodb_flush_log_at_trx_commit','sync_binlog');"
+------------------------------+----------------+
| Variable_name                | Value          |
+------------------------------+----------------+
| datadir                      | /var/lib/mysql/|
| innodb_flush_log_at_trx_commit | 1            |
| sync_binlog                  | 1              |
+------------------------------+----------------+

Ce que cela signifie : Durabilité complète : flush des logs à chaque commit, sync du binlog à chaque commit.

Décision : Conservez cela en production sauf si vous avez accepté formellement la perte de données en cas de crash. Si la performance est mauvaise, corrigez le stockage et la configuration avant de diminuer ces valeurs.

Task 6: Check authentication plugin defaults and user configuration (MySQL 8 classic issue)

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT user, host, plugin FROM mysql.user WHERE user IN ('app','root');"
+------+-----------+-----------------------+
| user | host      | plugin                |
+------+-----------+-----------------------+
| app  | %         | caching_sha2_password |
| root | localhost | caching_sha2_password |
+------+-----------+-----------------------+

Ce que cela signifie : L’utilisateur app utilise caching_sha2_password. Des clients anciens peuvent échouer.

Décision : Mettez à jour le connecteur client ou changez le plugin par utilisateur (et testez). Ne « corrigez » pas cela en rétrogradant le serveur sans plan.

Task 7: Validate SQL mode and catch local/prod drift

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SELECT @@sql_mode\G"
*************************** 1. row ***************************
@@sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

Ce que cela signifie : Les modes stricts sont activés. Les apps qui dépendaient d’un comportement laxiste peuvent casser.

Décision : Préférez corriger l’application et le schéma plutôt que d’assouplir le sql_mode globalement. Si vous devez ajuster, faites-le explicitement et documentez-le.

Task 8: Check default character set and collation (data correctness, not cosmetics)

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'collation_server';"
+----------------------+---------+
| Variable_name        | Value   |
+----------------------+---------+
| character_set_server | utf8mb4 |
+----------------------+---------+
+------------------+--------------------+
| Variable_name    | Value              |
+------------------+--------------------+
| collation_server | utf8mb4_0900_ai_ci |
+------------------+--------------------+

Ce que cela signifie : MySQL 8 utilise des collations récentes par défaut. MariaDB diffère. Le tri/comparaison peut changer entre environnements.

Décision : Standardisez le charset/collation dans les migrations de schéma. Ne laissez pas cela aux valeurs par défaut du serveur.

Task 9: Inspect container logs for crash recovery, permission issues, and plugin failures

cr0x@server:~$ docker logs --tail=80 db
2025-12-31T00:11:21.443915Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.36) initializing of server in progress as process 1
2025-12-31T00:11:22.119203Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-12-31T00:11:27.991130Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
2025-12-31T00:11:28.250984Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections.

Ce que cela signifie : Démarrage propre. Si vous voyez « permission denied », « upgrade after crash », ou redémarrages répétés, considérez-le comme un problème de stockage/config plutôt qu’un problème applicatif.

Décision : Faites que les vérifications de readiness reflètent l’utilisabilité réelle (par ex. une requête simple) et investiguez une récupération lente si elle se répète.

Task 10: Confirm resource limits (cgroups) to avoid mystery OOMs

cr0x@server:~$ docker inspect db --format 'Memory={{.HostConfig.Memory}} NanoCpus={{.HostConfig.NanoCpus}}'
Memory=2147483648 NanoCpus=2000000000

Ce que cela signifie : Limite mémoire de 2 GiB et quota équivalent à 2 cœurs CPU sont définis.

Décision : Ajustez le buffer pool et les limites de connexions pour tenir compte de cela. Si vous exécutez MySQL avec des valeurs par défaut illimitées dans une boîte de 2 GiB, l’OOM killer organisera éventuellement une fête surprise.

Task 11: Watch runtime memory and check for OOM kill history

cr0x@server:~$ docker stats --no-stream db
CONTAINER ID   NAME   CPU %     MEM USAGE / LIMIT     MEM %     NET I/O        BLOCK I/O    PIDS
a1b2c3d4e5f6   db     185.23%   1.92GiB / 2.00GiB     96.00%    1.2GB / 1.1GB  9.8GB / 14GB  61

Ce que cela signifie : Vous frôlez la limite mémoire. C’est comme ça qu’on obtient des OOM kills pendant des pics.

Décision : Réduisez max_connections, redimensionnez le buffer pool ou augmentez la mémoire. Évaluez aussi les patterns de requêtes qui causent des pics.

Task 12: Check InnoDB buffer pool sizing vs container memory

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 1073741824|
+-------------------------+-----------+

Ce que cela signifie : Le buffer pool est de 1 GiB. Sur un conteneur de 2 GiB, cela peut être raisonnable — si le reste (connexions, tables temporaires, buffers de tri) est contrôlé.

Décision : Conservez une marge. Si vous êtes régulièrement à 90 % de mémoire, réduisez le buffer pool ou corrigez l’usage mémoire par connexion.

Task 13: Measure slow queries and whether you’re CPU-bound or I/O-bound

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW GLOBAL STATUS LIKE 'Slow_queries'; SHOW VARIABLES LIKE 'slow_query_log';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries  | 1842  |
+---------------+-------+
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+

Ce que cela signifie : Le compteur de requêtes lentes augmente. Le logging est activé, vous pouvez l’inspecter et corriger.

Décision : Récupérez les slow logs, corrigez les index/patrons de requêtes, et vérifiez que la prod utilise les mêmes plans que la staging.

Task 14: Validate binary logging and format (replication and point-in-time recovery)

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+

Ce que cela signifie : Les binlogs sont activés et en format ROW — généralement le format de réplication le plus sûr pour la correction.

Décision : Si vous voulez une récupération point-in-time, conservez les binlogs et expédiez-les. Sinon, soyez honnête : votre plan de récupération est « restaurer la dernière sauvegarde et perdre des données ».

Task 15: Confirm that your app isn’t accidentally using localhost/socket assumptions

cr0x@server:~$ docker exec -it app bash -lc "getent hosts db && nc -vz db 3306"
172.20.0.3      db
Connection to db 3306 port [tcp/mysql] succeeded!

Ce que cela signifie : Le container app peut résoudre et atteindre la DB par nom de service via TCP.

Décision : Standardisez sur TCP pour la connectivité conteneur-à-conteneur sauf si vous avez une raison délibérée de partager un socket UNIX via un volume (rare et fragile).

Task 16: Check for table corruption or crash recovery warnings (InnoDB health)

cr0x@server:~$ docker exec -it db mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,80p'
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2025-12-31 00:22:10 0x7f1a2c1ff700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 44 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 102 srv_active, 0 srv_shutdown, 188 srv_idle
srv_master_thread log flush and writes: 290

Ce que cela signifie : Vous pouvez inspecter la présence de « LATEST DETECTED DEADLOCK » et les attentes I/O. L’absence d’erreurs flagrantes est bonne ; la présence d’attentes longues indique E/S ou contention.

Décision : Si vous voyez de fortes attentes « log i/o » ou des blocages « fsync », investigatez d’abord la latence du stockage.

Playbook de diagnostic rapide

Vous avez un incident. L’app est lente ou indisponible. Les gens tapent « des nouvelles ? » dans le chat comme si c’était un test de charge. Voici l’ordre qui trouve rapidement le goulot d’étranglement.

Premier point : la base est‑elle réellement saine et prête ?

  • Vérifiez les redémarrages de conteneur et les logs pour boucles de récupération, erreurs de permission et messages de mise à niveau.
  • Exécutez une requête triviale depuis l’intérieur du conteneur : SELECT 1; Si elle bloque, vous n’êtes pas « up ».
  • Confirmez l’espace disque et la pression sur les inodes du chemin hôte qui alimente le volume.

Deuxième point : est‑ce de la latence I/O (fsync) ou de la contention CPU ?

  • Si les commits sont lents et la concurrence élevée, suspectez la latence fsync et le stockage.
  • Si le CPU est saturé et que les requêtes sont lentes, suspectez des index manquants, de mauvais plans, ou trop de threads.
  • Si la mémoire est proche de la limite, suspectez du swapping (hôte) ou des OOM kills (conteneur).

Troisième point : est‑ce une dérive de configuration ou une régression de compatibilité ?

  • Comparez sql_mode, charset/collation et plugins d’authentification entre local/staging/prod.
  • Confirmez le tag et le digest de l’image. Y a‑t‑il eu un redeploy « innocent » ?
  • Vérifiez les versions des connecteurs clients et les exigences TLS.

Quatrième point : est‑ce le chemin réseau et la résolution de noms ?

  • Validez la découverte de services à l’intérieur du réseau Docker.
  • Confirmez les mappings de ports et règles de firewall lors d’échanges inter‑hôtes.
  • Recherchez des tempêtes de connexions éphémères (mauvaise configuration des pools).

Erreurs courantes : symptômes → cause racine → correctif

1) Symptom: App can’t log in after moving to MySQL 8

Cause racine : Incompatibilité du plugin d’authentification (caching_sha2_password vs attentes des clients anciens).

Correctif : Mettez à jour le connecteur client ; ou définissez le plugin utilisateur sur mysql_native_password pour cet utilisateur et réémettez le mot de passe ; puis planifiez une montée de version correcte.

2) Symptom: Random “permission denied” on startup after redeploy

Cause racine : Propriété du volume / labels SELinux ne correspondant pas à l’UID/GID d’exécution du conteneur ; chemin bind mount créé par root sur l’hôte.

Correctif : Standardisez la création des volumes ; appliquez la bonne propriété sur le chemin hôte ; utilisez un contexte de sécurité cohérent ; évitez la création manuelle ad hoc des répertoires.

3) Symptom: Writes are slow only in production, reads seem fine

Cause racine : Latence fsync sur le chemin de stockage de production (volume réseau, disque lent, contention hôte). En dev, votre disque est rapide et inactif.

Correctif : Mesurez le comportement de fsync ; déplacez les données sur SSD/NVMe local ou sur du block storage correctement provisionné ; évitez l’overlay pour datadir ; tunez l’ordonnanceur I/O et l’hôte.

4) Symptom: “Too many connections” during traffic spikes

Cause racine : Pooling mal configuré, autoscaling des conteneurs sans planification de capacité DB, ou max_connections trop bas par rapport à la charge.

Correctif : Corrigez d’abord les paramètres du pool (timeouts, max open connections). Ensuite dimensionnez max_connections en tenant compte de la mémoire (buffers par connexion).

5) Symptom: Queries behave differently between MariaDB and MySQL

Cause racine : Choix optimiseur différents, collations différentes, ou différences de sql_mode.

Correctif : Épinglez moteur + version entre environnements ; lancez EXPLAIN en conditions proches de la prod ; standardisez les valeurs par défaut de schéma pour charset/collation et sql_mode.

6) Symptom: Container “healthy” but app gets timeouts right after restart

Cause racine : La vérification de santé ne vérifie que le processus ; la BDD est encore en récupération ou en phase de chauffe des caches.

Correctif : La vérification doit exécuter une requête (et éventuellement valider l’état de réplication). Caler le démarrage de l’app sur la readiness, pas sur le simple « container démarré ».

7) Symptom: “Disk full” inside container, host has free space

Cause racine : Vous écrivez dans la couche du conteneur (overlay) au lieu du volume ; ou le volume est sur un autre filesystem plein.

Correctif : Assurez-vous que datadir pointe vers le volume monté ; vérifiez les montages ; nettoyez les binlogs et tmpdir avec une politique de rétention.

8) Symptom: Replication breaks after “minor” version change

Cause racine : Incompatibilités de binlog/GTID ; changements de valeurs par défaut ou comportements dépréciés entre versions/moteurs.

Correctif : Montez en version avec un plan incrémental ; validez la réplication en pré-prod avec des patterns de trafic réalistes ; épinglez les versions jusqu’à validation.

Trois mini-récits d’entreprise tirés du terrain

Mini-story 1: The incident caused by a wrong assumption

L’équipe avait un fichier Docker Compose propre. Un service MySQL, un service app et un volume. Le dev était content. La staging « allait bien ». La production est passée en live et est morte doucement : échecs d’authentification intermittents et pic de 500 qui ressemblait à une régression applicative.

L’hypothèse était subtile : « MySQL, c’est MySQL. » En local, ils exécutaient MariaDB parce qu’elle démarrait plus vite et paraissait familière. En production, un ingénieur plateforme l’a remplacée par mysql:8 pour standardiser. Pas de malveillance, juste un nettoyage bien intentionné.

L’app utilisait un ancien connecteur MySQL embarqué dans une image de base pas à jour. Sous faible charge ça marchait parfois (merci à la réutilisation de connexions et un chemin de test indulgent). Sous vraie charge, de nouvelles connexions montaient et l’authentification échouait avec des erreurs de plugin.

La correction n’était pas compliquée : mettre à jour le connecteur et standardiser les attentes d’authentification. Le temps perdu fut conséquent parce que tout le monde a débogué le « réseau » pendant une heure. La leçon : la sélection du moteur fait partie du contrat applicatif, pas un détail d’infrastructure à échanger un vendredi soir.

Mini-story 2: The optimization that backfired

Une autre organisation avait un problème de performance : les écritures étaient lentes. Quelqu’un a repéré les suspects habituels — innodb_flush_log_at_trx_commit=1 et sync_binlog=1. Ils les ont changés respectivement en 2 et 0. La latence s’est améliorée. Le canal incident s’est calmé. Des high fives.

Deux semaines plus tard, un reboot hôte est survenu. Même pas spectaculaire. Après le redémarrage, l’application manquait des transactions récentes. Pas beaucoup. Juste assez pour faire mal, parce que les lignes manquantes étaient visibles côté client et sensibles dans le temps.

Maintenant l’équipe avait deux problèmes : réparer les données et rétablir la confiance. Ils ont dû réconcilier via des systèmes externes, retraiter des événements et expliquer pourquoi la durabilité avait été troquée sans décision documentée. Le vrai problème n’était pas tant les paramètres eux-mêmes ; c’était leur usage comme pansement de performance au lieu de corriger la latence du stockage et la contention I/O.

La solution finale fut ennuyeuse : déplacer le volume DB sur du block storage correctement provisionné, limiter les tempêtes de connexions et optimiser les requêtes lentes. Les paramètres de durabilité sont revenus stricts, parce que « rapide mais incorrect » n’est pas une fonctionnalité.

Mini-story 3: The boring but correct practice that saved the day

Une autre équipe exécutait MariaDB en conteneurs pour une plateforme interne. Pas glamour, mais disciplinée. Chaque déploiement épinglait des digests d’image. Chaque changement de config passait en revue. Les sauvegardes étaient nocturnes avec un test de restauration chaque sprint.

Un matin, après une maintenance de nœud routinière, la base n’a pas démarré. Les logs montraient des symptômes de corruption dans les fichiers tablespace InnoDB. Personne n’a célébré, mais personne n’a paniqué non plus. Ils ont suivi le runbook : arrêter les écritures, snapshotter l’existant, restaurer la dernière sauvegarde saine, rejouer les binlogs jusqu’à la frontière de l’incident.

Ils étaient de retour en service avec une exposition limitée à la perte de données car leur rétention et l’envoi des binlogs avaient été conçus à l’avance. La cause racine s’est avérée être un problème de couche de stockage sur le nœud, pas MariaDB en lui‑même. Mais la raison pour laquelle cela n’est pas devenu un événement de carrière était simple : ils pratiquaient la récupération comme une chose normale, pas un exercice annuel.

Une blague courte (2/2) : Les sauvegardes sont comme des parachutes — si vous attendez d’en avoir besoin pour le vérifier, vous avez déjà fait un choix.

Listes de contrôle / plan étape par étape

Étape par étape : passer de « succès local » à « résilience en production »

  1. Choisissez le moteur et la version délibérément. Documentez pourquoi vous utilisez MySQL vs MariaDB, et épinglez la version exacte.
  2. Standardisez l’injection de configuration. Préférez un fichier de config monté ou un mécanisme contrôlé ; évitez de mélanger variables d’environnement et fragments de config sans politique.
  3. Définissez le stockage. Décidez : volume nommé vs bind mount ; système de fichiers hôte ; classe de stockage ; exigences IOPS/durabilité.
  4. Décidez consciemment de la durabilité. Conservez innodb_flush_log_at_trx_commit=1 et sync_binlog=1 sauf si vous avez signé une tolérance de perte de données.
  5. Fixez des limites mémoire et tunez pour tenir. Calez max_connections, dimensionnez le buffer pool, et vérifiez le comportement OOM du conteneur.
  6. Implémentez des checks de readiness. Une vérification de santé qui exécute une requête vaut mieux qu’un « port ouvert ».
  7. Journalisez et exportez les métriques. Slow query log, error log et métriques MySQL/MariaDB core (connections, buffer pool hit ratio, redo log waits).
  8. Sauvegardes avec tests de restauration. Planifiez des restaurations dans un environnement jetable ; validez la compatibilité schéma et application.
  9. Répétition des upgrades. Répétez les montées de version avec des données et patrons de trafic proches de la production. Mesurez les temps de récupération.
  10. Runbooks d’incident. Ayez un playbook pour : latence élevée, réplication en retard, disque plein, et échecs de démarrage.

Checklist de déploiement (imprimez avant d’envoyer)

  • Tag d’image épinglé et revu ; idéalement digest épinglé.
  • Répertoire de données sur un volume (pas sur la couche du conteneur).
  • Système de fichiers et options de montage revus pour la compatibilité BDD.
  • Limites mémoire/CPU définies ; MySQL/MariaDB tunés en conséquence.
  • Compatibilité plugin d’auth et client validée.
  • Charset/collation standardisés dans le schéma, pas laissés aux valeurs par défaut.
  • Paramètres de durabilité décidés explicitement et documentés.
  • Sauvegardes configurées ; restauration testée.
  • Monitoring et alertes pour disque, CPU, mémoire et latence des requêtes.
  • Checks de readiness / santé valident le succès de requêtes réelles.

FAQ

1) Can I switch from MariaDB to MySQL by just changing the Docker image?

Vous pouvez essayer, et parfois ça démarre même. Mais les formats de répertoire de données, les tables système et les valeurs par défaut diffèrent selon les versions. Planifiez une migration : dump logique/restauration ou cutover basé sur la réplication.

2) Why is MySQL 8 breaking my app when MariaDB worked?

Le plus souvent : différences du plugin d’authentification, strictesse du sql_mode, et valeurs par défaut de collation. Confirmez avec SELECT @@version_comment, plugins utilisateurs, et @@sql_mode.

3) Are named volumes safer than bind mounts?

Opérationnellement, les volumes nommés réduisent les pièges liés aux permissions et aux écritures accidentelles sur la couche du conteneur. Les bind mounts conviennent si vous contrôlez l’hôte et avez de bonnes pratiques sur la propriété, les labels et les sauvegardes.

4) Why is the database slower in Docker than on bare metal?

Si le datadir est réellement sur un volume, le surcoût peut être faible. Les ralentissements majeurs viennent généralement du stockage sous‑jacent (volumes réseau, disques lents, contention) et du comportement de fsync — pas Docker en lui‑même.

5) Should I disable fsync-related durability settings for performance?

Seulement si vous pouvez tolérer la perte de transactions qui semblaient committées lors d’un crash. Pour des systèmes de production dignes de confiance, corrigez la latence de stockage et les patterns de requêtes d’abord. Changer la durabilité est une décision métier, pas un tweak de perf.

6) What’s the quickest way to confirm I/O is my bottleneck?

Recherchez dans le status InnoDB des indications de log waits, corrélez avec la latence des commits, et vérifiez les métriques disque de l’hôte. En incident, le pattern « écritures lentes, lectures OK » est un indice fort.

7) Why does Compose “depends_on” not solve startup issues?

Il contrôle l’ordre de démarrage, pas la readiness. MySQL/MariaDB peut accepter des connexions avant de pouvoir servir des requêtes de manière fiable (récupération après crash, initialisations, migrations).

8) Can I use the same backup strategy for MySQL and MariaDB containers?

Conceptuellement oui — dumps logiques et backups physiques existent pour les deux — mais les outils et détails de compatibilité diffèrent. Règle d’or : testez les restaurations avec le même moteur/version que celui que vous exécuterez.

9) What’s the safest way to run schema migrations in containers?

Exécutez les migrations comme un job explicite avec un mécanisme de lock/leader, et bloquez le déploiement de l’app sur la réussite de la migration. Ne laissez pas plusieurs réplicas se battre pour migrer au démarrage.

10) How do I prevent accidental upgrades when rebuilding images?

Épinglez les images de base, épinglez les tags/digests des images DB, et traitez les changements de version comme des événements gérés avec un plan de rollback.

Conclusion : étapes suivantes pour ne pas réapprendre ça

« Ça marchait en local » est généralement vrai. C’est juste hors de propos. La production est une machine différente avec des modes de panne différents, et les bases de données amplifient chaque mensonge que votre environnement vous raconte.

Faites ceci ensuite, dans l’ordre :

  1. Choisissez le moteur et épinglez la version (et arrêtez de prétendre que MySQL et MariaDB sont interchangeables à l’exécution).
  2. Auditez votre chemin de stockage pour le datadir : type de système de fichiers, options de montage, et latence fsync réelle sous charge.
  3. Standardisez les valeurs par défaut : sql_mode, charset/collation, attentes d’authentification — explicitez-les.
  4. Implémentez des checks de readiness et des exercices de récupération : une BDD que vous ne pouvez pas restaurer est une rumeur, pas un système.
  5. Exécutez les tâches pratiques ci‑dessous et conservez leurs sorties dans votre runbook pour comparer avant/après changements.

Si vous ne faites rien d’autre : arrêtez d’utiliser « latest », arrêtez de traiter le répertoire de données comme un dossier occasionnel, et arrêtez de laisser les valeurs par défaut décider de la correction. La production récompense l’ennui. Gagnez‑la.

← Précédent
Concevez un tableau de tarification SaaS qui convertit sans casser votre frontend
Suivant →
Docker « réseau introuvable » : reconstruire les réseaux sans tout casser

Laisser un commentaire