Rook oder nicht Rook — das ist hier die Frage

Rook oder nicht Rook — das ist hier die Frage

Anfang dieses Monats, am 3. Mai, wurde die große Veröffentlichung des "Management-Systems für verteilte Datenlager in Kubernetes" angekündigt — Rook 1.0.0. Vor über einem Jahr haben wir bereits eine allgemeine Übersicht über Rook veröffentlicht. Damals wurden wir gebeten, über die Erfahrungen mit dessen praktischer Anwendung zu berichten — und nun, rechtzeitig zu diesem bedeutenden Meilenstein in der Geschichte des Projekts, freuen wir uns, die gesammelten Eindrücke zu teilen.

Kurz gesagt, Rook ist eine Sammlung von Operatoren für Kubernetes, die die Bereitstellung, Verwaltung und automatische Wiederherstellung von Speicherlösungen wie Ceph, EdgeFS, Minio, Cassandra und CockroachDB vollständig übernehmen.

Derzeit ist die am weitesten entwickelte (und einzige in stabile in der) rook-ceph-operator.

Hinweis: zu den bedeutenden Änderungen in der Veröffentlichung Rook 1.0.0, die Ceph betreffen, gehört die Unterstützung von Ceph Nautilus und die Möglichkeit, NFS für CephFS- oder RGW-Buckets zu verwenden. Unter anderen fällt die "Reifung" der Unterstützung von EdgeFS auf Beta-Level auf.

In diesem Artikel werden wir:

  • die Frage beantworten, welche Vorteile wir in der Nutzung von Rook für die Bereitstellung von Ceph in einem Kubernetes-Cluster sehen;
  • unsere Erfahrungen und Eindrücke von der Nutzung von Rook in der Produktion teilen;
  • erklären, warum wir Rook "Ja!" sagen und welche Pläne wir damit haben.

Beginnen wir mit den allgemeinen Konzepten und der Theorie.

„Ich habe einen Vorteil von einem Turm!“ (unbekannter Schachspieler)

Rook oder nicht Rook — das ist hier die Frage

Eines der größten Vorteile von Rook ist, dass die Interaktion mit Datenspeichern über die Mechanismen von Kubernetes erfolgt. Das bedeutet, dass man keine Befehle mehr von einem Blatt Papier in die Konsole kopieren muss, um Ceph einzurichten.

— Möchtest du CephFS in einem Cluster bereitstellen? Schreib einfach eine YAML-Datei!
­— Was? Möchtest du auch einen Object Store mit S3 API bereitstellen? Schreib einfach eine zweite YAML-Datei!

Rook wurde nach allen Regeln eines typischen Operators erstellt. Die Interaktion mit ihm erfolgt über CRD (Custom Resource Definitions), in denen wir die erforderlichen Merkmale von Ceph-Einheiten beschreiben (da dies die einzige stabile Implementierung ist, wird im Artikel standardmäßig über Ceph gesprochen, sofern nicht ausdrücklich anders angegeben). Je nach den festgelegten Parametern wird der Operator die notwendigen Befehle zur Einrichtung automatisch ausführen.

Konkret wollen wir das am Beispiel der Erstellung eines Object Stores betrachten, genauer gesagt — CephObjectStoreUser.

apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: {{ .Values.s3.crdName }}
  namespace: kube-rook
spec:
  metadataPool:
    failureDomain: host
    replicated:
      size: 3
  dataPool:
    failureDomain: host
    erasureCoded:
      dataChunks: 2
      codingChunks: 1
  gateway:
    type: s3
    sslCertificateRef:
    port: 80
    securePort:
    instances: 1
    allNodes: false
---
apiVersion: ceph.rook.io/v1
kind: CephObjectStoreUser
metadata:
  name: {{ .Values.s3.crdName }}
  namespace: kube-rook
spec:
  store: {{ .Values.s3.crdName }}
  displayName: {{ .Values.s3.username }}

Die im Listing angegebenen Parameter sind ziemlich Standard und bedürfen kaum eines Kommentars, jedoch sollte besonderes Augenmerk auf jene gelegt werden, die in Vorlagendateien hervorgehoben sind.

Das allgemeine Funktionsschema besteht darin, dass wir über eine YAML-Datei Ressourcen "bestellen", wofür der Operator die notwendigen Befehle ausführt und uns ein "nicht ganz echtes" Geheimnis zurückliefert, mit dem wir weiterarbeiten können. (siehe unten). Aus den oben angegebenen Variablen wird ein Befehl und der Name des Geheimnisses erstellt.

Was ist das für ein Befehl? Bei der Erstellung eines Benutzers für den Rook-Objektspeicher führt der Operator innerhalb des Pods Folgendes aus:

radosgw-admin user create --uid="rook-user" --display-name="{{ .Values.s3.username }}"

Das Ergebnis der Ausführung dieses Befehls wird eine JSON-Struktur sein:

{
    "user_id": "rook-user",
    "display_name": "{{ .Values.s3.username }}",
    "keys": [
        {
           "user": "rook-user",
           "access_key": "NRWGT19TWMYOB1YDBV1Y",
           "secret_key": "gr1VEGIV7rxcP3xvXDFCo4UDwwl2YoNrmtRlIAty"
        }
    ],
    ...
}

Schlüssel — das, was in Zukunft von Anwendungen benötigt wird, um über die S3 API auf den Objektspeicher zuzugreifen. Der Rook-Operator wählt diese aus und speichert sie in seinem Namensraum als Geheimnis mit dem Namen rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}.

Um die Daten aus diesem Geheimnis zu verwenden, reicht es aus, sie als Umgebungsvariablen in den Container hinzuzufügen. Als Beispiel gebe ich eine Vorlage für einen Job an, in dem wir automatisch Buckets für jede Benutzerumgebung erstellen:

{{- range $bucket := $.Values.s3.bucketNames }}
apiVersion: batch/v1
kind: Job
metadata:
  name: create-{{ $bucket }}-bucket-job
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "2"
spec:
  template:
    metadata:
      name: create-{{ $bucket }}-bucket-job
    spec:
      restartPolicy: Never
      initContainers:
      - name: waitdns
        image: alpine:3.6
        command: ["/bin/sh", "-c", "while ! getent ahostsv4 rook-ceph-rgw-{{ $.Values.s3.crdName }}; do sleep 1; done" ]
      - name: config
        image: rook/ceph:v1.0.0
        command: ["/bin/sh", "-c"]
        args: ["s3cmd --configure --access_key=$(ACCESS-KEY) --secret_key=$(SECRET-KEY) -s --no-ssl --dump-config | tee /config/.s3cfg"]
        volumeMounts:
        - name: config
          mountPath: /config
        env:
        - name: ACCESS-KEY
          valueFrom:
            secretKeyRef:
              name: rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}
              key: AccessKey
        - name: SECRET-KEY
          valueFrom:
            secretKeyRef:
              name: rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}
              key: SecretKey
      containers:
      - name: create-bucket
        image: rook/ceph:v1.0.0
        command: 
        - "s3cmd"
        - "mb"
        - "--host=rook-ceph-rgw-{{ $.Values.s3.crdName }}"
        - "--host-bucket= "
        - "s3://{{ $bucket }}"
        ports:
        - name: s3-no-sll
          containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /root
      volumes:
      - name: config
        emptyDir: {}
---
{{- end }}

Alle in diesem Job aufgeführten Aktionen wurden innerhalb Kubernetes durchgeführt. Die in den YAML-Dateien beschriebenen Strukturen sind in einem Git-Repository gespeichert und werden mehrfach wiederverwendet. Dies stellt einen deutlichen Vorteil für DevOps-Ingenieure und den CI/CD-Prozess insgesamt dar.

Mit Rook und Rados macht es Freude.

Die Verwendung von Ceph + RBD bringt bestimmte Einschränkungen bei der Anbindung von Volumes an Pods mit sich.

Insbesondere muss im Namespace ein Geheimnis für den Zugriff auf Ceph vorhanden sein, damit zustandsbehaftete Anwendungen funktionieren können. Es ist in Ordnung, wenn Sie 2-3 Umgebungen in Ihren Namensräumen haben: Dann kann man das Geheimnis manuell kopieren. Aber was, wenn für jedes Feature der Entwickler eine eigene Umgebung mit einem eigenen Namespace erstellt wird?

Wir haben dieses Problem bei uns gelöst durch Shell-Operator, der die Geheimnisse automatisch in neue Namespaces kopierte (ein Beispiel für einen solchen Hook wird in diesem Artikel).

#! /bin/bash

if [[ $1 == “--config” ]]; then
   cat <<EOF
{"onKubernetesEvent":[
 {"name": "OnNewNamespace",
  "kind": "namespace",
  "event": ["add"]
  }
]}
EOF
else
    NAMESPACE=$(kubectl get namespace -o json | jq '.items | max_by( .metadata.creationTimestamp ) | .metadata.name')
    kubectl -n ${CEPH_SECRET_NAMESPACE} get secret ${CEPH_SECRET_NAME} -o json | jq ".metadata.namespace="${NAMESPACE}"" | kubectl apply -f -
fi

Bei der Verwendung von Rook existiert dieses Problem jedoch einfach nicht. Der Vorgang der Anbindung erfolgt über eigene Treiber basierend auf Flexvolume oder CSI (derzeit im Beta-Stadium) und erfordert daher keine Geheimnisse.

Rook löst automatisch viele Probleme, was uns dazu bewegt, es in neuen Projekten zu verwenden.

Die Belagerung von Rook

Wir werden den praktischen Teil mit der Bereitstellung von Rook und Ceph beenden, um eigene Experimente durchführen zu können. Um diesen unzugänglichen Turm leichter erobern zu können, haben die Entwickler ein Helm-Paket vorbereitet. Lassen Sie uns dieses herunterladen:

$ helm fetch rook-master/rook-ceph --untar --version 1.0.0

In der Datei rook-ceph/values.yaml es gibt viele verschiedene Einstellungen zu finden. Das Wichtigste ist, Toleranzen für Agenten und Suchvorgänge anzugeben. Wie der Mechanismus von Taints/Tolerations genutzt werden kann, haben wir ausführlich in der diesem Artikel.

Kurz gesagt, wir möchten nicht, dass Pods mit der Client-Anwendung auf denselben Knoten platziert werden, auf denen sich die Datenspeicher befinden. Der Grund ist einfach: So beeinträchtigt die Arbeit der Rook-Agenten nicht die Anwendung selbst.

Also, öffnen wir die Datei rook-ceph/values.yaml mit unserem Lieblingseditor und fügen am Ende den folgenden Block hinzu:

discover:
  toleration: NoExecute
  tolerationKey: node-role/storage
agent:
  toleration: NoExecute
  tolerationKey: node-role/storage
  mountSecurityMode: Any

Wir fügen jedem Knoten, der für die Datenspeicherung reserviert ist, den entsprechenden Taint hinzu:

$ kubectl taint node ${NODE_NAME} node-role/storage="":NoExecute

Danach installieren wir das Helm-Diagramm mit diesem Befehl:

$ helm install --namespace ${ROOK_NAMESPACE} ./rook-ceph

Jetzt müssen wir einen Cluster erstellen und den Standort angeben OSD:

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  clusterName: "ceph"
  finalizers:
  - cephcluster.ceph.rook.io
  generation: 1
  name: rook-ceph
spec:
  cephVersion:
    image: ceph/ceph:v13
  dashboard:
    enabled: true
  dataDirHostPath: /var/lib/rook/osd
  mon:
    allowMultiplePerNode: false
    count: 3
  network:
    hostNetwork: true
  rbdMirroring:
    workers: 1
  placement:
    all:
      tolerations:
      - key: node-role/storage
        operator: Exists
  storage:
    useAllNodes: false
    useAllDevices: false
    config:
      osdsPerDevice: "1"
      storeType: filestore
    resources:
      limits:
        memory: "1024Mi"
      requests:
        memory: "1024Mi"
    nodes:
    - name: host-1
      directories:
      - path: "/mnt/osd"
    - name: host-2
      directories:
      - path: "/mnt/osd"
    - name: host-3
      directories:
      - path: "/mnt/osd"

Wir überprüfen den Status von Ceph - wir erwarten, " HEALTH_OK:

$ kubectl -n ${ROOK_NAMESPACE} exec $(kubectl -n ${ROOK_NAMESPACE} get pod -l app=rook-ceph-operator -o name -o jsonpath='{.items[0].metadata.name}') -- ceph -s

Gleichzeitig überprüfen wir, dass Pods mit der Client-Anwendung nicht auf die für Ceph reservierten Knoten gelangen:

$ kubectl -n ${APPLICATION_NAMESPACE} get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName

Als nächstes können nach Wunsch zusätzliche Komponenten konfiguriert werden. Weitere Details dazu finden Sie in der Dokumentation. Für die Verwaltung empfehlen wir dringend, das Dashboard und das Toolbox zu installieren.

Rook-Fesseln: Reicht es für Rook?

Wie man sieht, ist die Entwicklung von Rook voll im Gange. Doch es gibt immer noch Probleme, die uns daran hindern, vollständig auf die manuelle Konfiguration von Ceph zu verzichten:

  • Kein Rook-Treiber kann Metriken zur Nutzung der gemounteten Blöcke exportieren, was uns der Überwachung beraubt.
  • Flexvolume und CSI können die Größe von Volumes nicht ändern (im Gegensatz zu RBD), daher fehlt Rook ein nützliches (und manchmal kritisches!) Werkzeug.
  • Rook ist immer noch nicht so flexibel wie das herkömmliche Ceph. Wenn wir die Konfiguration vornehmen möchten, dass der Pool für CephFS-Metadaten auf SSD und die Daten selbst auf HDD gespeichert wird, müssen wir manuell separate Gerätegruppen in den CRUSH-Maps angeben.
  • Obwohl der rook-ceph-operator als stabil gilt, gibt es derzeit bestimmte Probleme beim Upgrade von Ceph von Version 13 auf 14.

Das DBMS Tarantool ist ein attraktives, zukunftsträchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.

„Momentan ist die Rook vor der Außenwelt durch Bauern geschützt, aber wir glauben, dass sie eines Tages eine entscheidende Rolle im Spiel spielen wird!“ (Zitat speziell für diesen Artikel erstellt)

Das Projekt Rook hat zweifellos unsere Herzen erobert – wir sind der Meinung, dass [mit all seinen Vor- und Nachteilen] es definitiv Ihre Aufmerksamkeit verdient.

Unsere zukünftigen Pläne bestehen darin, Rook-Ceph zu einem Modul für Addon-Operator, was seine Nutzung in unseren zahlreichen Kubernetes-Clustern noch einfacher und bequemer machen wird.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

60GB SSD 8Gb DDR4