Migracja Cassandry w Kubernetes: cechy i rozwiązania

Migracja Cassandry w Kubernetes: cechy i rozwiązania

Z bazą danych Apache Cassandra i koniecznością jej eksploatacji w ramach infrastruktury opartej na Kubernetes spotykamy się regularnie. W tym materiale podzielimy się naszym spojrzeniem na niezbędne kroki, kryteria oraz istniejące rozwiązania (w tym przegląd operatorów) związane z migracją Cassandry do K8s.

„Kto może zarządzać kobietą, poradzi sobie i z państwem”

Kim właściwie jest Cassandra? To rozproszony system zarządzania danymi, zaprojektowany do obsługi dużych ilości danych, zapewniając jednocześnie wysoką dostępność bez pojedynczego punktu awarii. Projekt prawdopodobnie nie wymaga długiego przedstawiania, dlatego przedstawię tylko główne cechy Cassandry, które będą istotne w kontekście tego artykułu:

  • Cassandra jest napisana w Java.
  • Topologia Cassandry obejmuje kilka poziomów:
    • Węzeł — jeden uruchomiony egzemplarz Cassandry;
    • Rack — grupa egzemplarzy Cassandry, połączonych według określonego kryterium, znajdująca się w jednym centrum danych;
    • Centrum danych — zbiorowość wszystkich grup egzemplarzy Cassandry, znajdujących się w jednym centrum danych;
    • Klaster — zbiorowość wszystkich centrów danych.
  • Aby zidentyfikować węzeł, Cassandra używa adresu IP.
  • Dla szybkości operacji zapisu i odczytu część danych Cassandra przechowuje w pamięci operacyjnej.

Teraz przejdźmy do potencjalnej migracji do Kubernetes.

Lista kontrolna do przeniesienia

Mówiąc o migracji Cassandry do Kubernetes, mamy nadzieję, że z nową lokalizacją zarządzanie nią stanie się łatwiejsze. Czego potrzebujemy, co nam w tym pomoże?

1. Przechowywanie danych

Jak już wspomniano, część danych Cassandry przechowuje w pamięci operacyjnej — w Memtable. Ale jest też inna część danych, która jest zapisywana na dysku — w postaci SSTable. Do tych danych dodawana jest jednostka Commit Log — zapisy dotyczące wszystkich transakcji, które także są zapisywane na dysku.

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Schemat transakcji zapisu w Cassandrze

W Kubernetes możemy używać PersistentVolume do przechowywania danych. Dzięki sprawdzonym mechanizmom, praca z danymi w Kubernetes staje się z każdym rokiem coraz prostsza.

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Każdemu podowi z Cassandrą przydzielimy własny PersistentVolume.

Warto zauważyć, że Cassandra sama w sobie zakłada replikację danych, oferując w tym celu wbudowane mechanizmy. Dlatego, jeśli tworzysz klaster Cassandry z dużą liczbą węzłów, nie ma potrzeby korzystania z rozproszonych systemów przechowywania danych, takich jak Ceph czy GlusterFS. W takim przypadku logiczne będzie przechowywanie danych na dysku węzła za pomocą lokalnych dysków persystentnych lub montowania hostPath.

Inna kwestia, jeśli chcesz tworzyć osobne środowisko dla każdego feature branch dla deweloperów. W takim przypadku właściwym podejściem będzie uruchomienie jednego węzła Cassandry i przechowywanie danych w rozproszonym magazynie, tj. wspomniane Ceph i GlusterFS będą Twoją opcją. Wówczas deweloper będzie pewny, że nie straci danych testowych, nawet w przypadku utraty jednego z węzłów klastra Kubernetesa.

2. Monitorowanie

Praktycznie bezalternatywnym wyborem dla realizacji monitorowania w Kubernetesie jest Prometheus (szczegółowo opisaliśmy to w odpowiedniej prezentacji). Jak wygląda współpraca Cassandry z eksporterami metryk dla Prometheusa? I, co w niektórych przypadkach może być ważniejsze, z odpowiednimi dashboardami dla Grafany?

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Przykład wyglądu wykresów w Grafanie dla Cassandry

Jest tylko dwóch eksporterów: jmx_exporter i cassandra_exporter.

Wybraliśmy pierwszy, ponieważ:

  1. JMX Exporter rozwija się i ewoluuje, podczas gdy Cassandra Exporter nie uzyskał odpowiedniego wsparcia społeczności. Cassandra Exporter wciąż nie wspiera większości wersji Cassandry.
  2. Można go uruchomić jako javaagent, dodając flagę -javaagent:/cassandra-exporter.jar=--listen=:9180.
  3. Dla niego istnieje odpowiedni dashboard, który nie jest kompatybilny z Cassandra Exporter.

3. Wybór prymitywów Kubernetes

Zgodnie z powyżej przedstawioną strukturą klastra Cassandry spróbujmy przetłumaczyć wszystko, co tam opisano, na terminologię Kubernetes:

  • Cassandra Node → Pod
  • Cassandra Rack → StatefulSet
  • Cassandra Datacenter → pula StatefulSets
  • Cassandra Cluster → ???

Okazuje się, że brakuje jakiegoś dodatkowego bytu, aby zarządzać całym klastrem Cassandry jednocześnie. Ale jeśli czegoś brakuje, możemy to stworzyć! W Kubernetesie do tego służy mechanizm definiowania własnych zasobów — Custom Resource Definitions.

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Ogłoszenie dodatkowych zasobów dla logów i powiadomień

Ale sam w sobie Custom Resource nic nie znaczy: potrzebny jest dla niego kontroler. Możliwe, że będziemy musieli skorzystać z pomocy operatora Kubernetes

4. Identyfikacja podów

W punkcie powyżej uzgodniliśmy, że jeden węzeł Cassandra będzie odpowiadał jednemu podowi w Kubernetes. Jednak adresy IP podów za każdym razem będą różne. Identyfikacja węzła w Cassandra odbywa się właśnie na podstawie adresu IP... Oznacza to, że po każdym usunięciu podu klaster Cassandra doda nowy węzeł.

Są różne rozwiązania:

  1. Możemy prowadzić ewidencję według identyfikatorów hostów (UUID, które jednoznacznie identyfikują instancje Cassandra) albo według adresów IP, zachowując to wszystko w odpowiednich strukturach/tabelach. Metoda ta ma dwie główne wady:
    • Ryzyko wystąpienia warunku wyścigu podczas awarii dwóch węzłów jednocześnie. Po wznowieniu oba węzły Cassandra zaczynają jednocześnie żądać adresu IP z tabeli i konkurować o ten sam zasób.
    • Jeżeli węzeł Cassandra utraci swoje dane, nie będziemy mogli go zidentyfikować.
  2. Drugie rozwiązanie wydaje się prostym hackiem, jednak można je zastosować: możemy tworzyć Service z ClusterIP dla każdego węzła Cassandra. Problemy tej implementacji:
    • Jeśli w klastrze Cassandra jest zbyt wiele węzłów, będziemy musieli stworzyć wiele Service’ów.
    • Możliwość ClusterIP jest realizowana przez iptables. Może to stać się problemem, jeśli w klastrze Cassandra jest dużo (1000… a może nawet 100?) węzłów. Chociaż load balancing oparty na IPVS może rozwiązać ten problem.
  3. Trzecie rozwiązanie - wykorzystanie sieci węzłów Cassandra zamiast dedykowanej sieci podów poprzez włączenie ustawienia hostNetwork: true. Ta metoda nakłada pewne ograniczenia:
    • Na zastępowanie węzłów. Nowy węzeł musi mieć ten sam adres IP co poprzedni (w chmurach takich jak AWS, GCP jest to praktycznie niemożliwe);
    • Korzyując z sieci węzłów klastra, zaczynamy konkurować o zasoby sieciowe. W związku z tym umieszczenie na jednym węźle klastra więcej niż jednego podu Cassandra będzie problematyczne.

5. Kopie zapasowe

Chcemy regularnie zachowywać pełną wersję danych jednego węzła Cassandra. Kubernetes oferuje wygodną możliwość użycia CronJob, ale sama Cassandra wprowadza pewne ograniczenia.

Przypomnę, że część danych Cassandra przechowuje w pamięci. Aby wykonać pełną kopię zapasową, należy przenieść dane z pamięci (Memtables) na dysk (SSTables). W tym momencie węzeł Cassandra przestaje przyjmować połączenia, całkowicie wyłączając się z pracy klastra.

Następnie wykonuje się kopię zapasową (snapshot) i zachowuje schemat (keyspace). I tutaj okazuje się, że sama kopia zapasowa nam nic nie daje: musimy zachować identyfikatory danych, za które odpowiadał węzeł Cassandra — to są specjalne tokeny.

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Rozdzielenie tokenów do identyfikacji, za jakie dane odpowiadają węzły Cassandra

Przykład skryptu do wykonania kopii zapasowej Cassandra od Google w Kubernetes można znaleźć pod tym linkiem. Jedyny moment, którego skrypt nie uwzględnia, to reset danych na węźle przed wykonaniem snapshotu. Oznacza to, że kopia zapasowa jest wykonywana nie dla bieżącego stanu, ale stanu sprzed chwili. Jednak to pozwala nie wyłączać węzła z pracy, co wydaje się bardzo logiczne.

set -eu

if [[ -z "$1" ]]; then
  info "Proszę podać keyspace"
  exit 1
fi

KEYSPACE="$1"

result=$(nodetool snapshot "${KEYSPACE}")

if [[ $? -ne 0 ]]; then
  echo "Błąd podczas tworzenia snapshotu"
  exit 1
fi

timestamp=$(echo "$result" | awk '\/Snapshot directory: \/ { print $3 }')

mkdir -p \/tmp\/backup

for path in $(find "\/var\/lib\/cassandra\/data\/${KEYSPACE}" -name $timestamp); do
  table=$(echo "${path}" | awk -F "[\/-]" '{print $7}')
  mkdir \/tmp\/backup\/$table
  mv $path \/tmp\/backup\/$table
done


tar -zcf \/tmp\/backup.tar.gz -C \/tmp\/backup .

nodetool clearsnapshot "${KEYSPACE}"

Przykład skryptu bash do wykonania kopii zapasowej z jednego węzła Cassandra

Gotowe rozwiązania dla Cassandra w Kubernetes

Czego w ogóle używa się teraz do wdrożenia Cassandra w Kubernetes i co z tego najbardziej odpowiada zadanym wymaganiom?

1. Rozwiązania oparte na StatefulSet lub wykresach Helm

Użycie podstawowych funkcji StatefulSets do uruchomienia klastra Cassandra to dobry wybór. Przy użyciu wykresu Helm i szablonów Go można dostarczyć użytkownikowi elastyczny interfejs do wdrożenia Cassandra.

Zazwyczaj działa to dobrze… dopóki coś nie pójdzie nie tak — na przykład awaria węzła. Standardowe narzędzia Kubernetes po prostu nie mogą uwzględnić wszystkich wyżej opisanych aspektów. Ponadto, to podejście jest bardzo ograniczone w tym, jak może być rozwijane dla bardziej skomplikowanego użycia: wymiany węzłów, tworzenia kopii zapasowych, przywracania, monitorowania itp.

Reprezentanci:

Oba wykresy są równie dobre, ale są podatne na opisane powyżej problemy.

2. Rozwiązania oparte na operatorze Kubernetes

Takie opcje są bardziej interesujące, ponieważ oferują szerokie możliwości zarządzania klastrem. Dla zaprojektowania operatora Cassandra, jak i każdej innej bazy danych, dobry wzór wygląda jak Sidecar Controller CRD:

Migracja Cassandry w Kubernetes: cechy i rozwiązania
Schemat zarządzania węzłami w dobrze zaprojektowanym operatorze Cassandra

Rozważmy istniejące operatory.

1. Cassandra-operator od instaclustr

  • GitHub
  • Gotowość: Alpha
  • Licencja: Apache 2.0
  • Zrealizowany w: Java

To naprawdę obiecujący i aktywnie rozwijający się projekt firmy, która oferuje zarządzane wdrożenia Cassandra. Jak opisano powyżej, wykorzystuje sidecar-kontener, który przyjmuje polecenia za pośrednictwem HTTP. Napisany w Javie, dlatego czasami brakuje mu bardziej zaawansowanej funkcjonalności biblioteki client-go. Operator nie obsługuje również różnych Racków dla jednego Data Center.

Zalety operatora to wsparcie monitoringu, zaawansowane zarządzanie klastrem za pomocą CRD oraz dokumentacja dotycząca tworzenia kopii zapasowych.

2. Navigator od Jetstack

  • GitHub
  • Gotowość: Alpha
  • Licencja: Apache 2.0
  • Zrealizowany w: Golang

Operator zaprojektowany do wdrażania DB-as-a-Service. Na chwilę obecną obsługuje dwie bazy danych: Elasticsearch i Cassandra. Posiada ciekawe rozwiązania, takie jak kontrola dostępu do bazy danych przez RBAC (do tego uruchamiany jest oddzielny navigator-apiserver). Interesujący projekt, któremu warto się przyjrzeć, chociaż ostatni commit został wykonany półtora roku temu, co wyraźnie obniża jego potencjał.

3. Cassandra-operator od vgkowski

  • GitHub
  • Gotowość: Alpha
  • Licencja: Apache 2.0
  • Zrealizowany w: Golang

Nie zdecydowano się na jego poważne rozważenie, ponieważ ostatni commit w repozytorium miał miejsce ponad rok temu. Rozwój operatora został porzucony: ostatnia wersja Kubernetes, która była zgłaszana jako wspierana, to 1.9.

4. Cassandra-operator od Rook

  • GitHub
  • Gotowość: Alpha
  • Licencja: Apache 2.0
  • Zrealizowany w: Golang

Operator, którego rozwój nie postępuje tak szybko, jakbyśmy tego chcieli. Posiada przemyślaną strukturę CRD do zarządzania klastrem, rozwiązuje problem identyfikacji węzłów za pomocą Service z ClusterIP (ten słynny 'hack')… ale na razie to wszystko. Obecnie nie ma monitoringu ani kopii zapasowych (przy okazji, zajęliśmy się monitoringiem sami). Zajęliśmy się sami). Interesujący fakt, że przy użyciu tego operatora można również uruchomić ScyllaDB.

NB: Użyliśmy tego operatora z drobnymi poprawkami w jednym z naszych projektów. Nie zauważono żadnych problemów z działaniem operatora podczas całego okresu eksploatacji (~4 miesiące).

5. CassKop od Orange

  • GitHub
  • Gotowość: Alpha
  • Licencja: Apache 2.0
  • Zrealizowany w: Golang

Najmłodszy operator na liście: pierwszy commit został wykonany 23 maja 2019 roku. Już teraz ma w swoim arsenale dużą ilość funkcji z naszej listy, z którymi można się zapoznać w repozytorium projektu. Operator oparty na popularnym operator-sdk. Obsługuje monitoring 'z pudełka'. Główną różnicą w porównaniu do innych operatorów jest wykorzystanie wtyczki CassKop, zaimplementowanej w Pythonie i używanej do komunikacji między węzłami Cassandry.

Wnioski

Liczba podejść i możliwych sposobów przeniesienia Cassandry do Kubernetes sama mówi za siebie: temat jest poszukiwany.

Na tym etapie można spróbować czegoś z powyższego na własne ryzyko: żaden z deweloperów nie gwarantuje 100% działania swojego rozwiązania w środowisku produkcyjnym. Jednak już teraz wiele produktów wygląda obiecująco, aby spróbować wykorzystać je w środowiskach deweloperskich.

Myślę, że w przyszłości ta kobieta na statku będzie na miejscu!

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