Der Docker und seine Daten

Docker Container ermöglichen es, Applikationen mit unterschiedlichen und sogar widersprüchlichen Anforderungen von einander zu isolieren. Darüber hinaus ermöglichen sie eine sehr konsequente Trennung der einzelnen Ebenen einer Anwendung sowie den Daten.

Wie ich vor ein paar Tagen geschrieben habe arbeite ich mich gerade verstärkt in Docker ein. Der Grund hierfür ist, dass ich für unterschiedliche Projekte auch immer wieder unterschiedliche Rahmenbedingungen habe. Statt unterschiedliche Datenbanken und Programmiersprachen irgendwie parallel zu installieren möchte sie lieber sauber voneinander trennen und je nach Bedarf schnell und bequem zur Verfügung haben. Und dafür ist Docker ein tolles Werkzeug. Zudem erlaubt mir Docker, die Daten einer Anwendung von der Software zu trennen.

In diesem Artikel erkläre ich kurz, wie Docker Container funktionieren und zeige am Beispiel eines MySQL-Containers die unterschiedlichen Möglichkeiten, mit Docker seine Daten (dauerhaft) zu speichern. In einem zweiten Artikel werde ich ein wenig auf die Netz-Funktionen von Docker eingehen und anhand einer einfachen PHP-Anwendung die wunderbare Welt des Container Linkings demonstrieren. Zum (vorläufigen) Abschluss gibt es noch Remote Debugging via XDebug im Docker Containern.

Ein Hinweis vorweg: Ich lasse in allen Beispielen das Kommando „sudo“ weg. Das dient zum Einen der Lesbarkeit, zum Anderen kann man bei der Benutzung von boot2docker sudo nicht verwenden. Außerdem war es kompliziert genug mir das dockern ohne sudo zu ermöglichen…

Schichtarbeit

Um zu verstehen, warum ich der Datenhaltung bei Docker so viel Aufmerksamkeit schenke, ist ein kurzer Blick auf die Innereien von Docker notwendig. Wie schon in meinem Artikel „von .git nach Gerrit“ auch hier das Versprechen: Ich halte mich kurz. 🙂 Wer mehr wissen will dem sei die offizielle Dokumentation empfohlen.

Eine der Grundlagen von Docker ist ein Union File System. Das besondere an so einem UnionFS ist, dass man nicht genau eine Version einer Datei oder eines Verzeichnisses hat, sondern diverse Varianten „stapeln“ kann. Ursprünglich wurden UnionFS für Live-CDs wie etwa knoppix entwickelt. Obwohl die CD nicht beschreibbar ist, kann man neue Programme installieren oder existierende Dateien verändern. Diese Änderungen werden in einer für die Anwendungen und damit den Nutzer transparenten Schicht über dem ursprünglichen Inhalt verwaltet und nur im RAM gespeichert. Wird der Rechner neu gebootet gehen die Änderungen verloren.

Docker macht es genauso. Ausgehend von einem sogenannten „Image“ (z.B. Ubuntu 14.04) wird ein Container gestartet. Änderungen innerhalb des Containers werden in einer Schicht über dem Image verwaltet und beeinflussen das Image selbst in keiner Weise. Daher können sich mehrere Container problemlos ein Image teilen und beliebig Daten schreiben ohne einander irgendwie zu beeinflussen. Stoppt man den Container sind die Änderungen weg.

Data Volumes

Natürlich ist eine solche Vergänglichkeit manchmal gar nicht im Sinne des Erfinders (oder Entwicklers) – bei einer Datenbank zum Beispiel. Daher bietet Docker mit Hilfe von (Data-)Volumes die Option Änderungen im laufenden Betrieb zu speichern. Dabei gibt es vier Möglichkeiten.

Volumes im Dockerfile

Der Aufbau eines Image wird über ein Dockerfile definiert. Das sieht im Fall des offiziellen mysql-Image (gekürzt) so aus

FROM debian:wheezy

# install mysql here

VOLUME /var/lib/mysql

COPY docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

EXPOSE 3306
CMD ["mysqld"]

In Zeile 5 wird das Volume /var/lib/mysql definiert. Das bedeutet, dass Änderungen, die innerhalb des Containers an diesem Verzeichnis durchgeführt, werden dauerhaft sind. Docker legt hierfür auf dem Host für jeden Container, der das Image verwendet, ein Verzeichnis unter /var/lib/docker/vfs/dir/ an.

Der Aufruf

docker run --name db1 -e MYSQL_ROOT_PASSWORD=rootpwd -e MYSQL_DATABASE=test1 -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpwd -d mysql:latest

lädt von docker hub das passende Image runter und startet mit den übergebenen Parametern einen neuen Container namens ‚db1‘. Mit docker inspect db1 kann ich sehen, dass ein Volume angelegt wurde:

    "Volumes": {
        "/var/lib/mysql": "/var/lib/docker/vfs/dir/4586fc1e5536aa8c409fa94d92c9fb287bf0ae8a6576ec9c49947d6526726571"
    },

Ein (sudo-)Blick in das Verzeichnis zeigt, dass die Datenbank angelegt wurde.

> sudo -s
> ls /var/lib/docker/vfs/dir/4586fc1e5536aa8c409fa94d92c9fb287bf0ae8a6576ec9c49947d6526726571
auto.cnf  ibdata1  ib_logfile0  ib_logfile1  mysql  performance_schema  test1

Auch wenn ich den Container mittels docker stop/restart/start an und aus schalte bleiben die Daten erhalten. ACHTUNG! Diese Volumes werden nicht automatisch entsorgt und es gibt keine Garbage Collection. Wer beim Löschen eines Containers nicht aufpasst hat hinterher ein sogenanntes „dangling volume“ auf der Platte. Um einen Container und die zugehörigen Volumes zu löschen muss docker rm -v <containername> verwendet werden.

Volumes bei Starten anlegen lassen

Auch wenn das Volume nicht im Dockerfile definiert wurde kann man Docker anweisen, für eines oder mehrere Verzeichnisse im Container ein Volume anzulegen. Hierfür dient der Parameter -v. So erzeugt

docker create -v /var/lib/mysql --name mysql_data tianon/true true

einen Container namens mysql_data mit einem Volume für /var/lib/mysql. Das Image tianon/true ist im Übrigen so gut wie leer:

FROM scratch
ADD true-asm /true
CMD ["/true"]

scratch wiederum ist wirklich ganz leer; es basiert auf einem leeren tar-Archiv. Wir werden mysql_data im Übrigen gleich noch benötigen…

Der Vorteil dieser beiden Methoden ist, dass man sich um nichts kümmern muss und es nicht zu Konflikten zwischen Containern kommen kann. Der Nachteil ist, dass so ein Volume fest seinem Container zugeordnet wird. Wenn ich zum Beispiel eine andere Version von MySQL verwenden will muss ich die Daten aus dem alten Container ex- und in den neuen importieren.

Ein Verzeichnis auf dem Host durchreichen

Wer nicht möchte, dass Docker das Volume verwaltet, kann statt dessen selbst ein Verzeichnis auswählen, in das die Daten geschrieben werden. Man ist dann aber auch dafür verantwortlich dafür zu sorgen, dass sich nicht mehrere Container gegenseitig in die Quere kommen. Der Aufruf

docker run --name db2 -e MYSQL_ROOT_PASSWORD=rootpwd -e MYSQL_DATABASE=test1 -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpwd -d -v /home/cgd/dockervolumes/var/lib/mysql/:/var/lib/mysql mysql:latest

bewirkt, dass Docker die Änderungen in /var/lib/mysql in den Ordner in meinem home schreibt:

> ls dockervolumes/var/lib/mysql/
auto.cnf  ibdata1  ib_logfile0  ib_logfile1  mysql  performance_schema  test1

Auch wenn ich den Container mit -v lösche wird das Verzeichnis nicht entfernt. Docker bietet im Übrigen auch die Möglichkeit ein Verzeichnis als read-only Volume zu nutzen. Dafür wird :ro an den Parameter gehängt.

docker run ... -v /hostdir:/containerdir:ro

Der Vorteil dieser Methode ist, dass ich die Daten sehr leicht unterschiedlichen Containern (z.B. von unterschiedlichen Datenbank-Images) zur Verfügung stellen kann. Nachteil ist, dass ich selbst aufpassen muss, dass nicht zwei Container gleichzeitig auf dem selben Volume arbeiten. Zudem muss ich dafür sorgen, dass die entsprechenden Verzeichnisse vorhanden und für Docker erreichbar sind. Und auch aufräumen muss man selbst…

Data Only Container

Zu guter Letzt gibt es noch die Möglichkeit die Volumes aus einem anderen Container zu verwenden. Das hat vereint die Vorteile der obigen Ansätze und vermeidet zum größten Teil deren Nachteile. Wir erinnern uns an dieser Stelle an den Container mit dem hübschen Namen ‚mysql_data‘ der ganz zufällig /var/lib/mysql als Docker Volume eingebunden hat… Diesmal starte ich den Datenbank Container mit dem Parameter –volumes-from:

docker run --volumes-from mysql_data --name db3 -e MYSQL_ROOT_PASSWORD=rootpwd -e MYSQL_DATABASE=test1 -e MYSQL_USER=testuser -e MYSQL_PASSWORD=testpwd -d mysql:latest

Docker entnimmt jetzt den Metadaten (die man mit docker inspect mysql_data  bekommt) welche Volumes in der Containerdefinition stehen und reicht diese direkt an db3 weiter. Hierfür muss mysql_data nicht laufen weshalb ich oben auch nur docker create statt docker run  verwendet habe. Wenn ich später auf eine andere Datenbank-Version wechseln möchte reicht es, wenn ich db3 stoppe und einen neuen Container basierend auf einem anderen Image erzeuge. 

An dieser Stelle noch ein Hinweis zum Schluss: Im Internet findet man sehr häufig Beispiele von Data Containern die auf dem busybox-Image aufsetzen. Busybox ist ein sehr leichtgewichtige Linuxdistribution und das Image ist relativ klein. Daher scheint es sinnvoll für einen Container, der gar nicht laufen soll, ein kleines Image zu verwenden. Das ist Unsinn. Falls ich busybox nicht noch für einen anderen Container brauche verschwendet dieses Vorgehen Platz. Wenn ich statt dessen auf einem Image aufsetze, dass sowieso schon in Verwendung ist, wird es vom Data Container einfach auch verwendet. Es wäre von daher erstmal sogar besser den Data Container von mysql:latest statt von tianon/true zu erzeugen – jedenfalls so lange ich das selbe Image in Gebrauch habe.

 

Das Titelbild stammt von https://www.flickr.com/photos/xmodulo/14098888813 und steht unter der CC BY 2.0 Lizenz.

Teilen Sie diesen Beitrag

Das könnte dich auch interessieren …

Eine Antwort

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert