Нашият опит в разработването на CSI драйвера в Kubernetes за Яндекс.Облака

Нашият опит в разработването на CSI драйвера в Kubernetes за Яндекс.Облака

С радостью обявяваме, че компанията "Флант" увеличава своя принос в Open Source инструментите за Kubernetes, пускайки алфа версия на CSI драйвера (Container Storage Interface) за Yandex Cloud.

Но преди да преминем към детайлите на реализацията, нека отговорим на въпроса защо всъщност е необходимо това, когато Yandex вече предлага услуга Managed Service for Kubernetes.

Въведение

Защо е това необходимо?

Вътре в нашата компания, още от самото начало на експлоатацията на Kubernetes в продукция (т.е. вече няколко години), се развива собствен инструмент (deckhouse), който, между другото, планираме скоро да направим достъпен като Open Source проект. С негова помощ ние унифицирано конфигурираме и настройваме всичките си клъстери, а в момента вече има над 100, и то на най-различни конфигурации на хардера и във всички налични облачни услуги.

Клъстерите, в които се използва deckhouse, съдържат всички необходими компоненти за работа: балансировчици, мониторинг с удобни графики, метрики и аларми, аутентификация на потребителите чрез външни доставчици за достъп до всички табла и така нататък. Такъв "прокачен" клъстер не е смислено да се поставя в managed решение, тъй като често това е или невъзможно, или ще доведе до необходимостта да се изключат половината компоненти.

NB: Това е нашият опит и той е доста специфичен. Ние по никакъв начин не твърдим, че на всеки му е необходимо самостоятелно да се занимава с разгръщането на клъстери Kubernetes, вместо да използва готови решения. Между другото, нямаме реален опит с експлоатацията на Kubernetes от страна на Yandex и няма да даваме никаква оценка на тази услуга в настоящата статия.

Какво е това и за кого е?

Така че, вече разказахме за съвременния подход към хранилищата в Kubernetes: как е устроен CSI и как общността е стигнала до този подход.

В момента много големи доставчици на облачни услуги разработват драйвери за използване на своите "облачни" дискове като Persistent Volume в Kubernetes. Ако обаче такъв драйвер не е наличен при доставчика, но всички необходими функции се предоставят чрез API, нищо не пречи да се реализира драйвер собственоръчно. Тъкмо така се получи и при нас с Yandex.Cloud.

За основа на разработването взехме CSI драйвера за облака DigitalOcean и няколко идеи от драйвера за GCP, тъй като взаимодействието с API на тези облаци (Google и Yandex) има редица сходства. По-специално, API и на GCP, и у Yandex върщат обект Операция за проследяване на статуса на дългосрочни операции (например, създаване на нов диск). За взаимодействие с API на Yandex.Cloud се използва Yandex.Cloud Go SDK.

Резултатът от извършената работа е публикуван на GitHub и може да бъде полезен на тези, които по някаква причина използват собствена инсталация на Kubernetes на виртуални машини в Yandex.Cloud (но не готов управляван клъстър) и биха искали да използват (поръчват) дискове чрез CSI.

Реализация

Основни възможности

Към момента драйверът поддържа следните функции:

  • Поръчка на дискове във всички зони на клъстера според топологията на наличните възли в клъстера;
  • Изтриване на по-рано поръчани дискове;
  • Offline промяна на размера на дискове (Yandex.Cloud не поддържа увеличаване на дискове, които са монтирани на виртуалната машина). За това как се наложи да се доработи драйверът, за да се извършва промяната на размера максимално безболезнено, вижте по-долу.

В бъдеще се предвижда реализиране на поддръжка за създаване и изтриване на моментни снимки на дискове.

Основна трудност и нейното преодоляване

Отсъствието на възможност в API на Yandex.Cloud за увеличаване на дискове в реално време — ограничение, което усложнява операцията по промяната на размера за PV (Persistent Volume): в такъв случай е необходимо pod приложението, което използва диска, да бъде спряно, а това може да доведе до временно прекратяване на приложението.

Според спецификации CSI, ако CSI контролерът съобщава, че може да прави промяна на размера на дисковете само „в офлайн режим“ (VolumeExpansion.OFFLINE), то процесът на увеличаване на диска трябва да протече така:

Ако плъгинът има само VolumeExpansion.OFFLINE възможности за разширение и обемът в момента е публикуван или наличен на възел, тогава ControllerExpandVolume ТРЯБВА да бъде извикан САМО след:

  • Плъгинът има контролер PUBLISH_UNPUBLISH_VOLUME възможност и ControllerUnpublishVolume е бил успешно извикан.

ИЛИ

  • Плъгинът НЯМА контролер PUBLISH_UNPUBLISH_VOLUME възможност, плъгинът има възел STAGE_UNSTAGE_VOLUME възможност и NodeUnstageVolume е бил успешно завършен.

ИЛИ

  • Плъгинът НЯМА контролер PUBLISH_UNPUBLISH_VOLUME възможност, нито възел STAGE_UNSTAGE_VOLUME възможност и NodeUnpublishVolume е завършен успешно.

По същество това означава, че е необходимо да се откъсне диска от виртуалната машина, преди да се увеличава.

Въпреки това, за съжаление, реализации спецификациите на CSI чрез sidecar не отговарят на тези изисквания:

  • В sidecar контейнера csi-attacher, който трябва да отговаря за присъствието на необходимия интервал между монтиранията, при офлайн промяна на размера просто не е реализирана тази функционалност. Обсъждането по този въпрос е иницирано тук..
  • Какво всъщност е sidecar контейнер в този контекст? Самият CSI плъгин не взаимодейства с Kubernetes API, а само реагира на gRPC повиквания, които му изпращат sidecar контейнерите. Последните се разработват в общността на Kubernetes.

В нашия случай (CSI плъгин) операцията за увеличаване на диска изглежда по следния начин:

  1. Получаваме gRPC повикване ControllerExpandVolume;
  2. Опитваме се да увеличим диска в API, но получаваме грешка за невъзможност за изпълнение на операцията, тъй като дискът е монтиран;
  3. Запазваме идентификатора на диска в map, съдържащ дисковете, за които е необходимо да се извърши операцията за увеличаване. По-кратко, ще наричаме този map volumeResizeRequired;
  4. Ръчно изтриваме pod, който използва диска. Kubernetes автоматично ще го рестартира. За да не успее дискът да се монтира (ControllerPublishVolume) преди завършване на операцията по увеличаване при опит за монтиране, проверяваме дали дискът все още е в volumeResizeRequired и връщаме грешка;
  5. CSI драйверът се опитва да повтори операцията по resize. Ако операцията е успешна, изтриваме диска от volumeResizeRequired;
  6. Тъй като идентификаторът на диска липсва в volumeResizeRequired, ControllerPublishVolume преминава успешно, дискът се монтира, pod-ът се стартира.

Всичко изглежда доста просто, но както винаги, има подводни камъни. Увеличаването на дисковете се осъществява от external-resizer, който в случай на грешка при изпълнение на операцията използва опашка с експоненциално увеличаване на времето за таймаут до 1000 секунди:

func DefaultControllerRateLimiter() RateLimiter {
  return NewMaxOfRateLimiter(
  NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
  	// 10 qps, 100 bucket size. Това е само за скорост на повторение и е само общ фактор (не за всяка отделна единица)
  &BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
  )
}

Това може периодично да доведе до това, че операцията по увеличаване на диска се разпъва на 15+ минути, което води до недостъпност на съответния pod.

Единственият вариант, който достатъчно лесно и безболезнено позволи да намалим потенциалното време на неработоспособност, стана използването на наша версия на external-resizer с максимално ограничение на таймаута от 5 секунди:

workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 5*time.Second)

Не смятахме за необходимо спешно да инициираме дискусия и патчваме external-resizer, защото offline увеличаването на дискове е архаизъм, който скоро ще изчезне при всички облачни доставчици.

Как да започнете да използвате?

Драйверът се поддържа в Kubernetes версия 1.15 и по-нова. За работата на драйвера трябва да се изпълнят следните изисквания:

  • FPIPE --allow-privileged установен на стойност истинно за API-сървъра и kubelet;
  • Включени --feature-gates=VolumeSnapshotDataSource=true,KubeletPluginsWatcher=true,CSINodeInfo=true,CSIDriverRegistry=true за API-сървъра и kubelet;
  • Разпространение на монтиране (mount propagation) трябва да бъде включено в кластера. При използване на Docker, демонът трябва да е конфигуриран да разрешава споделени обекти за монтиране (shared mounts).

Всички стъпки по самата инсталация са описани в README. Инсталацията представлява създаване на обекти в Kubernetes от манифести.

За работа на драйвера ще Ви е необходимо следното:

  • Да посочите в манифеста идентификатор на директория (folder-id) на Яндекс.Облак (вж. документацията);
  • За взаимодействие с API на Яндекс.Облак в CSI драйвера се използва сервизен акаунт. В манифеста Secret трябва да предадете упълномощени ключове от сервизния акаунт. В документацията е описано, как да създадете сервизен акаунт и да получите ключове.

В общи линии — опитайте, а ние ще се радваме на обратна връзка и нови issues, ако се сблъскате с някакви проблеми!

Допълнителна поддръжка

В заключение, важно е да отбележим, че този CSI драйвер сме реализирали не от голямо желание да се забавляваме с писането на приложения на Go, а от остра необходимост в компанията. Не смятаме за уместно да поддържаме собствена реализация, затова ако Яндекс прояви интерес и реши да продължи подкрепата на драйвера, с удоволствие ще предадем репозитория на тях.

Освен това, вероятно има собствена реализация на CSI драйвера в управлявания кластер на Kubernetes на Яндекс, която може да бъде пусната с отворен код. Този вариант за развитие ни изглежда също благоприятен — общността ще може да ползва изпробван драйвер от доставчика на услуги, а не от трета страна.

P.S.

Прочетете също в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster