Wtyczki wolumenów dla magazynów w Kubernetes: od Flexvolume do CSI

Wtyczki wolumenów dla magazynów w Kubernetes: od Flexvolume do CSI

W czasach, gdy Kubernetes był jeszcze w wersji v1.0.0, istniały wtyczki do woluminów (volume plugins). Były one potrzebne do podłączania systemów przechowywania danych pervistywnych (trwałych) kontenerów do Kubernetes. Ich liczba była niewielka, a wśród pierwszych były takie dostawcy pamięci masowej, jak GCE PD, Ceph, AWS EBS i inni.

Wtyczki były dostarczane razem z Kubernetes, za co otrzymały swoją nazwę — in-tree. Jednak wielu użytkowników uznało, że istniejący zestaw tych wtyczek jest niewystarczający. Zdolni deweloperzy dodawali proste wtyczki do rdzenia Kubernetes za pomocą łatek, a następnie budowali swój własny Kubernetes i instalowali go na swoich serwerach. Z czasem jednak twórcy Kubernetes zrozumieli, że to nie wystarczy. Ludziom potrzebna jest wędka.I w wydaniu Kubernetes v1.2.0 pojawiła się…

Wtyczka Flexvolume: wędka w minimalistycznym wydaniu.

Twórcy Kubernetes stworzyli wtyczkę FlexVolume, która była logicznym opakowaniem złożonym z zmiennych i metod do pracy z realizowanymi przez zewnętrznych deweloperów sterownikami Flexvolume.

Przyjrzyjmy się dokładniej, czym jest sterownik FlexVolume. Jest to pewien plik wykonywalny (plik binarny, skrypt Python, skrypt Bash itp.), który podczas wykonywania przyjmuje argumenty w wierszu poleceń i zwraca wiadomość z wcześniej zdefiniowanymi polami w formacie JSON. Pierwszym argumentem wiersza poleceń według umowy zawsze jest metoda, a pozostałe argumenty to jej parametry.

Wtyczki wolumenów dla magazynów w Kubernetes: od Flexvolume do CSI
Schemat podłączenia udostępnianych zasobów CIFS w OpenShift. Sterownik Flexvolume — dokładnie pośrodku.

Minimalny zestaw metod wygląda tak:

flexvolume_driver mount # odpowiada za podłączenie wolumenu do pod'a
# Format zwracanej wiadomości:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "Dlaczego został zwrócony taki status",
}

flexvolume_driver unmount # odpowiada za odłączenie wolumenu od pod'a
# Format zwracanej wiadomości:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "Dlaczego został zwrócony taki status",
}

flexvolume_driver init # odpowiada za inicjalizację wtyczki
# Format zwracanej wiadomości:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "Dlaczego został zwrócony taki status",
  // Określa, czy sterownik używa metod attach/detach
  "capabilities":{"attach": True/False}
}

Wykorzystanie metod attach i detach określi scenariusz, według którego w przyszłości kubelet będzie działać przy wywołaniu sterownika. Istnieją również specjalne metody expandvolume i expandfs, które odpowiadają za dynamiczną zmianę rozmiaru woluminu.

Przykładem zmian, które dodaje metoda expandvolume, a wraz z nią — możliwość wykonywania zmiany rozmiaru woluminów w czasie rzeczywistym, można zapoznać się z naszym pull requestem w Rook Ceph Operator.

Oto przykład implementacji sterownika Flexvolume do pracy z NFS:

usage() {
    err "Niepoprawne użycie. Użycie: "
    err "t$0 init"
    err "t$0 mount <katalog montowania> <parametry json>"
    err "t$0 unmount <katalog montowania>"
    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": "Sukces"}'
        exit 0
    fi

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

    mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Niepowodzenie", "message": "Nie udało się zamontować ${NFS_SERVER}:${SHARE} w ${MNTPATH}"}"
        exit 1
    fi
    log '{"status": "Sukces"}'
    exit 0
}

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

    umount ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Niepowodzenie", "message": "Nie udało się odmontować woluminu w ${MNTPATH}"}"
        exit 1
    fi

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

op=$1

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

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

shift

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

exit 1

Tak więc, po przygotowaniu pliku wykonywalnego należy umieścić sterownik w klastrze Kubernetes. Sterownik musi być obecny na każdym węźle klastra zgodnie z wcześniej ustaloną ścieżką. Domyślnie wybrano:

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

… ale podczas używania różnych dystrybucji Kubernetes (OpenShift, Rancher…) ścieżka może być inna.

Problemy z Flexvolume: jak właściwie zarzucić wędkę?

Umieszczenie sterownika Flexvolume na węzłach klastra okazało się skomplikowanym zadaniem. Po wykonaniu operacji raz ręcznie, łatwo napotkać sytuację, gdy w klastrze pojawią się nowe węzły: z powodu dodania nowego węzła, automatycznego poziomego skalowania lub — co gorsza — wymiany węzła z powodu awarii. W takim przypadku praca z magazynem na tych węzłach odbywa się niemożliwe, dopóki manualnie nie dodasz sterownika Flexvolume na nich.

Rozwiązaniem tego problemu stał się jeden z elementów Kubernetes — DaemonSet. Po pojawieniu się nowego węzła w klastrze automatycznie na nim ląduje pod z naszego DaemonSet’a, do którego przyłączany jest lokalny wolumen pod ścieżką do lokalizacji sterowników Flexvolume. Po pomyślnym utworzeniu pod kopiowane są niezbędne pliki do działania sterownika na dysk.

Oto przykład takiego DaemonSet’a do wdrożenia wtyczki Flexvolume:

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:

… i przykład skryptu Bash do wdrożenia sterownika Flexvolume:

#!/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

Ważne jest, aby nie zapomnieć, że operacja kopiowania nie jest atomowa. Istnieje duże prawdopodobieństwo, że kubelet zacznie korzystać ze sterownika, zanim proces jego przygotowania się zakończy, co spowoduje błąd w działaniu systemu. Odpowiednie rozwiązanie polega na najpierw skopiowaniu plików sterownika pod inną nazwą, a następnie użyciu atomowej operacji zmiany nazwy.

Wtyczki wolumenów dla magazynów w Kubernetes: od Flexvolume do CSI
Schemat pracy z Ceph w operatorze Rook: sterownik Flexvolume na schemacie znajduje się wewnątrz agenta Rook

Kolejnym problemem przy korzystaniu ze sterowników Flexvolume jest to, że dla większości magazynów na węźle klastra musisz zainstalować niezbędne oprogramowanie (na przykład pakiet ceph-common dla Ceph). Na początku wtyczka Flexvolume nie została zaprojektowana do realizacji tak skomplikowanych systemów.

Oryginalne rozwiązanie tego problemu można zobaczyć w implementacji sterownika Flexvolume operatora Rook:

Sam sterownik jest zrealizowany w postaci klienta RPC. Soket IPC do komunikacji znajduje się w tym samym katalogu, co sam sterownik. Pamiętamy, że do kopiowania plików sterownika byłoby dobrze użyć DaemonSet, który jako wolumen podłącza sobie katalog z sterownikiem. Po skopiowaniu niezbędnych plików sterownika rook ten pod nie umiera, a łączy się z soketem IPC przez podłączony wolumen jako pełnoprawny serwer RPC. Pakiet ceph-common jest już zainstalowany wewnątrz kontenera pod’a. Soket IPC zapewnia, że kubelet będzie komunikował się dokładnie z tym pod’em, który znajduje się z nim na tym samym węźle. Wszystko genialne jest proste!..

Do widzenia, nasze kochane… wtyczki in-tree!

Programiści Kubernetes odkryli, że liczba wtyczek do pamięci w jądrze wynosi dwadzieścia. Zmiana w każdej z nich w ten czy inny sposób przechodzi przez pełny cykl wydania Kubernetes.

Okazuje się, że aby używać nowej wersji wtyczki pamięci, należy zaktualizować cały klaster. Ponadto możesz być zaskoczony, że nowa wersja Kubernetes nagle stanie się niekompatybilna z używanym jądrem Linux… W związku z tym ocierasz łzy i zgrzytając zębami uzgadniasz z kierownictwem i użytkownikami czas aktualizacji jądra Linux oraz klastra Kubernetes. Z możliwym przestojem w świadczeniu usług.

Sytuacja jest bardziej niż komiczna, prawda? Cała społeczność zdała sobie sprawę, że takie podejście nie działa. Decyzją programistów Kubernetes ogłoszono, że nowe wtyczki do pamięci nie będą już przyjmowane do jądra. Dodatkowo, jak już wiemy, w realizacji wtyczki Flexvolume zidentyfikowano szereg niedoróbek…

Ostatecznym rozwiązaniem kwestii z trwałymi pamięciami danych miała być ostatnio dodana wtyczka do woluminów w Kubernetes — CSI. Jej wersję alfa, pełniej nazywaną Out-of-Tree CSI Volume Plugins, zapowiedziano w wydaniu Kubernetes 1.9.

Container Storage Interface, czyli spinn CSI 3000!

Na początek chciałbym zauważyć, że CSI to nie tylko wtyczka do woluminów, ale prawdziwy standard do tworzenia komponentów użytkowych do pracy z pamięciami danych.Zakładano, że systemy kontenerowe, takie jak Kubernetes i Mesos, powinny «nauczyć się» pracy z komponentami wdrożonymi zgodnie z tym standardem. I oto Kubernetes już się nauczył.

Jak wygląda wtyczka CSI w Kubernetes? Wtyczka CSI współpracuje ze specjalnymi sterownikami (sterownikami CSI), napisanymi przez zewnętrznych programistów. Sterownik CSI w Kubernetes musi minimalnie składać się z dwóch komponentów (podów):

  • Kontroler — zarządza zewnętrznymi trwałymi pamięciami. Wydawany jest jako serwer gRPC, dla którego używany jest prymityw StatefulSet.
  • Węzeł — odpowiada za montowanie trwałych pamięci do węzłów klastra. Również realizowany jest jako serwer gRPC, ale dla niego używany jest prymityw DaemonSet.

Wtyczki wolumenów dla magazynów w Kubernetes: od Flexvolume do CSI
Schemat działania wtyczki CSI w Kubernetes

O niektórych innych szczegółach działania CSI możesz się dowiedzieć, na przykład z artykułu „Understanding the CSI», przekład którego publikowaliśmy rok temu.

Zalety takiej realizacji

  • Dla podstawowych rzeczy — na przykład rejestracji sterownika dla węzła — twórcy Kubernetes zaimplementowali zestaw kontenerów. Już nie trzeba samodzielnie tworzyć odpowiedzi JSON z capabilities, jak to było w przypadku wtyczek Flexvolume.
  • Zamiast „podstawiania” plików wykonywalnych na węzły, teraz publikujemy w klastrze pod’y. Tego właśnie oczekujemy od Kubernetes: wszystkie procesy odbywają się wewnątrz kontenerów uruchomionych przy użyciu prymitywów Kubernetes.
  • Do realizacji złożonych sterowników nie trzeba już rozwijać serwera RPC i klienta RPC. Klienta za nas zaimplementowali twórcy Kubernetes.
  • Przekazywanie argumentów do pracy w protokole gRPC jest znacznie wygodniejsze, elastyczniejsze i bezpieczniejsze niż ich przekazywanie za pomocą argumentów wiersza poleceń. Aby zrozumieć, jak dodać do CSI wsparcie dla metryk dotyczących użycia wolumenu poprzez dodanie znormalizowanej metody gRPC, można zapoznać się z naszym pull requestem dla sterownika vsphere-csi.
  • Komunikacja odbywa się przez sokety IPC, aby nie było zamieszania, do którego pod’a kubelet wysłał zapytanie.

Czy ten wykaz nie przypomina wam niczego? Korzyści z CSI to rozwiązania tych właśnie problemów, które nie zostały uwzględnione przy opracowywaniu wtyczki Flexvolume.

Wnioski

CSI jako standard realizacji wtyczek użytkowników do interakcji z pamięciami masowymi został bardzo ciepło przyjęty przez społeczność. Co więcej, dzięki swoim zaletom i wszechstronności, sterowniki CSI są tworzone nawet dla takich pamięci masowych jak Ceph czy AWS EBS, wtyczki do których zostały dodane już w pierwszej wersji Kubernetes.

Na początku 2019 roku wtyczki in-tree zostały ogłoszone przestarzałe. Planuje się kontynuację wsparcia dla wtyczki Flexvolume, ale nowe funkcjonalności dla niej nie będą opracowywane.

Sami mamy już doświadczenie w używaniu ceph-csi, vsphere-csi i jesteśmy gotowi uzupełnić tę listę! Na razie CSI z nałożonymi na nie zadaniami radzi sobie świetnie, a co będzie dalej, zobaczymy.

Nie zapominajcie, że wszystko nowe — to dobrze przemyślane stare!

P.S.

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster