Volumes, OOM-Kill-Risiko, automatische InnoDB-Crash-Recovery, Kubernetes StatefulSets und Backup-Strategien — alles was man für MySQL Docker wirklich wissen muss.
Inhaltsverzeichnis
- 1. Datenpersistenz in Docker-Containern verstehen
- 2. Named Volume vs. Bind Mount: Was wann verwenden
- 3. docker-compose MySQL-Konfiguration richtig aufsetzen
- 4. OOM-Kill: Der gefährlichste Container-Fehler
- 5. Liveness und Readiness Probes für MySQL
- 6. Init-Skripte und Datenbankinitialisierung
- 7. InnoDB Crash Recovery nach OOM-Kill
- 8. Kubernetes StatefulSet für MySQL
- 9. Magento docker-magento MySQL-Konfiguration
- 10. mysqldump-Backup in containerisierten Umgebungen
- 11. Unterstützung
- 12. Zusammenfassung
- 13. FAQ
1. Datenpersistenz in Docker-Containern verstehen
MySQL Docker läuft grundsätzlich wie auf einer normalen VM — mit einem entscheidenden Unterschied: der Container selbst ist ephemer. Wird der Container gelöscht, sind alle Daten im Container-Dateisystem verloren. Für eine Datenbank ist das ein fundamentales Problem, das von Anfang an richtig adressiert werden muss.
Die Lösung heißt Volume. Docker kennt zwei Haupttypen: Named Volumes und Bind Mounts. Beide persistieren Daten über Container-Neustarts hinaus, unterscheiden sich aber erheblich in Verhalten, Performance und Isolation — Punkte, die für MySQL Docker im Produktionsbetrieb entscheidend sind.
2. Named Volume vs. Bind Mount: Was wann verwenden
Ein Bind Mount verbindet ein Verzeichnis auf dem Host-Dateisystem direkt mit einem Pfad im Container. Auf Linux ist das performant, weil kein Übersetzungs-Layer zwischen Host und Container steht. Auf macOS und Windows hingegen führt die Virtualisierungsschicht zu deutlichem I/O-Overhead — für MySQL mit vielen kleinen Schreiboperationen ein erhebliches Problem.
Ein Named Volume wird von Docker selbst verwaltet. Die Daten liegen in einem Docker-internen Bereich, isoliert vom Host-Dateisystem. Das bietet bessere Portabilität zwischen Maschinen, einfachere Backup-Befehle via docker run --volumes-from und keine Berechtigungsprobleme durch Host-UID-Konflikte.
-- Named Volume: empfohlen für Produktion und Mac/Windows-Entwicklung
-- services:
-- mysql:
-- image: mysql:8.0
-- volumes:
-- - mysql_data:/var/lib/mysql # Named Volume
-- volumes:
-- mysql_data:
-- Bind Mount: performanter auf Linux, problematisch auf Mac/Windows
-- services:
-- mysql:
-- image: mysql:8.0
-- volumes:
-- - ./data/mysql:/var/lib/mysql # Bind Mount
-- Performance-Check nach Setup: InnoDB I/O-Status prüfen
SHOW GLOBAL STATUS LIKE 'Innodb_data_%';
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_%';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
Für Magento-Entwicklung auf Linux sind Bind Mounts praktisch, weil man direkt auf die Datenbankdateien zugreifen kann. Für containerisierte Produktionsumgebungen und alle macOS/Windows-Setups sind Named Volumes die bessere Wahl.
3. docker-compose MySQL-Konfiguration richtig aufsetzen
Die Standard-Konfiguration des MySQL-Docker-Images ist für kleine Container nicht optimiert. Besonders der InnoDB Buffer Pool ist standardmäßig auf 128MB gesetzt, was für Magento deutlich zu wenig ist. Die Konfiguration kann über Umgebungsvariablen oder eine eigene my.cnf-Datei über einen Volume-Mount eingebracht werden.
-- Empfohlene mysql-Einstellungen als my.cnf (config/mysql/my.cnf)
-- [mysqld]
-- innodb_buffer_pool_size = 1G
-- innodb_buffer_pool_instances = 4
-- innodb_log_file_size = 256M
-- innodb_flush_log_at_trx_commit = 2
-- innodb_flush_method = O_DIRECT
-- max_connections = 200
-- slow_query_log = 1
-- long_query_time = 2
-- sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,
-- ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
-- Konfiguration nach Start prüfen
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_log_file_size';
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'sql_mode';
4. OOM-Kill: Der gefährlichste Container-Fehler
Der häufigste und gefährlichste Fehler bei MySQL Docker ist ein falsches Verhältnis zwischen innodb_buffer_pool_size und dem Container-Memory-Limit. Wenn der MySQL-Prozess mehr Speicher anfordert als das Container-Limit erlaubt, beendet der Linux-Kernel den Prozess mit OOM-Kill (Out of Memory Kill). MySQL hat dann keine Chance, einen sauberen Shutdown durchzuführen.
Die Regel: innodb_buffer_pool_size muss deutlich kleiner als das Container-Memory-Limit sein. MySQL benötigt neben dem Buffer Pool noch Speicher für Verbindungs-Threads, temporäre Tabellen, Sort-Buffer und den Query-Cache. Als Faustregel: Buffer Pool maximal 60-70% des Container-Limits.
-- Memory-Nutzung in laufendem MySQL prüfen
SELECT
VARIABLE_NAME,
ROUND(VARIABLE_VALUE / 1024 / 1024, 1) AS mb
FROM performance_schema.global_variables
WHERE VARIABLE_NAME IN (
'innodb_buffer_pool_size',
'innodb_log_buffer_size',
'key_buffer_size',
'tmp_table_size',
'max_heap_table_size',
'sort_buffer_size',
'read_buffer_size',
'join_buffer_size'
);
-- Maximale Speichernutzung abschätzen:
-- Total = innodb_buffer_pool_size
-- + (sort_buffer_size + read_buffer_size + join_buffer_size) * max_connections
-- Muss kleiner als Container-Limit sein!
-- Sichere Konfiguration für 2GB Container:
-- innodb_buffer_pool_size = 1200M (60% von 2048MB)
-- tmp_table_size = 64M
-- max_heap_table_size = 64M
-- sort_buffer_size = 2M
-- max_connections = 150
-- OOM-Kill in Container-Logs erkennen
-- docker inspect mysql_container | grep OOMKilled
-- Ausgabe: "OOMKilled": true -> Container wurde durch OOM-Kill beendet
5. Liveness und Readiness Probes für MySQL
In Docker Compose und Kubernetes müssen andere Dienste warten, bis MySQL wirklich bereit ist — nicht nur gestartet. Ein Container, der läuft, ist nicht zwingend bereit für Datenbankverbindungen. InnoDB braucht nach dem Start Zeit, um den Buffer Pool zu initialisieren und ggf. eine Crash Recovery durchzuführen.
-- docker-compose: healthcheck für MySQL (YAML-Syntax)
-- services:
-- mysql:
-- healthcheck:
-- test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpassword"]
-- interval: 10s
-- timeout: 5s
-- retries: 10
-- start_period: 30s
-- magento:
-- depends_on:
-- mysql:
-- condition: service_healthy
-- Kubernetes livenessProbe:
-- exec:
-- command: ["mysqladmin", "ping", "-h", "localhost"]
-- initialDelaySeconds: 30
-- periodSeconds: 10
-- timeoutSeconds: 5
-- failureThreshold: 3
-- Kubernetes readinessProbe (strenger als liveness):
-- exec:
-- command: ["mysql", "-u", "root", "-prootpassword", "-e", "SELECT 1"]
-- initialDelaySeconds: 5
-- periodSeconds: 2
-- timeoutSeconds: 1
-- failureThreshold: 30
-- MySQL-Bereitschaft nach Start selbst prüfen
SHOW GLOBAL STATUS LIKE 'uptime';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SELECT @@global.read_only; -- Sollte 0 sein (nicht read-only)
6. Init-Skripte und Datenbankinitialisierung
Das offizielle MySQL-Docker-Image führt beim ersten Start automatisch alle .sql, .sql.gz und .sh-Dateien aus, die im Verzeichnis /docker-entrypoint-initdb.d/ liegen. Das ist der empfohlene Weg, um eine initiale Datenbankstruktur oder Seed-Daten einzuspielen. Wichtig: Diese Skripte laufen nur beim allerersten Start, wenn /var/lib/mysql noch leer ist.
-- Volume-Mount für Init-Skripte (docker-compose):
-- volumes:
-- - ./init-scripts:/docker-entrypoint-initdb.d:ro
-- init-scripts/01-create-user.sql
CREATE USER IF NOT EXISTS 'magento'@'%' IDENTIFIED BY 'magento';
GRANT ALL PRIVILEGES ON magento.* TO 'magento'@'%';
FLUSH PRIVILEGES;
-- init-scripts/02-set-settings.sql
SET GLOBAL time_zone = '+00:00';
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;
-- Nach Initialisierung Benutzer und Berechtigungen prüfen
SHOW DATABASES;
SHOW GRANTS FOR 'magento'@'%';
SELECT user, host, plugin FROM mysql.user WHERE user = 'magento';
7. InnoDB Crash Recovery nach OOM-Kill
Wenn MySQL durch OOM-Kill abrupt beendet wird, hinterlässt InnoDB unfertige Transaktionen im Redo Log. Beim nächsten Start führt InnoDB automatisch eine Crash Recovery durch: Es replayed abgeschlossene Transaktionen aus dem Redo Log und rollt unfertige Transaktionen zurück. Das ist ein eingebauter Schutzmechanismus, der in den meisten Fällen zuverlässig funktioniert.
-- Typische InnoDB-Crash-Recovery-Meldungen im Error Log:
-- [InnoDB] Starting crash recovery.
-- [InnoDB] Database was not shut down normally!
-- [InnoDB] Log scan progressed past the checkpoint lsn ...
-- [InnoDB] Doing recovery: scanned up to log sequence number ...
-- [InnoDB] Crash recovery finished successfully.
-- Nach Recovery: Tabellen-Integrität prüfen
CHECK TABLE sales_order;
CHECK TABLE catalog_product_entity;
CHECK TABLE quote;
-- InnoDB-Status nach Recovery analysieren
SHOW ENGINE INNODB STATUS\G
-- Redo-Log-Größe und aktuelle LSN prüfen
SHOW GLOBAL STATUS LIKE 'Innodb_lsn_current';
SHOW GLOBAL STATUS LIKE 'Innodb_lsn_last_checkpoint';
-- innodb_force_recovery: NUR bei schweren Korruptionen (nicht für normale OOM-Recovery)
-- 0 = normal (Standard, immer zuerst versuchen)
-- 1 = SRV_FORCE_IGNORE_CORRUPT: Korrupte Pages ignorieren
-- 2 = SRV_FORCE_NO_BACKGROUND: Background-Threads deaktivieren
-- Maximal 3 für Daten-Export, nie dauerhaft auf > 0 setzen!
SHOW VARIABLES LIKE 'innodb_force_recovery';
Nach einer OOM-Kill-Episode sollte man nicht nur auf erfolgreiche Recovery vertrauen, sondern aktiv die kritischen Tabellen prüfen. In Magento besonders: sales_order, quote und catalog_product_entity. Wenn innodb_force_recovery auf 0 keinen Start erlaubt, muss man schrittweise erhöhen, Daten exportieren und dann neu aufsetzen.
8. Kubernetes StatefulSet für MySQL
In Kubernetes ist MySQL kein Deployment, sondern ein StatefulSet. Der Unterschied ist fundamental: Deployments sind für zustandslose Workloads gedacht, StatefulSets für zustandsbehaftete Dienste mit stabiler Netzwerkidentität und persistentem Storage. Jeder Pod in einem StatefulSet bekommt einen stabilen DNS-Namen (mysql-0) und einen eigenen PersistentVolumeClaim.
-- Kubernetes StatefulSet Kernkonfiguration (vereinfacht):
-- apiVersion: apps/v1
-- kind: StatefulSet
-- metadata:
-- name: mysql
-- spec:
-- serviceName: mysql
-- replicas: 1
-- template:
-- spec:
-- containers:
-- - name: mysql
-- image: mysql:8.0
-- resources:
-- requests:
-- memory: "1Gi"
-- limits:
-- memory: "2Gi" # Container-Limit
-- volumeMounts:
-- - name: mysql-data
-- mountPath: /var/lib/mysql
-- volumeClaimTemplates:
-- - metadata:
-- name: mysql-data
-- spec:
-- accessModes: ["ReadWriteOnce"]
-- resources:
-- requests:
-- storage: 50Gi
-- PVC und Pod-Status in Kubernetes prüfen
-- kubectl get pvc -n production
-- kubectl get pods -n production -l app=mysql
-- kubectl logs mysql-0 -n production | grep -i "crash\|recovery\|ready"
-- MySQL im StatefulSet-Pod erreichen
-- kubectl exec -it mysql-0 -n production -- mysql -u root -p
-- Nach Start: Datenbank-Status prüfen
SHOW GLOBAL STATUS LIKE 'uptime';
SHOW DATABASES;
9. Magento docker-magento MySQL-Konfiguration
Mark Shust's docker-magento verwendet standardmäßig MariaDB als Datenbankcontainer. Die Konfiguration liegt in compose.yaml und kann über eine custom my.cnf erweitert werden. Für Magento sind besonders der Buffer Pool, die Log-File-Größe und der sql_mode kritisch.
-- Empfohlene MySQL/MariaDB-Einstellungen für Magento (my.cnf)
-- [mysqld]
-- innodb_buffer_pool_size = 1G
-- innodb_buffer_pool_instances = 4
-- innodb_log_file_size = 512M
-- innodb_log_buffer_size = 16M
-- innodb_flush_log_at_trx_commit = 2
-- innodb_flush_method = O_DIRECT
-- sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,
-- ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
-- max_allowed_packet = 64M
-- max_connections = 300
-- tmp_table_size = 128M
-- max_heap_table_size = 128M
-- Status nach Magento-Setup prüfen
SELECT
t.table_schema,
ROUND(SUM(t.data_length + t.index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables t
WHERE t.table_schema = 'magento'
GROUP BY t.table_schema;
-- Größte Tabellen in der Magento-Datenbank
SELECT table_name, table_rows,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'magento'
ORDER BY data_length + index_length DESC
LIMIT 15;
10. mysqldump-Backup in containerisierten Umgebungen
In containerisierten Umgebungen ist mysqldump die einfachste Backup-Strategie. Der Dump kann direkt vom Host aus über docker exec ausgeführt werden, ohne in den Container gehen zu müssen. Das Flag --single-transaction ist dabei essenziell: Es erstellt einen konsistenten Snapshot für InnoDB-Tabellen ohne die Datenbank zu sperren.
-- mysqldump aus Docker-Container ausführen:
-- docker exec mysql_container mysqldump \
-- --single-transaction \
-- --routines \
-- --triggers \
-- --set-gtid-purged=OFF \
-- -u root -prootpassword magento | gzip > backup-$(date +%Y%m%d-%H%M%S).sql.gz
-- Nur Struktur (ohne Daten):
-- docker exec mysql_container mysqldump \
-- --no-data --routines --triggers \
-- -u root -prootpassword magento > structure.sql
-- Restore in Container:
-- zcat backup.sql.gz | docker exec -i mysql_container \
-- mysql -u root -prootpassword magento
-- Backup-Integrität prüfen (außerhalb MySQL):
-- zcat backup.sql.gz | grep "CREATE TABLE" | wc -l # Tabellenanzahl
-- zcat backup.sql.gz | tail -5 # Ende des Dumps prüfen
-- In Magento: Log-Tabellen die vom Backup ausgeschlossen werden können
SELECT table_name,
ROUND((data_length + index_length) / 1024 / 1024, 1) AS mb
FROM information_schema.tables
WHERE table_schema = 'magento'
AND table_name REGEXP '^(report_event|catalogrule_product_price|index_)'
ORDER BY data_length + index_length DESC;
Mironsoft
MySQL Docker und Kubernetes zuverlässig betreiben — wir helfen beim richtigen Setup
Von der Volume-Konfiguration über OOM-Kill-Prävention bis zur Kubernetes-Migration — wir optimieren Ihre containerisierte Datenbankinfrastruktur für Magento.
Docker Setup Review
Volumes, Limits, my.cnf und Healthchecks für Magento optimieren
K8s Migration
StatefulSets, PVCs und MySQL Operator für Produktionsumgebungen einrichten
Backup-Strategie
Automatisierte Backups, Recovery-Tests und Disaster-Recovery-Plan
12. Zusammenfassung
MySQL Docker ist produktionstauglich, wenn man die Fallstricke kennt. Named Volumes schützen Daten über Container-Neustarts hinaus, das richtige Verhältnis zwischen innodb_buffer_pool_size und Container-Memory-Limit verhindert OOM-Kill, und Healthchecks sorgen dafür, dass abhängige Dienste erst starten, wenn MySQL wirklich bereit ist. InnoDB Crash Recovery ist automatisch und zuverlässig — ersetzt aber keine solide Memory-Planung.
In Kubernetes erfordert MySQL ein StatefulSet mit PersistentVolumeClaim. Regelmäßige mysqldump-Backups über docker exec sind einfach zu automatisieren und sollten zusammen mit Volume-Snapshots als zweistufige Backup-Strategie eingesetzt werden.
MySQL Docker und Kubernetes — Das Wichtigste auf einen Blick
Volumes
Named Volumes für Produktion und Mac/Windows. Bind Mounts auf Linux performanter, aber weniger isoliert.
OOM-Kill
innodb_buffer_pool_size maximal 60-70% des Container-Limits. Sonst OOM-Kill ohne sauberen Shutdown.
Crash Recovery
InnoDB replayed Redo Log automatisch nach OOM-Kill. Anschließend kritische Tabellen mit CHECK TABLE prüfen.
Kubernetes
StatefulSet + PersistentVolumeClaim, nicht Deployment. Stable Pod-Identity ist Voraussetzung für Replikation.