Cloud-Native Storage mit rook.io für Kubernetes

Wer sich mit Kubernetes beschäftigt, wird sich irgendwann auch mit persistenten Storage auseinandersetzen. In diesem Blogpost zeigt euch Sebastian, wie mit Hilfe von rook.io Cloud-Native Storage auf einem Kubernetes Cluster betrieben wird.

Doch was ist eigentlich Rook? Rook wird auf der eigenen Website wie folgt beschrieben:

A Storage Orchestrator for Kubernetes

Rook turns distributed storage systems into self-managing, self-scaling, self-healing storage services. It automates the tasks of a storage administrator: deployment, bootstrapping, configuration, provisioning, scaling, upgrading, migration, disaster recovery, monitoring, and resource management.
Rook uses the power of the Kubernetes platform to deliver its services: cloud-native container management, scheduling, and orchestration.

https://rook.io/

Rook ermöglicht es, mehrere Storage Provider zu verwenden. Darunter befinden sich Ceph und EdgeFS, aber auch Datenbanken wie CockroachDB und Cassadra. In diesem Beitrag werde ich mit Rook einen Ceph Cluster bauen und dann sowohl Block- wie auch File-Storage davon beziehen.

Übrigens: Auch RedHat setzt mit RedHat OpenShift Container Storage auf Rook.

Kubernetes Cluster

Für dieses Setup habe ich zuerst einen neuen Kubernetes Cluster erstellt. Dafür verwende ich unsere Rancher Installation und den cloudscale.ch Rancher UI Driver, um automatisch bei unserem Cloud-Provider einen neuen Kubernetes Cluster zu provisionieren. Damit direkt im UI Driver zusätzlichen Storage an die VM’s angehängt werden kann, musste ich aber zuerst den UI Driver etwas weiterentwickeln. Rancher verwendet Docker Machine Driver, um VM’s für die Kubernetes Cluster zu erstellen. Cloudscale.ch hat ihren Docker Machine Driver vor kurzem aktualisiert und ermöglicht nun zusätzliche Volumes (SSD oder Bulk) direkt beim Erzeugen der VM’s anzuhängen. Somit konnte ich den UI Driver um diese Funktionalität ergänzen. Neu können direkt im Node-Template weitere Volumes der VM hinzugefügt werden:

Der Pull Request dafür ist bereits offen, das Feature sollte bald verfügbar sein.

Damit habe ich einen Kubernetes Cluster mit 3 Master Nodes, 3 Storage Nodes und 2 Worker Nodes erstellt. Die Storage Nodes wurden je mit einem zusätzlich 500 GB SSD Volume erstellt, die ich dann für Rook verwenden will. Die 3 Storage Nodes haben ausserdem ein Taint und ein Node Label gesetzt, damit diese später dediziert für Rook und den Ceph Cluster verwendet werden können. Der normale Container Workload wird somit nicht durch Storage Workload beinträchtigt (und umgekehrt).

$ kubectl get node   
NAME                STATUS   ROLES               AGE   VERSION
k8s-test-master1    Ready    controlplane,etcd   17h   v1.17.2
k8s-test-master2    Ready    controlplane,etcd   17h   v1.17.2
k8s-test-master3    Ready    controlplane,etcd   17h   v1.17.2
k8s-test-storage1   Ready    worker              17h   v1.17.2
k8s-test-storage2   Ready    worker              17h   v1.17.2
k8s-test-storage3   Ready    worker              17h   v1.17.2
k8s-test-worker1    Ready    worker              17h   v1.17.2
k8s-test-worker2    Ready    worker              15h   v1.17.2

Ceph Cluster mit dem rook-ceph Operator

Rook verwendet einen Operator um Ceph Cluster zu erstellen – den rook-ceph Operator. Für die Installation, verwende ich das Ceph Operator Helm Chart, das von Rook zur Verfügung gestellt wird. Das Chart habe ich  via Rancher App Catalog installiert und ich verwende folgende Values:

---
  agent: 
    flexVolumeDirPath: "/var/lib/kubelet/volumeplugins"
  nodeSelector:
    storage: "true"
  tolerations:
    - key: storage
      operator: Exists
  discover:
    tolerations:
      - key: storage
        operator: Exists
    nodeAffinity: storage=true

Der flexVolumeDirPath muss angepasst werden, da Rancher diesen nicht am Standardpfad hat. Allerdings verwenden wir später sowieso CSI und nicht flexvolumes. Weiter sind Node-Affinity/Node-Selector und Tolerations gesetzt, damit der Ceph Operator und auch die Disk Discovery Pods nur auf den zuvor erwähnten Storage Nodes erstellt werden. Sobald die App installiert ist, sehen wir drei Pods und diese wie gewünscht nur auf Storage Nodes:

kubectl get pod -o wide                
NAME                                   READY   STATUS      NODE             
rook-ceph-operator-6775969454-xgf44    1/1     Running     k8s-test-storage2
rook-discover-hjfbn                    1/1     Running     k8s-test-storage3
rook-discover-hrn2j                    1/1     Running     k8s-test-storage1
rook-discover-lg2tl                    1/1     Running     k8s-test-storage2

Der Ceph-Operator läuft nun und wartet darauf, dass wir einen Ceph Cluster erstellen. Rook hat dafür diverse CRD’s, unter anderem eines das CephCluster.ceph.rook.io/v1 heisst. Um den Ceph Cluster zu erstellen, müssen wir also eine Kubernetes Ressource dieses Typs erstellen:

---
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    image: ceph/ceph:v14.2.6
  dataDirHostPath: /var/lib/rook
  dashboard:
    enabled: true
  placement:
    all:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: storage
              operator: In
              values:
              - "true"
      tolerations:
      - key: storage
        operator: Exists
  mon:
    count: 3
    allowMultiplePerNode: true
  storage:
    useAllNodes: false
    useAllDevices: true
    nodes:
    - name: k8s-test-storage1
    - name: k8s-test-storage2
    - name: k8s-test-storage3

Darin wird definiert, wie unser Ceph Cluster auszusehen hat. Besonders interessant sind wieder die Node-Affinity und Tolerations. Damit wird gesteuert auf welchen Kubernetes Nodes die Komponenten des Ceph Cluster laufen sollen. Weitere definieren wir explizit, welche Nodes als Storage Backend verwendet werden sollen. Es wäre auch möglich, useAllNodes: true zu verwenden. Dank den Node-Affinity / Tolerations würden trotzdem nur die drei Storage Nodes verwendet werden. Damit könnte später aber durch Hinzufügen weiteree Storage Node, automatisch der Ceph Cluster erweitert werden (ohne Anpassen der CephCluster Ressource).

Der Ceph Operator deployet nun alle Komponenten, die für den Ceph Cluster benötigt werden. Wir sehen nun diverse neue Pods mit allen Ceph Komponenten (mgt, mon, osd’s).

Sobald alle ready sind, ist auch das Ceph Dashboard verfügbar. Um darauf zuzugreifen müssen wir den Port mit kubectl forwarden und das admin Passwort aus einem Secret auslesen:

# Admin Password des Dashboard
kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo
# Port-Forwarding für Dashboard
kubectl port-forward svc/rook-ceph-mgr-dashboard 7000:7000

Anstelle des Portforwardings könnten wir natürlich auch einen Ingress einrichten und darüber dann von überall zugreifen. Der Ceph Cluster ist nun ready und kann verwendet werden.

Block Storage

Wir wollen zuerst Block Storage für Kubernetes zur Verfügung stellen. Dazu benötigen wir einen neuen CephBlockPool. Dieser wird mit einer weiteren CRD definiert, aus dem der Ceph-Operator dann automatisch ein Storage Pool in Ceph erstellt:

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3

Um nun Block-Storage aus diesem Storage Pool in Kubernetes zu beziehen, erstellen wir eine Kubernetes StorageClass. Diese sagt Kubernetes, wie und wer aus PVCs dynamisch Storage provisioniert und dann die Informationen zum Beziehen dieses Storage in ein PV ablegt:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-block
parameters:
  clusterID: rook-ceph
  csi.storage.k8s.io/fstype: xfs
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  imageFeatures: layering
  imageFormat: "2"
  pool: replicapool
provisioner: rook-ceph.rbd.csi.ceph.com
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: false

Mit einem PVCs, welches diese StorageClass verwendet, erstellt dann der csi-rdbplugin-provisioner automatisch ein Block Image:

Jedes PV ist auch im Ceph Dashboard als Image Block sichtbar.

File Storage

Um FileStorage von Ceph zu beziehen, existiert die CephFilesystem CRD. Analog zu vorher bei Block-Storage können wir also ein Objekt diese CephFilesystem Typ erstellen:

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: myfs
  namespace: rook-ceph
spec:
  metadataPool:
    replicated:
      size: 3
  dataPools:
    - replicated:
        size: 3
  preservePoolsOnDelete: true
  metadataServer:
    activeCount: 1
    activeStandby: true

Der Ceph-Operator konfiguriert dann wiederum automatisch ein Ceph Filesystem und den dazugehörigen Ceph Block Pools. Um mit PVC’s dynamisch PV’s zu erstellen, wird wiederum eine StorageClass benötigt:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  # clusterID is the namespace where operator is deployed.
  clusterID: rook-ceph

  # CephFS filesystem name into which the volume shall be created
  fsName: myfs

  # Ceph pool into which the volume shall be created
  pool: myfs-data0

  # The secrets contain Ceph admin credentials. These are generated automatically by the operator
  # in the same namespace as the cluster.
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph

reclaimPolicy: Delete

Die PVs, die davon erstellt werden, unterstützen nun als accessMode ReadWriteMany und können daher von mehreren Pod’s gleichzeitig gemounted werden. Ceph stellt ein shared Filesystem (CephFS) zur Verfügung.

Object Storage

Mit dem Rook ceph-operator kann analog zu Block- und File-Storage auch Object-Storage bezogen werden. Das Vorgehen ist sehr ähnlich wie zuvor. Ich verzichte hier aber auf eine Anleitung. Mehr Infos dazu ist in der Rook Dokumentation zu finden.

Kommentare sind geschlossen.