Volume-Plugins für Kubernetes-Speicher: von Flexvolume bis CSI

Volume-Plugins für Kubernetes-Speicher: von Flexvolume bis CSI

Als Kubernetes noch v1.0.0 war, gab es Volume-Plugins. Sie wurden benötigt, um Systeme zur Speicherung persistenter (permanenter) Containerdaten mit Kubernetes zu verbinden. Ihre Zahl war gering und zu den ersten gehörten Speicheranbieter wie GCE PD, Ceph, AWS EBS und andere.

Die Plugins wurden zusammen mit Kubernetes ausgeliefert, weshalb sie auch ihren Namen erhielten – in-tree. Für viele erwies sich der vorhandene Satz solcher Plugins jedoch als unzureichend. Handwerker fügten mithilfe von Patches einfache Plugins zum Kubernetes-Kern hinzu, stellten anschließend ihr eigenes Kubernetes zusammen und installierten es auf ihren Servern. Aber mit der Zeit haben die Kubernetes-Entwickler das erkannt Fisch Das Problem kann nicht gelöst werden. Menschen brauchen Angelrute. Und in der Veröffentlichung von Kubernetes v1.2.0 erschien es ...

Flexvolume-Plugin: Minimale Angelrute

Kubernetes-Entwickler haben das FlexVolume-Plugin erstellt, ein logisches Framework aus Variablen und Methoden für die Arbeit mit Flexvolume-Treibern, die von Drittentwicklern implementiert wurden.

Lassen Sie uns innehalten und einen genaueren Blick auf den FlexVolume-Treiber werfen. Das ist sicher ausführbare Datei (Binärdatei, Python-Skript, Bash-Skript usw.), das bei seiner Ausführung Befehlszeilenargumente als Eingabe verwendet und eine Nachricht mit vorbekannten Feldern im JSON-Format zurückgibt. Konventionell ist das erste Befehlszeilenargument immer eine Methode und die übrigen Argumente sind deren Parameter.

Volume-Plugins für Kubernetes-Speicher: von Flexvolume bis CSI
Verbindungsdiagramm für CIFS-Freigaben in OpenShift. Flexvolume-Treiber – genau in der Mitte

Mindestsatz an Methoden Es sieht wie folgt aus:

flexvolume_driver mount # отвечает за присоединение тома к pod'у
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
}

flexvolume_driver unmount # отвечает за отсоединение тома от pod'а
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
}

flexvolume_driver init # отвечает за инициализацию плагина
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
  // Определяет, использует ли драйвер методы attach/deatach
  "capabilities":{"attach": True/False}
}

Methoden nutzen attach и detach definiert das Szenario, in dem das Kubelet künftig beim Aufruf des Treibers agieren wird. Es gibt auch spezielle Methoden expandvolume и expandfs, die für die dynamische Größenänderung des Volumes verantwortlich sind.

Als Beispiel für die Änderungen, die die Methode hinzufügt expandvolume, und damit die Möglichkeit, die Größe von Volumes in Echtzeit zu ändern, können Sie sich damit vertraut machen unsere Pull-Anfrage im Rook Ceph Operator.

Und hier ist ein Beispiel für die Implementierung des Flexvolume-Treibers für die Arbeit mit NFS:

usage() {
    err "Invalid usage. Usage: "
    err "t$0 init"
    err "t$0 mount <mount dir> <json params>"
    err "t$0 unmount <mount dir>"
    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": "Success"}'
        exit 0
    fi

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

    mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Failure", "message": "Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}"}"
        exit 1
    fi
    log '{"status": "Success"}'
    exit 0
}

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

    umount ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Failed", "message": "Failed to unmount volume at ${MNTPATH}"}"
        exit 1
    fi

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

op=$1

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

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

shift

case "$op" in
    mount)
        domount $*
        ;;
    unmount)
        unmount $*
        ;;
    *)
        log '{"status": "Not supported"}'
        exit 0
esac

exit 1

Nachdem Sie also die eigentliche ausführbare Datei vorbereitet haben, müssen Sie Folgendes tun Laden Sie den Treiber in den Kubernetes-Cluster hoch. Der Treiber muss auf jedem Clusterknoten gemäß einem vorgegebenen Pfad lokalisiert werden. Standardmäßig war Folgendes ausgewählt:

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

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

Flexvolume-Probleme: Wie wirft man eine Angelrute richtig?

Das Hochladen des Flexvolume-Treibers auf Clusterknoten erwies sich als keine triviale Aufgabe. Nachdem der Vorgang einmal manuell durchgeführt wurde, kann es leicht zu einer Situation kommen, in der neue Knoten im Cluster erscheinen: durch das Hinzufügen eines neuen Knotens, durch automatische horizontale Skalierung oder – was noch schlimmer ist – durch den Austausch eines Knotens aufgrund einer Fehlfunktion. In diesem Fall sollte mit dem Speicher auf diesen Knoten gearbeitet werden unmöglich, bis Sie den Flexvolume-Treiber noch manuell hinzufügen.

Die Lösung für dieses Problem war eines der Kubernetes-Grundelemente – DaemonSet. Wenn ein neuer Knoten im Cluster erscheint, enthält dieser automatisch einen Pod aus unserem DaemonSet, an den entlang des Pfads ein lokales Volume angehängt wird, um Flexvolume-Treiber zu finden. Nach erfolgreicher Erstellung kopiert der Pod die für die Arbeit des Treibers erforderlichen Dateien auf die Festplatte.

Hier ist ein Beispiel für ein solches DaemonSet zum Entwerfen eines Flexvolume-Plugins:

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

... und ein Beispiel für ein Bash-Skript zum Entwerfen 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, den Kopiervorgang nicht zu vergessen ist nicht atomar. Es besteht eine hohe Wahrscheinlichkeit, dass Kubelet den Treiber verwendet, bevor der Bereitstellungsprozess abgeschlossen ist, was zu einem Systemabsturz führt. Der richtige Ansatz besteht darin, zunächst die Treiberdateien unter einem anderen Namen zu kopieren und dann eine atomare Umbenennungsoperation zu verwenden.

Volume-Plugins für Kubernetes-Speicher: von Flexvolume bis CSI
Diagramm der Arbeit mit Ceph im Rook-Operator: Der Flexvolume-Treiber im Diagramm befindet sich im Rook-Agenten

Das nächste Problem bei der Verwendung von Flexvolume-Treibern betrifft den Großteil des Speichers auf einem Clusterknoten die dafür notwendige Software muss installiert sein (zum Beispiel das ceph-common-Paket für Ceph). Ursprünglich war das Flexvolume-Plugin nicht für die Implementierung solch komplexer Systeme konzipiert.

Eine originelle Lösung für dieses Problem ist in der Flexvolume-Treiberimplementierung des Rook-Operators zu sehen:

Der Treiber selbst ist als RPC-Client konzipiert. Der IPC-Socket für die Kommunikation liegt im selben Verzeichnis wie der Treiber selbst. Wir erinnern uns, dass es zum Kopieren von Treiberdateien gut wäre, DaemonSet zu verwenden, das das Verzeichnis als Volume mit dem Treiber verbindet. Nach dem Kopieren der erforderlichen Rook-Treiberdateien stirbt dieser Pod nicht ab, sondern stellt über das angeschlossene Volume eine Verbindung zum IPC-Socket als vollwertiger RPC-Server her. Das Paket ceph-common ist bereits im Pod-Container installiert. Der IPC-Socket stellt sicher, dass das Kubelet mit genau dem Pod kommuniziert, der sich auf demselben Knoten befindet. Alles Geniale ist einfach!..

Auf Wiedersehen, unsere liebevollen... In-Tree-Plugins!

Kubernetes-Entwickler haben herausgefunden, dass die Anzahl der Plugins für die Speicherung im Kern zwanzig beträgt. Und eine Änderung in jedem von ihnen durchläuft auf die eine oder andere Weise den gesamten Kubernetes-Release-Zyklus.

Es stellt sich heraus, dass zur Verwendung der neuen Version des Speicher-Plugins Sie müssen den gesamten Cluster aktualisieren. Darüber hinaus werden Sie möglicherweise überrascht sein, dass die neue Version von Kubernetes plötzlich nicht mehr mit dem von Ihnen verwendeten Linux-Kernel kompatibel ist ... Sie wischen sich also die Tränen aus und koordinieren zähneknirschend mit Ihrem Management und den Benutzern die Zeit dafür Aktualisieren Sie den Linux-Kernel und den Kubernetes-Cluster. Mit möglichen Ausfallzeiten bei der Leistungserbringung.

Die Situation ist mehr als komisch, finden Sie nicht? Der gesamten Community wurde klar, dass der Ansatz nicht funktionierte. Durch eine bewusste Entscheidung geben die Kubernetes-Entwickler bekannt, dass neue Plugins für die Arbeit mit Speicher nicht mehr in den Kernel aufgenommen werden. Darüber hinaus wurden, wie wir bereits wissen, eine Reihe von Mängeln bei der Implementierung des Flexvolume-Plugins festgestellt ...

Das neueste hinzugefügte Plugin für Volumes in Kubernetes, CSI, sollte das Problem mit der persistenten Datenspeicherung ein für alle Mal lösen. Die Alpha-Version, besser bekannt als Out-of-Tree CSI Volume Plugins, wurde in der Veröffentlichung angekündigt Kubernetes 1.9.

Container Storage Interface oder CSI 3000 Spinnrute!

Zunächst möchte ich anmerken, dass CSI nicht nur ein Volume-Plugin ist, sondern ein echtes стандарт zum Erstellen benutzerdefinierter Komponenten für die Arbeit mit Data Warehouses. Container-Orchestrierungssysteme wie Kubernetes und Mesos sollten „lernen“, mit nach diesem Standard implementierten Komponenten zu arbeiten. Und jetzt habe ich Kubernetes bereits gelernt.

Wie ist das CSI-Plugin in Kubernetes aufgebaut? Das CSI-Plugin funktioniert mit speziellen Treibern (CSI-Treiber), geschrieben von Drittentwicklern. Ein CSI-Treiber in Kubernetes sollte mindestens aus zwei Komponenten (Pods) bestehen:

  • Controller – verwaltet externe persistente Speicher. Es ist als gRPC-Server implementiert, für den das Primitiv verwendet wird StatefulSet.
  • Knoten – ist für die Bereitstellung von persistentem Speicher auf Clusterknoten verantwortlich. Es ist auch als gRPC-Server implementiert, verwendet jedoch das Primitiv DaemonSet.

Volume-Plugins für Kubernetes-Speicher: von Flexvolume bis CSI
So funktioniert das CSI-Plugin in Kubernetes

Weitere Details zur Arbeit von CSI erfahren Sie beispielsweise im Artikel „Den C.S.I. verstehen" Übersetzung davon wir haben vor einem Jahr veröffentlicht.

Die Vorteile einer solchen Implementierung

  • Für grundlegende Dinge wie die Registrierung eines Treibers für einen Knoten implementierten die Kubernetes-Entwickler eine Reihe von Containern. Sie müssen nicht mehr selbst eine JSON-Antwort mit Funktionen generieren, wie dies beim Flexvolume-Plugin der Fall war.
  • Anstatt ausführbare Dateien auf Knoten zu „schieben“, laden wir jetzt Pods in den Cluster hoch. Das ist es, was wir zunächst von Kubernetes erwarten: Alle Prozesse finden in Containern statt, die mit Kubernetes-Primitiven bereitgestellt werden.
  • Sie müssen keinen RPC-Server und RPC-Client mehr entwickeln, um komplexe Treiber zu implementieren. Der Client wurde für uns von Kubernetes-Entwicklern implementiert.
  • Die Übergabe von Argumenten an die Arbeit über das gRPC-Protokoll ist viel bequemer, flexibler und zuverlässiger als die Übergabe über Befehlszeilenargumente. Um zu verstehen, wie Sie CSI durch Hinzufügen einer standardisierten gRPC-Methode Unterstützung für Volumennutzungsmetriken hinzufügen können, können Sie Folgendes lesen: unsere Pull-Anfrage für Vsphere-CSI-Treiber.
  • Die Kommunikation erfolgt über IPC-Sockets, um nicht zu verwechseln, ob das Kubelet die Anfrage an den richtigen Pod gesendet hat.

Erinnert Sie diese Liste an irgendetwas? Die Vorteile von CSI sind die gleichen Probleme lösen, die bei der Entwicklung des Flexvolume-Plugins nicht berücksichtigt wurden.

Befund

CSI als Standard zur Implementierung benutzerdefinierter Plugins für die Interaktion mit Data Warehouses wurde von der Community sehr positiv aufgenommen. Darüber hinaus werden CSI-Treiber aufgrund ihrer Vorteile und Vielseitigkeit sogar für Speichersysteme wie Ceph oder AWS EBS erstellt, deren Plugins für die Arbeit mit der allerersten Version von Kubernetes hinzugefügt wurden.

Anfang 2019 In-Tree-Plugins wurden für veraltet erklärt. Wir planen, das Flexvolume-Plugin weiterhin zu unterstützen, werden jedoch keine neuen Funktionen dafür entwickeln.

Wir selbst haben bereits Erfahrung mit ceph-csi, vsphere-csi und sind bereit, diese Liste zu ergänzen! Bisher meistert CSI die ihm übertragenen Aufgaben mit Bravour, aber wir werden abwarten.

Vergessen Sie nicht, dass alles Neue ein gutes Überdenken des Alten ist!

PS

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen