Volumes-Plugins für Speicher in Kubernetes: von Flexvolume zu CSI

Volumes-Plugins für Speicher in Kubernetes: von Flexvolume zu CSI

In der Zeit, als Kubernetes noch v1.0.0 war, existierten Volume-Plugins. Diese waren notwendig, um Kubernetes mit Speichersystemen für die Persistierung von Daten in Containern zu verbinden. Ihre Anzahl war gering, und zu den ersten Anbietern gehörten GCE PD, Ceph, AWS EBS und andere.

Die Plugins wurden zusammen mit Kubernetes geliefert, weshalb sie den Namen in-tree erhielten. Doch vielen genügte der vorhandene Satz solcher Plugins nicht. Entwickler fügten einfache Plugins durch Patches in den Kubernetes-Kernel ein, erstellten ihre eigene Version von Kubernetes und installierten diese auf ihren Servern. Im Laufe der Zeit erkannten die Kubernetes-Entwickler jedoch, dass das Problem so nicht zu lösen ist. Die Menschen benötigen eine Angelschnur. Und mit der Veröffentlichung von Kubernetes v1.2.0 war sie da…

Flexvolume-Plugin: eine minimalistische Angelschnur

Die Entwickler von Kubernetes schufen das FlexVolume-Plugin, das eine logische Schnittstelle aus Variablen und Methoden zur Nutzung von Flexvolume-Treibern darstellt, die von Drittanbietern implementiert wurden.

Lassen Sie uns genauer betrachten, was ein FlexVolume-Treiber ist. Es handelt sich um eine Art ausführbare Datei (binäre Datei, Python-Skript, Bash-Skript usw.), das beim Ausführen Kommandozeilenargumente entgegennimmt und eine Nachricht mit vordefinierten Feldern im JSON-Format zurückgibt. Das erste Kommandozeilenargument ist nach Vereinbarung immer die Methode, die restlichen Argumente sind ihre Parameter.

Volumes-Plugins für Speicher in Kubernetes: von Flexvolume zu CSI
Schema zur Verbindung von CIFS Shares in OpenShift. Der Flexvolume-Treiber – direkt im Mittelpunkt.

Minimaler Methoden-Set sieht so aus:

flexvolume_driver mount # Verantwortlich für das Anhängen des Volumes an den Pod
# Format der zurückgegebenen Nachricht:
{
  "status": "Erfolg"/"Fehler"/"Nicht unterstützt",
  "message": "Warum dieser Status zurückgegeben wurde",
}

flexvolume_driver unmount # Verantwortlich für das Abtrennen des Volumes vom Pod
# Format der zurückgegebenen Nachricht:
{
  "status": "Erfolg"/"Fehler"/"Nicht unterstützt",
  "message": "Warum dieser Status zurückgegeben wurde",
}

flexvolume_driver init # Verantwortlich für die Initialisierung des Plugins
# Format der zurückgegebenen Nachricht:
{
  "status": "Erfolg"/"Fehler"/"Nicht unterstützt",
  "message": "Warum dieser Status zurückgegeben wurde",
  // Bestimmt, ob der Treiber die Methoden attach/detach verwendet
  "capabilities":{"attach": true/false}
}

Verwendung der Methoden attach und detach legt das Szenario fest, nach dem der kubelet in Zukunft beim Aufruf des Treibers handeln wird. Es gibt auch spezielle Methoden expandvolume und expandfs, die für die dynamische Größenänderung des Volumes zuständig sind.

Als Beispiel für die Änderungen, die die Methode hinzufügen kann expandvolume, sowie die Möglichkeit, Volumen in Echtzeit zu vergrößern, können Sie sich unseren Pull Request im Rook Ceph Operator ansehen.

Hier ist ein Beispiel für die Implementierung eines Flexvolume-Treibers zur Arbeit mit NFS:

usage() {
    err "Ungültige Verwendung. Verwendung: "
    err "t$0 init"
    err "t$0 mount  "
    err "t$0 unmount "
    exit 1
}

err() {
    echo -ne $* 1>&2
}

log() {
    echo -ne $* >&1
}

ismounted() {
    MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
    if [ "${MOUNT}" == "${MNTPATH}" ]; then
        echo "1"
    else
        echo "0"
    fi
}

domount() {
    MNTPATH=$1

    NFS_SERVER=$(echo $2 | jq -r '.server')
    SHARE=$(echo $2 | jq -r '.share')

    if [ $(ismounted) -eq 1 ]; then
        log '{"status": "Erfolg"}'
        exit 0
    fi

    mkdir -p ${MNTPATH} &> /dev/null

    mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Fehler", "message": "Fehler beim Einhängen von ${NFS_SERVER}:${SHARE} an ${MNTPATH}"}"
        exit 1
    fi
    log '{"status": "Erfolg"}'
    exit 0
}

unmount() {
    MNTPATH=$1
    if [ $(ismounted) -eq 0 ]; then
        log '{"status": "Erfolg"}'
        exit 0
    fi

    umount ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Fehlgeschlagen", "message": "Fehler beim Aushängen des Volumes an ${MNTPATH}"}"
        exit 1
    fi

    log '{"status": "Erfolg"}'
    exit 0
}

op=$1

if [ "$op" = "init" ]; then
    log '{"status": "Erfolg", "capabilities": {"attach": false}}'
    exit 0
fi

if [ $# -lt 2 ]; then
    usage
fi

shift

case "$op" in
    mount)
        domount $*
        ;;
    unmount)
        unmount $*
        ;;
    *)
        log '{"status": "Nicht unterstützt"}'
        exit 0
esac

exit 1

Nach der Vorbereitung der ausführbaren Datei ist es notwendig den Treiber im Kubernetes-Cluster bereitzustellen. Der Treiber muss an jedem Knoten des Clusters entsprechend dem zuvor vereinbarten Pfad vorhanden sein. Standardmäßig wurde folgender gewählt:

/usr/libexec/kubernetes/kubelet-plugins/volume/exec/имя_поставщика_хранилища~имя_драйвера/

… aber bei Verwendung verschiedener Kubernetes-Distributionen (OpenShift, Rancher…) kann der Pfad anders sein.

Flexvolume-Probleme: Wie setzt man den richtigen Anker?

Das Bereitstellen des Flexvolume-Treibers auf den Knoten des Clusters stellte sich als nicht triviale Aufgabe heraus. Hat man den Vorgang einmal manuell durchgeführt, kann man leicht in die Situation kommen, dass im Cluster neue Knoten erscheinen: durch das Hinzufügen eines neuen Knotens, automatisches horizontales Skalieren oder — was schlimmer ist — den Austausch eines Knotens aufgrund einer Störung. In diesem Fall kann die Arbeit mit dem Speicher auf diesen Knoten nicht erstellt werden kann, solange Sie nicht weiterhin manuell den Flexvolume-Treiber hinzufügen.

Die Lösung dieses Problems war eines der Kubernetes-Primitive — DaemonSet. Wenn ein neuer Knoten im Cluster erscheint, wird automatisch ein Pod aus unserem DaemonSet auf diesem erstellt, der ein lokales Volume entsprechend dem Pfad für die Flexvolume-Treiber verbindet. Bei erfolgreicher Erstellung kopiert der Pod die notwendigen Dateien zur Verwendung des Treibers auf die Festplatte.

Hier ist ein Beispiel für ein solches DaemonSet zur Bereitstellung des Flexvolume-Plugins:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: flex-set
spec:
  template:
    metadata:
      name: flex-deploy
      labels:
        app: flex-deploy
    spec:
      containers:
        - image: 
          name: flex-deploy
          securityContext:
              privileged: true
          volumeMounts:
            - mountPath: /flexmnt
              name: flexvolume-mount
      volumes:
        - name: flexvolume-mount
          hostPath:
            path:

… und ein Beispiel für ein Bash-Skript zur Bereitstellung des Flexvolume-Treibers:

#!/bin/sh

set -o errexit
set -o pipefail

VENDOR=k8s.io
DRIVER=nfs

driver_dir=$VENDOR${VENDOR:+"~"}${DRIVER}
if [ ! -d "/flexmnt/$driver_dir" ]; then
  mkdir "/flexmnt/$driver_dir"
fi

cp "/$DRIVER" "/flexmnt/$driver_dir/.$DRIVER"
mv -f "/flexmnt/$driver_dir/.$DRIVER" "/flexmnt/$driver_dir/$DRIVER"

while : ; do
  sleep 3600
done

Es ist wichtig zu beachten, dass der Kopiervorgang nicht atomar ist. Es besteht eine hohe Wahrscheinlichkeit, dass kubelet den Treiber verwendet, bevor der Vorbereitungsprozess abgeschlossen ist, was zu einem Fehler im System führen kann. Der richtige Ansatz besteht darin, die Treiberdateien zuerst unter einem anderen Namen zu kopieren und dann eine atomare Umbenennung durchzuführen.

Volumes-Plugins für Speicher in Kubernetes: von Flexvolume zu CSI
Das Schema der Arbeit mit Ceph im Rook-Operator: der Flexvolume-Treiber befindet sich im Schema innerhalb des Rook-Agenten

Ein weiteres Problem bei der Verwendung von Flexvolume-Treibern besteht darin, dass für die meisten Speicheranbieter auf dem Cluster-Knoten die erforderliche Software installiert sein muss (zum Beispiel das Paket ceph-common für Ceph). Ursprünglich war das Flexvolume-Plugin nicht für solch komplexe Systeme konzipiert.

Eine originelle Lösung für dieses Problem findet sich in der Implementierung des Flexvolume-Treibers des Providers Rook:

Der Treiber selbst agiert als RPC-Client. Der IPC-Socket für die Kommunikation liegt im selben Verzeichnis wie der Treiber. Wir erinnern uns, dass es sinnvoll wäre, zum Kopieren der Treiberdateien ein DaemonSet zu verwenden, das das Verzeichnis mit dem Treiber als Volume einbindet. Nachdem die benötigten Treiberdateien von Rook kopiert wurden, stirbt dieser Pod nicht, sondern verbindet sich über das angehängte Volume mit dem IPC-Socket und fungiert als vollwertiger RPC-Server. Das Paket ceph-common ist bereits innerhalb des Pod-Containers installiert. Der IPC-Socket gewährleistet, dass der Kubelet tatsächlich mit dem Pod kommuniziert, der sich auf demselben Knoten befindet. Alles Geniale ist einfach! …

Leb wohl, unsere geliebten… In-Tree-Plugins!

Die Kubernetes-Entwickler haben festgestellt, dass die Anzahl der im Kernel integrierten Speicher-Plugins zwanzig beträgt. Und jede Änderung in ihnen durchläuft auf die eine oder andere Weise den vollständigen Release-Zyklus von Kubernetes.

Es stellt sich heraus, dass für die Verwendung einer neuen Version des Speicher-Plugins der gesamte Cluster aktualisiert werden muss.. Zusätzlich können Sie überrascht sein, dass die neue Version von Kubernetes plötzlich nicht mehr mit dem verwendeten Linux-Kernel kompatibel ist… Daher trocknen Sie Ihre Tränen und stimmen zähneknirschend mit Ihrem Management und den Nutzern einen Zeitpunkt für das Update des Linux-Kernels und des Kubernetes-Clusters ab. Möglicherweise mit Ausfallzeiten bei der Bereitstellung von Dienstleistungen.

Die Situation ist mehr als komisch, oder? Der gesamten Community ist klar geworden, dass dieser Ansatz nicht funktioniert. Mit einer willentlichen Entscheidung erklären die Entwickler von Kubernetes, dass neue Plugins zur Arbeit mit Speichersystemen nicht mehr in den Kernel aufgenommen werden. Darüber hinaus haben wir bereits festgestellt, dass im Flexvolume-Plugin eine Reihe von Mängeln aufgetreten sind…

Um die Frage nach persistierenden Datenspeichern einmal für alle Mal zu klären, wurde das zuletzt hinzugefügte Plugin für Volumes in Kubernetes – CSI – geschaffen. Seine Alpha-Version, die vollständiger als Out-of-Tree CSI Volume Plugins bezeichnet wird, wurde in der Veröffentlichung angekündigt. Kubernetes 1.9.

Container Storage Interface, oder spinnt CSI 3000!

Als Erstes möchte ich betonen, dass CSI nicht einfach ein Volume-Plugin ist, sondern ein echtes Standard. zur Erstellung benutzerdefinierter Komponenten für die Arbeit mit Datenspeichern.. Man nahm an, dass Container-Orchestrierungssysteme wie Kubernetes und Mesos „lernen“ sollten, mit Komponenten zu arbeiten, die nach diesem Standard implementiert sind. Und Kubernetes hat bereits gelernt.

Wie ist der CSI-Plugin in Kubernetes aufgebaut? Der CSI-Plugin arbeitet mit speziellen Treibern (CSI-Treibern), die von Drittanbietern entwickelt wurden. Ein CSI-Treiber in Kubernetes muss mindestens aus zwei Komponenten (Pods) bestehen:

  • Controller — verwaltet externe persistente Speicher. Wird als gRPC-Server bereitgestellt, der das Primitive StatefulSet.
  • Knoten — kümmert sich um das Mounten von persistenten Speichern an die Cluster-Knoten. Auch dies wird als gRPC-Server implementiert, verwendet jedoch das Primitive DaemonSet.

Volumes-Plugins für Speicher in Kubernetes: von Flexvolume zu CSI
Funktionsweise des CSI-Plugins in Kubernetes

Über einige andere Details des CSI-Betriebs können Sie beispielsweise aus dem Artikel „Understanding the CSI», dessen Übersetzung wir vor einem Jahr veröffentlicht haben.

Vorteile dieser Implementierung

  • Für grundlegende Dinge – zum Beispiel, um einen Treiber für einen Knoten zu registrieren – haben die Entwickler von Kubernetes ein Set von Containern implementiert. Es ist nicht mehr notwendig, selbst die JSON-Antwort mit den capabilities zu erstellen, wie es zuvor für den Flexvolume-Plugin gemacht wurde.
  • Anstatt «Files in Nodes einzuschleusen», stellen wir jetzt Pods im Cluster bereit. Genau das erwarten wir von Kubernetes: Alle Prozesse finden innerhalb von Containern statt, die mit Kubernetes-Primitiven bereitgestellt werden.
  • Für die Implementierung komplexer Treiber ist es nicht mehr notwendig, einen RPC-Server und einen RPC-Client zu entwickeln. Die Kubernetes-Entwickler haben das für uns erledigt.
  • Die Übergabe von Argumenten über das gRPC-Protokoll ist deutlich benutzerfreundlicher, flexibler und zuverlässiger als die Übergabe über Kommandozeilenargumente. Um zu verstehen, wie man in CSI Unterstützung für Metriken zur Nutzung von Volumes durch die Hinzufügung einer standardisierten gRPC-Methode integriert, können Sie sich informieren über Pull Request für den Treiber vsphere-csi.
  • Die Kommunikation erfolgt über IPC-Sockets, um Verwirrungen darüber zu vermeiden, an welchen Pod kubelet die Anfrage geschickt hat.

Erinnert Sie diese Liste an etwas? Die Vorteile von CSI sind die Lösungen für genau diese Probleme, die bei der Entwicklung des Flexvolume-Plugins nicht berücksichtigt wurden.

Fazit

Die CSI-Standards für die Implementierung von benutzerdefinierten Plugins zur Interaktion mit Datenspeichern wurden von der Community sehr positiv angenommen. Darüber hinaus werden aufgrund ihrer Vorteile und Vielseitigkeit sogar CSI-Treiber für Speichersysteme wie Ceph oder AWS EBS erstellt, deren Plugins bereits in der ersten Version von Kubernetes integriert waren.

Anfang 2019 wurden die In-Tree-Plugins als veraltet erklärt. Die Unterstützung des Flexvolume-Plugins wird fortgesetzt, jedoch werden keine neuen Funktionen dafür entwickelt.

Wir haben bereits Erfahrungen mit ceph-csi, vsphere-csi gesammelt und sind bereit, diese Liste zu erweitern! Bisher hat CSI die ihm übertragenen Aufgaben hervorragend gemeistert, und wir werden sehen, wie es weitergeht.

Vergessen Sie nicht, dass alles Neue gut durchdachtes Altes ist!

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster