Kubernetes-Storage mit Rook

Eckstein, Eckstein...

Typischerweise verwenden Kubernetes-Admins den von der darunterliegenden Cloud bereitgestellten Storage. Mit Rook lässt sich auf Kubernetes ein eigener Ceph-Cluster betreiben, der allen Containeranwendungen zur Verfügung steht.
Immer größere Datenmengen bei gleichzeitig steigenden Anforderungen an die Sicherheit sowie Zugriffsmöglichkeiten stellen Administratoren vor neue ... (mehr)

Storage war bei Kubernetes schon immer ein schwieriges Thema. Prinzipiell wäre es ja allen am liebsten, wenn auf einem Kubernetes-Cluster nur zustandslose Container liefen, aber ohne State lässt sich eben wenig Sinnvolles anfangen. Dementsprechend wurde die federführend von Google entwickelte Orchestrierungssoftware mit Version 1.3 auch zum Betrieb von "Pets" gerüstet, also Instanzen, die anders als Wegwerfcontainer wie ein Haustier gehätschelt und gepflegt werden. Damit das klappt, braucht das Haustier auch einen kuscheligen Ort, an dem es sich wohlfühlt, und der Server benötigt permanenten Storage, der auch Reboots überlebt. Dies ermöglichte Kubernetes 1.3 mit dem automatischen Provisioning von Storage für Container, das seither immer weiter verbessert wurde.

Storage stellt Kubernetes als sogenannte Persistent Volumes, die es in verschiedenen Ausprägungen, je nach der zugrunde liegenden Plattform, gibt. Läuft Kubernetes in der Google Cloud, bietet es die GCEPersistentDisk, bei Amazon den ElasticBlockStore und auf Azure das AzureFile oder die AzureDisk. Diese haben den Nachteil, dass sie die Abstraktion durch Kubernetes zunichtemachen und an den jeweiligen Cloudprovider binden. Allgemeine Interfaces existieren etwa für NFS, iSCSI, Fibre Channel sowie die verteilten Dateisysteme Ceph (RBD, Ceph­FS), GlusterFS und Quobyte, die alle erfordern, die entsprechende Storage-Infrastruktur separat zu betreiben.

Den goldenen Mittelweg schlägt ein Projekt namens Rook [1] (englisch "Schachturm") ein, das ebenfalls ein verteiltes Dateisystem betreibt, dies aber direkt auf dem Kubernetes-Cluster tut und somit weiteres Management überflüssig macht. Rook setzt ebenfalls auf Ceph (derzeit das Luminous-Release), erfordert aber, zumindest in der Theorie, keine speziellen Ceph-Kenntnisse zur Installation und zum Betrieb. Im Folgenden beschreiben wir das System näher und führen vor, wie Sie es in Kubernetes aufsetzen. Nach eigenen Angaben befindet sich Rook im Alpha-Stadium und soll künftig auch andere Storage-Backends als Ceph unterstützen. Es wird sich zeigen, ob es jemals so weit kommen wird. Außerdem ist Rook ein offiziell von der Cloud Native Computing Foundation (CNCF) unterstütztes Projekt.

Ein verteiltes Dateisystem wie Ceph besteht aus mehreren Knoten, die zusammen einen Storage-Pool bereitstellen. Um die Kapazität zu erhöhen, werden einfach weitere Knoten zu dem Pool hinzugefügt. Jeder Knoten steuert dazu seinen lokalen Speicher bei und betreibt darüber hinaus eine bis mehrere Softwarekomponenten, die zur Koordination des Pools nötig sind. Bei Kubernetes übernimmt jeweils ein sogenannter Pod, was in etwa einem Container entspricht, eine solche Aufgabe. Darüber hinaus besteht Rook noch aus weiteren Komponenten, die über die Installation und den Betrieb der Ceph-Pods wachen. So läuft auf jedem Node ein Rook-Agent, der einen Teil des Storage-Treibers für Kubernetes bereitstellt.

Der sogenannte Rook-Operator kümmert sich um die einzelnen Agents und die Komponenten des Ceph-Clusters. Operatoren wurden von der Firma CoreOS für Kubernetes eingeführt und haben nichts mit logischen Operatoren zu tun, sondern sollen gewissermaßen das Wissen und die Tätigkeit eines menschlichen "Operators", also Administrators, verkörpern. Der Rook-Operator ist also praktisch der Admin eines Ceph-Clusters in einem Kubernetes-Cluster. Wenn beispielsweise ein Storage-Knoten ausfällt, kümmert sich der Operator darum, einen neuen bereitzustellen. Bild 1 zeigt noch einmal die Architektur im Überblick.

Bild 1: Rook besteht aus mehreren Komponenten, die gemeinsam einen Ceph-Cluster auf Kubernetes installieren und überwachen.

Installation

Rook setzt mindestens Kubernetes 1.6 voraus, wir verwenden aber die Version 1.8, in der die rollenbasierte Zugriffskontrolle (RBAC), die Rook benötigt, dem Betastadium entstiegen ist. Wer Kubernetes mit Kops installiert, muss den Cluster mit "--authorization RBAC" erzeugen. Auch das dynamische Discovery sogenannter FlexVolumes, mit denen Rook den Speicher dem Cluster bereitstellt, funktioniert erst mit dieser Kubernetes-Version.

Je nach Anforderung müssen Sie die passende Infrastruktur für den Rook/Ceph-Cluster verwenden. Läuft Kubernetes etwa auf AWS, stehen eine Vielzahl von Instanztypen zur Verfügung, die auf unterschiedliche Einsatzzwecke optimiert sind. Sinnvoll ist es vermutlich, für die Rook-Nodes AWS-Instanzen einzusetzen, die für Storage gedacht sind (Bild 2). Allerdings ist es nicht unbedingt sinnvoll (oder zumindest sehr teuer), für den eigentlichen Storage SSDs zu verwenden, für die häufig gelesenen OSD-Metadaten aber schon. Außerdem empfiehlt es sich, Instanzen mit einer schnellen Netzwerkverbindung zu verwenden, optimalerweise 10 GBit/s. Die Installation besteht aus zwei Schritten: dem Deployment des Rook-Operators und dem des Ceph-Clusters. Für beides steht ein Deployment-File im Yaml-Format zur Verfügung. Den Operator können Sie wahlweise auch mit dem Kubernetes-Paketmanager Helm installieren (Bild 3). Für den Cluster gibt es aber bisher kein Helm-Chart, hier rufen die Entwickler zur Mitarbeit auf.

Bild 2: Die zu den AWS-Storage-Instanzen (i3) gehörigen EBS-Volumes, die den Speicher für Ceph bereitstellen.

Haben Sie den Quellcode von Rook von Github ausgecheckt, wechseln Sie ins Verzeichnis "cluster/examples/kubernetes", wo Sie alle nötigen Yaml-Files finden. Zum Deployment des Operators rufen Sie folgenden Befehl auf:

$ kubectl create -f rook-operator.yaml

Geben Sie nun »kubectl get pods -n rook-system -w« ein, sehen Sie die Rook-Agenten und den Operator starten. Das sollte nach kurzer Zeit erledigt sein. Jetzt erzeugen Sie den Rook-Cluster mit:

$ kubectl create -f rook-cluster.yaml

Mit »kubectl get pods -n rook -w« können Sie nun die Rook-Pods beim Starten beobachten. Schlägt die Installation des Clusters fehl, etwa mit einer Fehlermeldung wie "error: unable to recognize "rook-cluster.yml": no matches for rook.io/", dann überprüfen Sie, ob der Kubernetes-Cluster auch wirklich RBAC unterstützt. Werfen Sie mit »kubectl get clusterroles« einen Blick auf die installierten Cluster-Rollen. In der Ausgabe muss beispielsweise "cluster-admin" zu finden sein.

Laufen alle Pods, sollte der von Ceph bereitgestellte Storage zur Verfügung stehen. Die Ausgabe des obigen Befehls auf einem Cluster mit fünf Nodes zeigt Listing 1.

Listing 1: Pods in einem Rook-Cluster



$ kubectl get pods -n rook
NAME                                                  READY      STATUS        RESTARTS      AGE
rook-ceph-mgr0-d4748b6d5-nmcrh    1/1             Running         0                      1m
rook-ceph-mon0-nrpl2                        1/1              Running        0                       1m
rook-ceph-mon1-vjmdz                       1/1              Running        0                      1m
rook-ceph-mon2-66dc4                       1/1              Running        0                      1m
rook-ceph-osd-29r2z                           1/1              Running        1                      1m
rook-ceph-osd-4882j                           1/1              Running        0                      1m
rook-ceph-osd-fqz4j                            1/1              Running        0                      1m
rook-ceph-osd-j2qd2                           1/1              Running        0                      1m
rook-ceph-osd-k8wtz                           1/1              Running        0                      1m

Hier ist zu sehen, dass auf jedem Knoten ein Pod mit einem Ceph-OSD (Object Storage Daemon) läuft, es aber nur drei Monitor-Instanzen (MON) und einen Manager-Daemon (MGR) gibt. Die Monitore stimmen sich untereinander ab und müssen in der Lage sein, eine Mehrheit (Quorum) zu bilden, auch wenn einzelne Monitore ausfallen. Ihre Anzahl bestimmen Sie über den Schlüssel "monCount" in der ConfigMap des Clusters, wie es das Beispiel in Listing 2 zeigt. Allerdings rät die Rook-Dokumentation davon ab, mehr als drei Mon-Instanzen zu starten, da der Operator ein paar Strategien anwendet, wenn ein Monitor ausfällt. Generell prüft er alle 20 Sekunden, ob jeder Monitor funktionstüchtig ist. Fällt ein Monitor länger als fünf Minuten aus, startet der Rook-Operator einen neuen. Die Einstellungen dafür sind in der Datei "rook-operator.yml" zu finden:

- name: ROOK_MON_HEALTHCHECK_INTERVAL value: "45s"
- name: ROOK_MON_OUT_TIMEOUT value: "300s"

Über die Custom Resource Definition (CRD) können Sie beim Anlegen des Clusters verschiedene Ceph-Parameter festlegen. Auf Rook-Ebene gibt es bei der aktuellen Version aber keine Möglichkeit, sie später zu verändern.

Listing 2: rook-cluster.yml



apiVersion: v1
kind: Namespace
metadata:
    name: rook
---
apiVersion: rook.io/v1alpha1
kind: Cluster
metadata:
    name: rook
    namespace: rook
spec:
    dataDirHostPath: /var/lib/rook
    monCount: 3
    storage:
       useAllNodes: true
       useAllDevices: false
       storeConfig:
          storeType: bluestore
          databaseSizeMB: 1024
          journalSizeMB: 1024
Bild 3: Für den Rook-Operator gibt es ein funktionierendes Helm-Chart, für den Cluster aber bisher nicht.

Storage verwenden

Ceph und damit Rook stellen drei Typen von Storage zur Verfügung: Block Storage, den ein Pod wie eine lokale Disk verwenden kann, Object Storage (über die Amazon-S3-API) oder ein Shared-Filesystem, das mehrere Pods gemeinsam nutzt.

Der klassische Anwendungsfall, um sogenannte Legacy-Anwendungen als Pets in Kubernetes zu betreiben, ist der Block-Storage. Ein Beispiel dafür ist eine Webanwendung, die einerseits Storage für das Speichern von HTML, Stylesheets, Java-script und Bildern benötigt und andererseits permanenten Speicher für den Betrieb einer Datenbank.

Als Erstes müssen Sie für den von Rook bereitgestellten Block Storage eine Storage-Klasse in Kubernetes anlegen. Das dafür erforderliche Yaml-File bringt Rook ebenfalls mit:

$ kubectl create -f rook-storageclass.yaml
pool "replicapool" created
storageclass "rook-block" created

Wenn Sie nun die verfügbaren Storage Classes anzeigen, sehen Sie neben den bereits vorhandenen AWS-nativen Klassen auch die neue von Rook – so wie in Listing 3 erkennbar.

Listing 3: Rook-Klassen anzeigen



$ kubectl get sc -a
NAME                PROVISIONER                 AGE
default                kubernetes.io/aws-ebs      47m
gp2 (default)      kubernetes.io/aws-ebs       47m
rook-block          rook.io/block                    1m

Um den Default-Storage-Pool zu ändern, gibt es kein einfaches atomares Kommando. Stattdessen müssen Sie dem neuen Default-Pool die entsprechene Annotation auf "true" setzen und bei dem alten Default-Pool auf "false".

$ kubectl patch storageclass gp2 -p '{"metadata": {"annotations": {"storageclass.kubernetes.io/is-default-class":"false"}}}'
$ kubectl patch storageclass rook-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Aus diesen Pools fordern Sie Storage mit einem Persistent Volume Claim an, den Kubernetes mit einem persistenten Volume bedient, sofern dies möglich ist. Um das manuell zu machen, legen Sie eine Yaml-Datei wie in Listing 4 an und rufen »kubectl create -f pvc.yml« auf. Anschließend sehen Sie mit »kubectl get pvc« den PVC und mit »kubectl get pv« das provisionierte Volume. Jetzt können Sie einen Pod starten, der das Volume als persistenten Storage verwendet, etwa mit dem Yaml-File in Listing 5, das einen Nginx-Webserver im Container startet und das Webroot-Directory auf das Volume legt. Spaßeshalber können Sie sich mit »kubectl« in dem Container einloggen und sich die Mounts anzeigen lassen:

$ kubectl exec -it task-pv-pod -- /bin/bash
root@my-pv-pod:/# mount | grep nginx/dev/rbd0 on /usr/share/nginx/html type ext4 (rw,relatime, stripe=1024,data=ordered)

Listing 4: pvc.yml



kind: PersistentVolumeClaim
apiVersion: v1
metadata:
    name: my-pv-claim
spec:
    storageClassName: rook-block
    accessModes:
       - ReadWriteOnce
    resources:
       requests:
          storage: 3Gi

Listing 5: pod-pvc.yml



kind: Pod
apiVersion: v1
metadata:
    name: my-pv-pod
spec:
    volumes:
       - name: my-pv-storage
         persistentVolumeClaim:
            claimName: my-pv-claim
    containers:
       - name: my-pv-container
         image: nginx
         ports:
           - containerPort: 80
             name: "http-server"
         volumeMounts:
           - mountPath: "/usr/share/nginx/html"
             name: my-pv-storage

Im Ernstfall würde man natürlich andere Strategien an den Tag legen, um Webapplikationen in einem Cluster zu deployen, und eigene, angepasste Container verwenden. Der Rook-Distribution liegen zwei Yaml-Dateien bei, die auf je einem provisionierten Volume einen Webserver mit Word­press und die dazu gehörige MySQL-Datenbank installieren.

comments powered by Disqus
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Konfigurationsmanagement

Ich konfiguriere meine Server

  • von Hand
  • mit eigenen Skripts
  • mit Puppet
  • mit Ansible
  • mit Saltstack
  • mit Chef
  • mit CFengine
  • mit dem Nix-System
  • mit Containern
  • mit anderer Konfigurationsmanagement-Software

Ausgabe /2023