K8S
DOCKER
SQL · MySQL Docker · Kubernetes · InnoDB · Magento
MySQL in Docker und Kubernetes: Persistenz, Volumes, Limits, Crash Recovery

Volumes, OOM-Kill-Risiko, automatische InnoDB-Crash-Recovery, Kubernetes StatefulSets und Backup-Strategien — alles was man für MySQL Docker wirklich wissen muss.

Named vs. Bind Volumes OOM-Kill verhindern Crash Recovery Kubernetes StatefulSet

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.

13. FAQ: MySQL Docker und Kubernetes

1 Verliere ich Daten wenn ein MySQL-Docker-Container gestoppt wird?
Nein, wenn ein Named Volume oder Bind Mount für /var/lib/mysql konfiguriert ist. Daten im Container-Layer selbst gehen beim Löschen verloren — daher immer ein Volume verwenden.
2 Was ist der Unterschied zwischen Named Volume und Bind Mount?
Named Volumes werden von Docker verwaltet, bieten bessere Isolation und funktionieren auf allen Betriebssystemen. Bind Mounts sind auf Linux schneller, aber auf macOS und Windows langsamer.
3 Warum passiert ein OOM-Kill bei MySQL in Docker?
Wenn innodb_buffer_pool_size plus Thread-Speicher das Container-Memory-Limit überschreitet, beendet der Linux-Kernel MySQL. Buffer Pool maximal 60-70% des Container-Limits setzen.
4 Erholt sich MySQL automatisch von einem OOM-Kill?
Ja, InnoDB führt beim nächsten Start automatisch Crash Recovery durch. Danach sollte man kritische Tabellen mit CHECK TABLE prüfen.
5 Warum brauche ich ein StatefulSet statt Deployment in Kubernetes?
StatefulSets geben jedem Pod einen stabilen DNS-Namen und eigenen PersistentVolumeClaim. Das ist für MySQL für stabilen Storage-Pfad und Netzwerkidentität notwendig.
6 Wie richte ich einen Healthcheck für MySQL ein?
In Docker Compose: healthcheck mit mysqladmin ping als CMD. In Kubernetes: livenessProbe (mysqladmin ping) und readinessProbe (SELECT 1) getrennt konfigurieren.
7 Wie mache ich Backups von MySQL in Docker?
Mit docker exec und mysqldump --single-transaction. Für Kubernetes: CronJob mit kubectl exec. Immer regelmäßig den Restore-Prozess testen.
8 Was ist der richtige innodb_buffer_pool_size für Magento in Docker?
60-70% des Container-Memory-Limits. Bei 2GB Container ca. 1.2GB. Container-Limit und Buffer-Pool immer zusammen planen.
9 Wie werden Init-Skripte in MySQL-Docker ausgeführt?
Alle .sql und .sh-Dateien in /docker-entrypoint-initdb.d/ werden beim ersten Start ausgeführt — aber nur wenn /var/lib/mysql noch leer ist.
10 Wie nutzt Magento docker-magento MySQL?
docker-magento verwendet standardmäßig MariaDB. Konfiguration über custom my.cnf via Volume-Mount. Wichtig: innodb_buffer_pool_size, max_allowed_packet und sql_mode korrekt setzen.