
В时代, когато Kubernetes беше още v1.0.0, съществуваха плъгини за обеми (volume plugins). Те бяха необходими за свързване на Kubernetes с хранилищни системи за персистентни (постоянни) данни на контейнерите. Броят им не беше голям, а сред първите бяха доставчици на хранилища като GCE PD, Ceph, AWS EBS и други.
Плъгините се доставяха с Kubernetes, оттук и името им — in-tree. Въпреки това, на много хора им се струваше, че настоящият набор от такива плъгини е недостатъчен. Умелите програмисти добавяха прости плъгини в ядрото на Kubernetes чрез патчове, след което компилираха собствен Kubernetes и го инсталираха на собствените си сървъри. Но с времето разработчиците на Kubernetes осъзнаха, че рыбой проблемът не може да бъде решен. На хората им трябва удочка. И в изданието на Kubernetes v1.2.0 тя се появи...
Плъгин Flexvolume: удочка на минималките
Разработчиците на Kubernetes създадоха плъгина FlexVolume, който представлява логическа обвивка от променливи и методи за работа с реализираните от трети страни Flexvolume-драйвери.
Нека спрем и по-подробно да разгледаме какво представлява драйвера FlexVolume. Това е нещо като изпълним файл (бинарен файл, Python скрипт, Bash скрипт и др.), който при изпълнение приема аргументи от командния ред и връща съобщение с предварително известни полета в JSON формат. Първият аргумент от командния ред по споразумение винаги е метод, а другите аргументи — негови параметри.

Схема на свързване на CIFS Shares в OpenShift. Драйвер Flexvolume — точно по средата
Минимален набор от методи изглежда така:
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}
} Използване на методите attach и detach определя сценария, по който в бъдеще kubelet ще действа при извикване на драйвера. Съществуват и специални методи expandvolume и expandfs, които отговарят за динамичното изменение на размера на тома.
Като пример за промените, които добавя методът expandvolume, а заедно с него — и възможността за извършване на промяна на размера на томовете в реално време, можете да се запознаете с в Rook Ceph Operator.
Ето пример за реализация на Flexvolume драйвера за работа с NFS:
употреба() {
грешка "Невалидна употреба. Употреба: "
грешка "t$0 init"
грешка "t$0 mount "
грешка "t$0 unmount "
изход 1
}
грешка() {
echo -ne $* 1>&2
}
лог() {
echo -ne $* &>1
}
ismounted() {
МОНТИРАНЕ=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
if [ "${МОНТИРАНЕ}" == "${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
лог '{"статус": "Успех"}'
изход 0
fi
mkdir -p ${MNTPATH} &> /dev/null
mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
грешка "{ "статус": "Неуспех", "съобщение": "Неуспешно монтиране на ${NFS_SERVER}:${SHARE} в ${MNTPATH}"}"
изход 1
fi
лог '{"статус": "Успех"}'
изход 0
}
unmount() {
MNTPATH=$1
if [ $(ismounted) -eq 0 ] ; then
лог '{"статус": "Успех"}'
изход 0
fi
umount ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
грешка "{ "статус": "Неуспех", "съобщение": "Неуспешно демонтиране на тома в ${MNTPATH}"}"
изход 1
fi
лог '{"статус": "Успех"}'
изход 0
}
op=$1
if [ "$op" = "init" ]; then
лог '{"статус": "Успех", "възможности": {"attach": false}}'
изход 0
fi
if [ $# -lt 2 ]; then
употреба
fi
shift
case "$op" in
mount)
domount $*
;;
unmount)
unmount $*
;;
*)
лог '{"статус": "Неподдържано"}'
изход 0
esac
изход 1Така, след подготовката на самия изпълняем файл, е необходимо да се качи драйвера в Kubernetes клъстер. Драйверът трябва да бъде на всеки възел в клъстера, съгласно предварително уговорен път. По подразбиране е избран:
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/имя_поставщика_хранилища~имя_драйвера/
… но при използване на различни дистрибуции на Kubernetes (OpenShift, Rancher…) пътят може да бъде различен.
Проблеми с Flexvolume: как да се постави правилно?
Качването на Flexvolume драйвера на възлите на клъстера се оказа не тривиална задача. След като извършите операцията веднъж ръчно, лесно можете да се сблъскате със ситуация, в която в клъстера ще се появят нови възли: поради добавянето на нов възел, автоматично хоризонтално мащабиране или — което е по-страшно — подмяна на възел поради повреда. В този случай работата с хранилището на данните на тези възли ще се извършва не е възможно, докато същевременно не добавите Flexvolume драйвера на тях ръчно.
Решение на този проблем е един от примитивите на Kubernetes — DaemonSet. Когато се появи нов възел в клъстера, на него автоматично се поставя pod от нашия DaemonSet, към който се присъединява локален том по пътя за намиране на Flexvolume драйвери. При успешното създаване на pod копира необходимите файлове за работата на драйвера на диска.
Ето пример за такъв DaemonSet за разгръщане на 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:… и пример за Bash скрипт за разгръщане на 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Важно е да не забравяме, че операцията по копиране не е атомарна. Има вероятност kubelet да започне да използва драйвера преди процесът на подготовка да бъде завършен, което ще доведе до грешка в работата на системата. Правилният подход е първо да се копират файловете на драйвера под друго име, след което да се използва атомарна операция за преименуване.

Схемата на работа с Ceph в оператора Rook: драйверът Flexvolume на схемата е вътре в агента Rook
Следващият проблем при използването на Flexvolume драйвери е, че за повечето хранилища на узела на клъстера трябва да бъде инсталиран необходимият софтуер (например пакетът ceph-common за Ceph). Първоначално плъгинът Flexvolume не е бил проектиран за реализиране на толкова сложни системи.
Оригиналното решение на този проблем може да бъде видяно в реализацията на Flexvolume драйвера на оператора Rook:
Самият драйвер е реализиран като RPC клиент. IPC сокетът за комуникация се намира в същата директория, в която е самият драйвер. Помним, че за копиране на файловете на драйвера е добре да се използва DaemonSet, който подкачва директорията с драйвера като том. След копиране на необходимите файлове драйвера, този pod не умира, а се свързва с IPC сокета чрез свързания том, действайки като пълноценен RPC сървър. Пакетът ceph-common вече е инсталиран вътре в контейнера на pod-а. IPC сокетът дава увереност, че kubelet ще комуникира точно с този pod, който е на един и същ възел с него. Всичко гениално е просто!.
Сбогом, нашите мили… in-tree плъгини!
Разработчиките на Kubernetes откриха, че броят на плъгините за хранилища в ядрото е двадесет. И промените във всеки от тях по един или друг начин преминават през пълен цикъл на издаване на Kubernetes.
Оказва се, че за да използвате новата версия на плъгина за хранилище, треба да актуализирате целия клъстер. В допълнение към това можете да се учудите, че новата версия на Kubernetes изведнъж ще стане несъвместима с използваното ядро на Linux… И поради това вие търкате сълзите си и, с прехапани зъби, уговоряте с ръководството и потребителите времето за обновяване на ядрото на Linux и клъстера Kubernetes. С възможен простой в предоставянето на услуги.
Ситуацията е повече от комична, нали? На цялата общност стана ясно, че подходът не работи. С волево решение, разработчиците на Kubernetes обявяват, че новите плъгини за работа с хранилища вече няма да бъдат приемани в ядрото. Освен това, както вече знаем, в реализирането на Flexvolume плъгина бяха открити редица недостатъци…
Последният добавен плъгин за томове в Kubernetes, имаше задачата да реши въпроса с персистентните хранилища на данни — CSI. Неговата алфа версия, по-пълно наречена Out-of-Tree CSI Volume Plugins, бе анонсирана в изданието .
Container Storage Interface, или спиннинг CSI 3000!
Първото нещо, което искам да подчертая, е, че CSI — това не е просто плъгин за томове, а истински за създаване на потребителски компоненти за работа с хранилища на данни.Очакваше се системите за оркестрация на контейнери, като Kubernetes и Mesos, да "научат" как да работят с компонентите, реализирани по този стандарт. И ето, Kubernetes вече е научил.
Какво представлява CSI плъгинът в Kubernetes? CSI плъгинът работи със специални драйвери (CSI драйвери), написани от външни разработчици. CSI драйверът в Kubernetes минимално трябва да се състои от два компонента (pod'а):
- Контролер — управлява външни персистентни хранилища. Издава се под формата на gRPC сървър, за който се използва примитив
StatefulSet. - Node — отговаря за монтирането на персистентни хранилища към узлите на клъстера. Също така се реализира в качеството на gRPC сървър, но за него се използва примитив
DaemonSet.

Схема на работа на CSI плъгина в Kubernetes
За някои други подробности относно работата на CSI можете да научите, например, от статията "», публикувахме преди година.
Плюсове на такава реализация
- За основните неща — например, за регистрация на драйвер за възел — разработчиците на Kubernetes реализираха набор от контейнери. Вече не е необходимо сами да изграждате JSON отговор с capabilities, както се правеше за плъгина Flexvolume.
- Вместо да „поставяме“ изпълними файлове на възлите, сега разполагаме pod’ове в кластера. Това е, което очакваме от Kubernetes: всички процеси стават вътре в контейнерите, разположени с помощта на примитиви на Kubernetes.
- За реализиране на сложни драйвери вече не е необходимо да разработвате RPC-сървър и RPC-клиент. Клиентът беше реализиран от разработчиците на Kubernetes.
- Предиаването на аргументи за работа по протокола gRPC е много по-удобно, гъвкаво и надеждно, отколкото чрез аргументи на командния ред. За да разберете как да добавите в CSI поддръжка на метрики за използване на тома чрез добавяне на стандартизиран gRPC-метод, можете да се запознаете с за драйвера vsphere-csi.
- Комуникацията става чрез IPC-сокети, за да не се бърка дали pod’ът, на който kubelet е изпратил заявка.
Този списък напомня ли ви нещо? Предимствата на CSI са решение за същите тези проблеми, които не бяха взети под внимание при разработването на плъгина Flexvolume.
Изводи
CSI като стандарт за реализация на потребителски плъгини за взаимодействие с хранилища на данни беше приет от общността с голямо одобрение. Освен това, благодарение на своите предимства и универсалност, CSI-драйвери се създават дори за такива хранилища, като Ceph или AWS EBS, плъгини за работа с които бяха добавени още в първата версия на Kubernetes.
В началото на 2019 година плъгините in-tree . Планира се продължаване на поддръжката на плъгина Flexvolume, но нови функционалности за него няма да бъдат разработвани.
Ние вече имаме опит с използването на ceph-csi, vsphere-csi и сме готови да допълваме този списък! Засега CSI се справя прекрасно с наложените му задачи, а там ще видим.
Не забравяйте, че всичко ново — това е добре преосмислено старо!
P.S.
Прочетете също в нашия блог:
- «»;
- «»;
- «».
Източник: habr.com
