
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.

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 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 1Tak 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
doneWaż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.

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 .
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 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.

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 „», 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 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 . 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
