Portfolio |

Storage in Docker & OpenShift

Im fünften Teil unserer Veröffentlichungsreihe zu „Docker vs. OpenShift“ nehmen wir uns dem Thema Storage an und erklären, warum es wichtig ist, über die Persistierung von Daten nachzudenken.

In den letzten Beiträgen unserer Serie haben wir bereits die Grundlagen für die Nutzung von Docker und OpenShift aufgezeigt. Der Einsatz von Images, Pods und Containern wurde erläutert. Bereits in unserem ersten Beitrag wurde das Thema Speicherung von Daten im Docker-Kontext angeschnitten.

In diesem Blogbeitrag steigen wir nun tiefer in die Materie ein und erklären, wieso es bei der Benutzung von Docker und OpenShift wichtig ist, über die Persistierung von Daten (Datenerhaltung) nachzudenken. Dabei klären wir die Fragen, warum Pods und Container keine Daten sichern und wie eine Persistierung in Docker und OpenShift konkret umgesetzt werden kann.

Warum Daten persistieren?

Wie bereits in unserem Beitrag über Container erläutert, handelt es sich bei einem Container um die Laufzeitinstanz eines Images. Sowohl Container als auch Pods sind zustandslos.

Wenn ein Container auf Basis eines Images gestartet wird, fügt Docker der schreibgeschützten Schicht des Images eine Schicht mit Lese- und Schreibzugriff auf dem Container hinzu. Dadurch bleiben die ursprünglichen Dateien auf dem Image unverändert. Die Daten, die während der Laufzeit des Containers entstehen, werden in der neuen Schicht abgelegt. Wird der Container dann gelöscht, geht diese Lese- und Schreibschicht mitsamt aller darauf gesammelten Daten verloren. Selbiges gilt für Pods (Verbund eines oder mehrerer Container) im OpenShift-Kontext. Beim Löschen eines Pods verfallen alle darin enthaltenen, zur Laufzeit generierten, Daten.

Gründe für das Schließen eines Pods können beispielsweise folgende sein:

  • Ein Pod mit einer höheren Priorität wird gestartet
  • Der Host oder der Pod stürzen ab und müssen neu gestartet werden

Kurzgefasst: Sobald ein Container gelöscht wird, sind alle zur Laufzeit darauf abgelegten Daten verloren. Bei einer Neuerstellung wird ein Container immer frisch auf Basis eines Images gebaut, welches nur die Daten der schreibgeschützten Schicht enthält.

Was sind relevante Daten für die Persistenz?

Bevor wir uns dem Thema widmen, wie Daten in Docker und OpenShift gespeichert werden können, müssen wir zuerst jene Daten identifizieren, die für eine Speicherung relevant sind.
Dies lässt sich am besten anhand eines Beispiels verdeutlichen.

Beispiel: Datenbanksystem

Abbildung 1: Ein Datenbanksystem besteht aus der Datenbank und dem Datenbankmanagementsystem

Wir möchten Veränderungen der Datenbank und des Managementsystems auch außerhalb der Laufzeit des Containers sichern können, um immer mit den aktuellsten Datensätzen und Konfigurationen arbeiten zu können – auch nach der Neuerstellung des Containers. Persistiert werden sollte somit alles, was für die Ausführung benötigt wird, dauerhaft relevante Informationen enthält und über das auszuführende Image hinausgeht. Da wir nun identifiziert haben, welche Daten sicherungswürdig sind, möchten wir im nächsten Abschnitt darauf eingehen, wie eine Persistierung umgesetzt werden kann.

Wie wird Datenpersistenz in Docker umgesetzt?

Container laufen auf einem Host und besitzen ein virtuelles File-System. Damit können Daten nicht ohne Weiteres zwischen dem Host und dem Container ausgetauscht oder anderen laufenden Containern zur Verfügung gestellt werden. Docker bietet zwei Möglichkeiten, damit das virtuelle Container- und das Host-File-System miteinander verknüpft werden können. Durch Volumes und Bind-Mounts können Containern Daten des Hosts zur Verfügung gestellt oder Daten aus den Containern auf dem Host-System abspeichert werden.

Bind-Mounts sind schon lange in Docker verfügbar. Dabei wird eine Datei oder ein Verzeichnis auf dem Host-Rechner in einen Container „eingehängt“. Die Datei oder das Verzeichnis werden durch ihren vollständigen Pfad auf dem Host-Rechner referenziert. Wenn das Verzeichnis auf dem Docker-Host noch nicht vorhanden ist, wird dieses automatisch erstellt. Dabei gibt es keine Einschränkung, welche Datei oder welches Verzeichnis an welcher Stelle im virtuellen Filesystem des Containers eingebunden wird.

Einbindung des Host-Verzeichnisses:

$ docker run -v "$pwd":/container/pfad

Mit $pwd wird das Arbeitsverzeichnis als Quelle angegeben. Damit weisen wir Docker an, den Bind-Mount in dem Verzeichnis zu erstellen, in dem wir uns gerade befinden.

Volumes bieten im Vergleich zu Bind-Mounts eine erweiterte Funktionalität. Diese werden auf dem Host-Dateisystem gespeichert und von Docker direkt verwaltet. Dabei bleiben sie in der Kernfunktionalität vom Host-Rechner isoliert.

Ein Volume kann manuell oder von Docker automatisiert während der Erstellung eines Containers erzeugt werden. Es ist möglich, ein Volume in mehreren Containern gleichzeitig einzubinden. Ein besonderer Vorteil eines Volumes ist, dass dieses nicht automatisch gelöscht wird, selbst wenn es von keinem laufenden Container verwendet wird. Es verbleibt auf dem Host-Dateisystem zur Einbindung durch Docker verfügbar. Ein eingebundenes Volume kann benannt oder anonym sein. Anonyme Volumes erhalten keinen expliziten Namen, wenn sie zum ersten Mal in einen Container eingebunden werden. Docker gibt ihnen daher einen zufälligen Namen. Des Weiteren bieten Volumes die Möglichkeit, Daten auf entfernten Hosts oder bei Cloud-Anbietern speichern können.

Vorteile von Volumes:

  • Volumes sind einfacher zu sichern und zu migrieren
  • Volumes können mit Docker-CLI-Befehlen oder der Docker-API verwaltet werden
  • Volumes können von mehreren Containern gemeinsam genutzt werden
  • Volumes funktionieren sowohl auf Linux- als auch auf Windows-Containern

Wie lege ich Volumes an und benutze sie?

Da nun aufgezeigt wurde was Volumes sind, stellt sich die Frage, wie diese angelegt und verwendet werden. Volumes werden von Docker erstellt und verwaltet. Unterschieden wird dabei zwischen anonymen, benannten und Host-Volumes.

Anonyme Volumes
Der Name als auch der Speicherort von anonymen Volumes wird von Docker verwaltet. Durch den zufälligen Namen kann es bei dem Verweis auf ein anonymes Volume jedoch zu Identifikationsproblemen kommen.

Ausführen:

$ docker run -v /container/pfad

Benannte Volumes
Benannte Volumes ähneln anonymen Volumes darin, dass beide durch Docker verwaltet werden und die gleiche Flexibilität bieten. Durch ihre vom Nutzer gewählte Benennung sind benannte Volumes hingegen leichter zu verwalten und zu verwenden.

Erstellung des Volumes:

$ docker volume create nameOfVolume

Ausführen:

$ docker run -v nameOfVolume:/container/pfad

Host-Volumes
Benannte und anonyme Volumes werden im Teil des Host-Dateisystems gespeichert, der von Docker verwaltet wird. Im Gegensatz dazu werden Host-Volume an einem benutzerdefinierten Ort des Hosts gespeichert. Daher ergibt ein Host-Volume immer dann Sinn, wenn ein festgelegter Speicherort verwendet werden muss.

Ausführen:

$ docker run -v /host/pfad:/container/pfad

In den letzten Abschnitten haben wir uns damit beschäftigt, wie Volumes im Zusammenhang mit Docker funktionieren. In den folgenden Abschnitten werden wir nun aufzeigen, wie OpenShift es ermöglicht, Daten zu persistieren.

Wie wird Datenpersistenz in OpenShift umgesetzt? Welche Schichten gibt es?

Bereits in einem der letzten Beiträge haben wir den Unterschied zwischen Pods und Containern aufgezeigt. In OpenShift kommen Pods zum Einsatz. Wie bereits in der Einleitung beschrieben, gehen alle Daten der obersten Lese- und Schreibschicht mit dem Beenden eines Pods verloren. Um Daten zu persistieren, kommen auch innerhalb von OpenShift Volumes zum Einsatz.

Der persistente Speicher in OpenShift besteht aus drei Objekten: dem Pod, dem PersistentVolumeClaim (PVC) und dem PersistentVolume (PV).

Abbildung 2: Schematischer Aufbau des persistenten Speichers

OpenShift Persistent Volumes sind Speichereinheiten, die von einem Administrator als Teil eines OpenShift-Clusters bereitgestellt werden. Persistente Volumes sind unabhängig vom Lebenszyklus des Pods, der sie verwendet. Damit werden selbst bei Herunterfahren des Pods die Daten im Volume nicht gelöscht. Da persistente Volumes vom Administrator bereitgestellte Volumes sind, haben diese vordefinierte Eigenschaften wie Dateisystem, Größe und Bezeichnung.

Damit ein Pod ein Volume nutzen kann, muss also ein Volume angefordert werden. Dieser Anspruch wird über ein PersistentVolumeClaim (PVC) gestellt. PVCs beschreiben die Speicherkapazität und die Eigenschaften, welche ein Pod benötigt. OpenShift versucht, diesen Anforderungen nachzukommen und das gewünschte persistente Volume bereitzustellen. OpenShift sucht nach einem PV, der die im PVC  definierten Kriterien erfüllt. Wenn es einen entsprechenden PV gibt, wird dieser Claim dem PV zugeordnet.

Durch die Unterteilung in die drei Objekte wird eine größere Portabilität geschaffen. Denn der Pod und der PVC beinhalten nur allgemeine Informationen und keine spezifischen, wie Ort des Speichers und dessen IP-Adresse. So können Pods und PVCs ohne zusätzliche Spezifikation auf unterschiedlichen Clustern ausgerollt werden.

Wie verknüpfe ich einen Container mit einem Volume?

Da nun das Prinzip der Speicherung innerhalb von OpenShift aufgezeigt wurde, geht es nun darum, wie dies in der Praxis angewendet wird. Zuerst wird ein PersistentVolume bereitgestellt, welches auf einen beliebigen physischen Speicher verweist. Als Speicher kann ein lokales Verzeichnis auf dem Server, oder wie hier im Beispiel, ein NFS im eigenen Netzwerk oder sogar eine Verknüpfung zu einer Cloud verwendet werden. Nachdem das Zusammenspiel der einzelnen OpenShift-Objekte erläutert wurde, wird dies im Folgenden an einem Beispiel verdeutlicht. Hierfür nutzen wir nicht das OpenShift-Frontend, sondern das OpenShift-CLI. Der Einstieg hierzu findet sich im Beitrag „Getting started mit OpenShift“. 

Dazu legen wir ein YAML-File für das persistente Volume an und nennen es persistence_volume.yaml.

apiVersion: v1
kind: PersistentVolume
metadata:
     name: mysql-pv-volume
     labels:
         type: local
spec:
     storageClassName: manual
     capacity:
         storage: 20Gi
     accessModes:
         - ReadWriteOnce
     nfs:
         server: 192.168.192.1
         path: "/mnt/data"

Um aus dem YAML-File ein PV zu erstellen:

oc create -f persistence_volume.yaml

Als nächstes wird die Anforderung nach Speicherplatz durch einen PVC erstellt.

Erstellung des PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
      name: mysql-pv-claim
spec:
     storageClassName: manual
     accessModes:
        - ReadWriteOnce
     resources:
        requests:
           storage: 20Gi

Um aus dem YAML-File einen PVC zu erstellen:

oc create -f Claim.yaml

Da nun die benötigen Ressourcen zu Verfügung stehen, kann der Pod erstellt werden. Ein Pod mit der Referenz zu dem erstellten PersistentVolumeClaim „mysql-pv-claim“ kann nun deployed werden.

Erstellung des Pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

Um den Pod auszuführen:

oc create -f mysql_deployment.yaml

Damit ist nun ein Deployment mit dem Zugriff auf ein persistentes Volume entstanden.

Neben PVC bietet OpenShift die Möglichkeit, Templates – sogenannte „StorageClasses“ – zu verwenden. Durch StorageClasses können PVs mit verschiedenen Eigenschaften wie Leistung, Größe oder Zugriffsparameter definiert und dem Benutzer persistenter Speicher zur Verfügung gestellt werden. Der Nutzer gibt nur an, welche Art von Speicher er benötigt, zum Beispiel durch Definitionen wie „fast“.
Dies stellt sicher, dass sich die Benutzer nicht mit der Komplexität und den Feinheiten der Speicherbereitstellung befassen müssen, aber dennoch die Möglichkeit haben, aus mehreren Speicheroptionen zu wählen.

Das Nutzen von StorageClasses ermöglicht darüber hinaus eine dynamische Bereitstellung von Volumes. Damit können Volumes nach Bedarf erstellt werden, ohne dass Administratoren manuelle Aufrufe bei ihrem Cloud- oder Speicheranbieter tätigen müssen.

Mit der dynamischen Provisioning-Funktion müssen Cluster-Administratoren keinen Speicher mehr im Voraus zur Verfügung stellen. Stattdessen wird der Speicher automatisch bereitgestellt, wenn er von den Benutzern angefordert wird. OpenShift verfügt über zahlreiche Plugins, mit denen verschiedene Arten von Speicherressourcen dem OpenShift-Cluster zur Verfügung gestellt werden.

Weitere Informationen dazu: https://docs.openshift.com/container-platform/4.6/storage/dynamic-provisioning.html

In diesem Beitrag haben wir aufgezeigt, wieso eine Datenerhaltung wichtig ist. Wir haben dargelegt, wie dies in Docker mittels Bind-Mount und Volumes und in OpenShift mittels PVCs und PV umgesetzt wird. Im nächsten Beitrag werden wir uns dem Thema Netzwerke in Docker und Openshift widmen.

Autor:in

Profilbild Dominik Kowatsch
Im Application Lifecycle fühlt sich Dominik zu Hause. Ob die Automatisierung von Tests oder anspruchsvolles Quality Engineering, mit seinem Know-how schlägt er die Brücke vom Entwickler zum Tester bis hin zum Administrator und verbindet diese in CI-/CD-Pipelines.

Diesen Beitrag teilen