Nasze doświadczenie w tworzeniu sterownika CSI w Kubernetes dla Yandex.Cloud

Nasze doświadczenie w tworzeniu sterownika CSI w Kubernetes dla Yandex.Cloud

Cieszymy się, że firma „Flant” zwiększa swój wkład w narzędzia Open Source dla Kubernetes, wydając wersję alfa sterownika CSI (Container Storage Interface) dla Yandex.Cloud.

Ale zanim przejdziemy do szczegółów implementacji, odpowiedzmy na pytanie, dlaczego to w ogóle jest potrzebne, gdy Yandex ma już usługę Managed Service for Kubernetes.

Wprowadzenie

Po co to?

W naszej firmie, od samego początku eksploatacji Kubernetes w środowisku produkcyjnym (tj. od kilku lat), rozwijamy własne narzędzie (deckhouse), które zresztą również planujemy wkrótce udostępnić jako projekt Open Source. Dzięki niemu jednolicie konfigurujemy i ustawiamy wszystkie nasze klastry, a obecnie jest ich już ponad 100, przy czym na bardzo różnych konfiguracjach sprzętowych i we wszystkich dostępnych usługach chmurowych.

Klastry wykorzystujące deckhouse mają wszystkie niezbędne komponenty do pracy: load balancery, monitoring z wygodnymi wykresami, metrykami i alertami, autoryzację użytkowników przez zewnętrznych dostawców do dostępu do wszystkich dashboardów itd. Taki „ulepszony” klaster nie ma sensu w rozwiązaniach zarządzanych, ponieważ często jest to niemożliwe lub prowadzi do konieczności wyłączenia połowy komponentów.

NB: To nasze doświadczenie, które jest dość specyficzne. Nie twierdzimy w żadnym wypadku, że każdy powinien samodzielnie zajmować się uruchamianiem klastrów Kubernetes zamiast korzystać z gotowych rozwiązań. Co więcej, nie mamy rzeczywistego doświadczenia w korzystaniu z Kubernetes oferowanego przez Yandex i nie zamierzamy oceniać tej usługi w tym artykule.

Czym to jest i dla kogo?

Zatem, już opowiadaliśmy o nowoczesnym podejściu do przechowywania w Kubernetes: jak działa CSI i jak społeczność doszła do takiego podejścia.

Obecnie wielu dużych dostawców usług chmurowych opracowało sterowniki do używania swoich „chmurowych” dysków jako Persistent Volume w Kubernetes. Jeżeli jednak dostawca nie ma takiego sterownika, ale wszystkie niezbędne funkcje są dostępne przez API, nic nie stoi na przeszkodzie, by zrealizować sterownik samodzielnie. Tak właśnie zrobiliśmy z Yandex.Cloud.

Jako bazę do rozwoju wzięliśmy sterownik CSI dla chmury DigitalOcean i kilka pomysłów z sterownika dla GCP, ponieważ interakcja z API tych chmur (Google i Yandex) ma wiele podobieństw. W szczególności API zarówno GCP, jak i Yandex zwracają obiekt Operacja do śledzenia statusu długotrwałych operacji (np. tworzenia nowego dysku). Aby komunikować się z API Yandex.Cloud, używa się Yandex.Cloud Go SDK.

Wynik wykonanej pracy opublikowany na GitHubie i może być przydatny dla tych, którzy z jakiegoś powodu używają własnej instalacji Kubernetes na maszynach wirtualnych Yandex.Cloud (ale nie gotowego zarządzanego klastra) i chcieliby korzystać z (zamawiać) dysków przez CSI.

Realizacja

Główne możliwości

W tej chwili sterownik wspiera następujące funkcje:

  • Zamawianie dysków we wszystkich strefach klastra zgodnie z topologią dostępnych w klastrze węzłów;
  • Usuwanie wcześniej zamówionych dysków;
  • Offline resize dla dysków (Yandex.Cloud nie obsługuje powiększanie dysków, które są zamontowane na maszynie wirtualnej). O tym, jak musiałem przerabiać sterownik, aby jak najmniej boleśnie przeprowadzać resize, patrz poniżej.

W przyszłości planowane jest wprowadzenie wsparcia dla tworzenia i usuwania migawek dysków.

Główna trudność i jej pokonanie

Brak w API Yandex.Cloud możliwości zwiększania dysków w czasie rzeczywistym — ograniczenie, które utrudnia operację resize'u dla PV (Persistent Volume): w takim przypadku konieczne jest, aby pod aplikacji, który używa dysku, był zatrzymany, co może spowodować przestój aplikacji.

Zgodnie z specyfikacji CSI, jeśli kontroler CSI informuje, że potrafi wykonywać resize dysków tylko „w offline” (VolumeExpansion.OFFLINE), to proces zwiększania dysku musi przebiegać w ten sposób:

Jeśli wtyczka ma tylko VolumeExpansion.OFFLINE zdolność rozszerzenia i wolumen jest obecnie opublikowany lub dostępny na węźle, to ControllerExpandVolume MUSI być wywołany TYLKO po tym, jak:

  • Wtyczka ma kontroler PUBLISH_UNPUBLISH_VOLUME zdolność i ControllerUnpublishVolume został pomyślnie wywołany.

LUB

  • Wtyczka NIE ma zdolności kontrolera PUBLISH_UNPUBLISH_VOLUME , wtyczka ma węzeł STAGE_UNSTAGE_VOLUME zdolność i NodeUnstageVolume został pomyślnie zakończony.

LUB

  • Wtyczka NIE ma zdolności kontrolera PUBLISH_UNPUBLISH_VOLUME zdolność, ani węzeł STAGE_UNSTAGE_VOLUME zdolność i NodeUnpublishVolume nie został pomyślnie zakończony.

W zasadzie oznacza to konieczność odłączenia dysku od maszyny wirtualnej przed jego zwiększeniem.

Niestety, realizacja specyfikacje CSI poprzez sidecary nie spełniają tych wymagań:

  • W kontenerze sidecar csi-attacher, który ma odpowiadać za zapewnienie odpowiedniej przerwy pomiędzy zamontowaniami, przy offline-resize po prostu nie zrealizowano tej funkcji. Dyskusję na ten temat zainicjowano tutaj.
  • Czym właściwie jest kontener sidecar w tym kontekście? Sam wtyczka CSI nie obsługuje interakcji z API Kubernetes, a jedynie reaguje na wywołania gRPC, które wysyłają mu kontenery sidecar. Ostatnie opracowywane są społeczność Kubernetes.

W naszym przypadku (wtyczka CSI) operacja zwiększenia dysku wygląda następująco:

  1. Otrzymujemy wywołanie gRPC ControllerExpandVolume;
  2. Próbujemy zwiększyć dysk w API, ale otrzymujemy błąd o niemożności wykonania operacji, ponieważ dysk jest zamontowany;
  3. Zapisujemy identyfikator dysku w mapie, która zawiera dyski, dla których należy wykonać operację zwiększenia. Dalej dla skrótu będziemy nazywać tę mapę volumeResizeRequired;
  4. Ręcznie usuwamy pod, który korzysta z dysku. Kubernetes w tym czasie go zrestartuje. Aby dysk nie zdążył się zamontować (ControllerPublishVolume) przed zakończeniem operacji zwiększenia podczas próby montowania, sprawdzamy, że dany dysk wciąż znajduje się w volumeResizeRequired i zwracamy błąd;
  5. Sterownik CSI próbuje ponownie wykonać operację resize. Jeśli operacja zakończyła się sukcesem, usuwamy dysk z volumeResizeRequired;
  6. Ponieważ identyfikator dysku nie występuje w volumeResizeRequired, ControllerPublishVolume operacja przebiega pomyślnie, dysk jest montowany, pod zostaje uruchomiony.

Wszystko wygląda dość prosto, ale jak zawsze są pewne pułapki. Zwiększeniem dysków zajmuje się external-resizer, który w przypadku błędu podczas wykonywania operacji wykorzystuje kolejkę z wykładniczym zwiększeniem czasu oczekiwania do 1000 sekund:

func DefaultControllerRateLimiter() RateLimiter {
  return NewMaxOfRateLimiter(
  NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
  	// 10 qps, 100 wielkość wiadra. To tylko dla prędkości powtórzeń i to tylko ogólny czynnik (nie dla każdego elementu)
  &BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
  )
}

Może to okresowo powodować, że operacja zwiększenia dysku rozciąga się na ponad 15 minut, a tym samym dysk staje się niedostępny.

Jedyną opcją, która wystarczająco łatwo i bez bólu pozwoliła nam zredukować potencjalny czas przestoju, było użycie własnej wersji external-resizer z maksymalnym ograniczeniem czasu oczekiwania do 5 sekund:

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

Nie uznaliśmy za konieczne pilnie podejmować dyskusji i łatkać external-resizer, ponieważ offline resize dysków to relikt, który wkrótce wyginie u wszystkich dostawców chmurowych.

Jak zacząć korzystać?

Sterownik jest wspierany w wersji Kubernetes 1.15 i nowszych. Aby sterownik działał, muszą być spełnione następujące wymagania:

  • Flaga --allow-privileged ustawione na wartość true dla API serwera i kubelet;
  • Włączone --feature-gates=VolumeSnapshotDataSource=true,KubeletPluginsWatcher=true,CSINodeInfo=true,CSIDriverRegistry=true dla API serwera i kubelet;
  • Rozprzestrzenienie montowania (rozprzestrzenienie montowania) musi być włączone w klastrze. Przy użyciu Dockera demon musi być skonfigurowany tak, aby umożliwiał współdzielone obiekty montowania (shared mounts).

Wszystkie niezbędne kroki dotyczące samej instalacji są opisane w README. Instalacja polega na tworzeniu obiektów w Kubernetes z manifestów.

Aby używać sterownika, będziesz potrzebować następujących:

  • Określ w manifeście identyfikator katalogu (folder-id) Yandex.Cloud (zobacz dokumentację);
  • Do interakcji z API Yandex.Cloud w sterowniku CSI używane jest konto serwisowe. W manifeście Secret należy przekazać autoryzowane klucze z konta serwisowego. W dokumentacji opisano, jak utworzyć konto serwisowe i uzyskać klucze.

Ogólnie — spróbuj, a będziemy wdzięczni za wszelkie opinie i nowe problemy, jeśli napotkasz jakieś trudności!

Dalsze wsparcie

Na zakończenie chcielibyśmy zaznaczyć, że ten sterownik CSI realizowaliśmy nie z powodu chęci zabawy w pisanie aplikacji w Go, ale z pilnej potrzeby wewnątrz firmy. Nie uważamy, że utrzymywanie własnej realizacji ma sens, dlatego, jeśli Yandex wykaże zainteresowanie i zdecyduje się na dalsze wsparcie sterownika, z przyjemnością przekażemy repozytorium do ich dyspozycji.

Ponadto, być może Yandex ma własną realizację sterownika CSI w zarządzanym klastrze Kubernetes, którą można wydać jako Open Source. Taki rozwój dla nas również wydaje się korzystny — społeczność mogłaby korzystać z sprawdzonego sterownika od dostawcy usług, a nie od zewnętrznej firmy.

P.S.

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup niezawodny hosting stron z ochroną DDoS, serwery VPS VDS 🔥 Kup niezawodny hosting stron z ochroną DDoS, serwery VPS VDS - ProHoster