
Referat dotyczy praktycznych kwestii związanych z rozwojem operatora w Kubernetes, projektowaniem jego architektury oraz podstawowych zasad funkcjonowania.
W pierwszej części referatu omówimy:
- czym jest operator w Kubernetes i dlaczego jest potrzebny;
- w jaki sposób operator upraszcza zarządzanie złożonymi systemami;
- co operator może zrobić, a co jest poza jego zakresem.
Następnie przejdziemy do omówienia wewnętrznej struktury operatora. Przeanalizujemy architekturę i działanie operatora krok po kroku. Szczegółowo omówimy:
- interakcję między operatorem a Kubernetes;
- jakie funkcje operator przejmuje na siebie, a co deleguje do Kubernetes.
Przyjrzymy się zarządzaniu shardami i replikami bazy danych w Kubernetes.
Następnie omówimy kwestie przechowywania danych:
- jak pracować z Persistent Storage z perspektywy operatora;
- pułapki związane z używaniem Local Storage.
W końcowej części referatu przedstawimy praktyczne przykłady zastosowania w Amazon lub Google Cloud Service. Referat oparty jest na przykładzie rozwoju i doświadczeń z eksploatacji operatora dla ClickHouse.
Wideo:

Nazywam się Władysław Klimeńko. Dziś chciałem opowiedzieć o naszym doświadczeniu w zakresie rozwoju i eksploatacji operatora, będącego specjalizowanym operatorem do zarządzania klastrami baz danych. Na przykładzie do zarządzania klastrem ClickHouse.

Dlaczego mamy prawo mówić o operatorze i ClickHouse?
- Zajmujemy się wsparciem i rozwojem ClickHouse.
- Obecnie staramy się stopniowo wnosić nasz skromny wkład w rozwój ClickHouse. Jesteśmy drugą firmą po Yandexie pod względem liczby wprowadzonych zmian w ClickHouse.
- Staraliśmy się realizować dodatkowe projekty dla ekosystemu ClickHouse.
O jednym z takich projektów chciałem opowiedzieć. Dotyczy on ClickHouse-operatora dla Kubernetes.
W moim referacie chciałbym poruszyć dwie tematy:
- Pierwszy temat – to jak działa nasz operator do zarządzania bazami danych ClickHouse w Kubernetes.
- Drugi temat – jak działa dowolny operator, tzn. jak współdziała z Kubernetes.
Te dwa zagadnienia będą się przenikać przez cały mój referat.

Kto może być zainteresowany tym, co próbuję przedstawić?
- Najbardziej zainteresowane będą osoby eksploatujące operatorów.
- Lub ci, którzy chcą stworzyć własnego, aby zrozumieć, jak to działa w środku, jak operator współdziała z Kubernetes i jakie pułapki mogą się pojawić.

Aby lepiej zrozumieć to, o czym dzisiaj będziemy rozmawiać, warto wiedzieć, jak działa Kubernetes i mieć podstawową wiedzę na temat technologii chmurowych.

Czym jest ClickHouse? To kolumnowa baza danych, skoncentrowana na online'owym przetwarzaniu zapytań analitycznych. Jest całkowicie open source.
Musimy wiedzieć tylko dwie rzeczy. Po pierwsze, jest to baza danych, więc to, o czym będę mówić, dotyczy w zasadzie każdej bazy danych. Po drugie, system zarządzania bazami danych ClickHouse skaluję bardzo dobrze, prawie w sposób liniowy. Dlatego stan klastra jest naturalnym stanem dla ClickHouse. Najbardziej interesuje nas omówienie, jak zarządzać klastrem ClickHouse w Kubernetes.

Po co jest tam potrzebny? Dlaczego nie możemy kontynuować jego eksploatacji samodzielnie? Odpowiedzi są częściowo techniczne, a częściowo organizacyjne.
- W praktyce coraz częściej spotykamy sytuację, w której w dużych firmach praktycznie wszystkie komponenty są już w Kubernetes. Bazy danych pozostają poza nim.
- Coraz częściej pojawia się pytanie: „Czy można to umieścić wewnątrz?”. Dlatego duże firmy starają się maksymalnie ujednolicić zarządzanie, aby szybko móc zarządzać swoimi magazynami danych.
- Pomaga to szczególnie, gdy potrzebna jest maksymalna zdolność do powtórzenia tego samego w nowym miejscu, tzn. maksymalna przenośność.

Jak trudne lub łatwe to jest? Można to oczywiście robić ręcznie. Ale nie jest to takie proste, ponieważ mamy dodatkową złożoność zarządzania samym Kubernetes, a także specyfikę ClickHouse. Powstaje agregacja.
Wszystko razem daje dość dużą liczbę technologii, którymi zarządzanie staje się dość skomplikowane, ponieważ Kubernetes wprowadza codzienne pytania dotyczące eksploatacji, a ClickHouse wprowadza własne pytania związane z codzienną eksploatacją. Szczególnie jeśli mamy kilka instancji ClickHouse i musimy ciągle coś z nimi robić.

ClickHouse w przypadku dynamicznej konfiguracji ma liczne pytania, które stają się stałym obciążeniem dla DevOps:
- Kiedy chcemy coś zmienić w ClickHouse, na przykład dodać replikę lub shard, musimy przeprowadzić zarządzanie konfiguracją.
- Następnie należy zmienić schemat danych, ponieważ ClickHouse ma specyficzny sposób shardowania. Należy rozłożyć schemat danych oraz skonfigurować go.
- Należy skonfigurować monitorowanie.
- Zbieranie logów dla nowych shardów, dla nowych replik.
- Zająć się przywracaniem.
- I ponownym uruchomieniem.
To są rutynowe prace, które bardzo chciałoby się uprościć w eksploatacji.

Sam Kubernetes dobrze wspomaga eksploatację, ale w podstawowych systemowych kwestiach.
Kubernetes dobrze upraszcza i automatyzuje takie rzeczy, jak:
- Przywracanie.
- Ponowne uruchomienie.
- Zarządzanie systemem pamięci.
To dobre i właściwy kierunek, ale całkowicie nie ma pojęcia o tym, jak eksploatować klaster bazy danych.
Chciałoby się więcej, chciałoby się, aby cała baza danych działała w Kubernetes.

Chciałoby się mieć coś w rodzaju dużego, magicznego czerwonego przycisku, na który się naciska i rozwija oraz utrzymuje przez cały cykl życia klaster z codziennymi zadaniami do rozwiązania. Klaster ClickHouse w Kubernetes.
I staraliśmy się stworzyć takie rozwiązanie, które miałoby ułatwić pracę. To ClickHouse-operator dla Kubernetes od firmy Altinity.

Operator to program, którego głównym zadaniem jest zarządzanie innymi programami, czyli jest to menedżer.
Zawiera on wzorce zachowań. Można to nazwać skodyfikowaną wiedzą o danej dziedzinie.
A jego głównym zadaniem jest ułatwienie życia DevOps i zmniejszenie mikrozarządzania, aby myślał on w kategoriach wysokiego poziomu, czyli aby nie zajmował się mikrozarządzaniem i nie konfigurował wszystkich szczegółów ręcznie.
Operator to robot asystent, który walczy z mikrozadaniami i wspiera DevOps.

Po co potrzebny jest operator? Sprawdza się on szczególnie dobrze w dwóch kwestiach:
- Kiedy specjalista zajmujący się ClickHouse'a ma niewystarczające doświadczenie, ale już musi eksploatować ClickHouse, operator ułatwia eksploatację i pozwala zarządzać klastrem ClickHouse z dość skomplikowaną konfiguracją, nie wnikając w szczegóły, jak to wszystko działa wewnątrz. Po prostu otrzymuje wysokopoziomowe zadania i to działa.
- A drugą zadaniem, w którym on najlepiej się sprawdza, jest automatyzacja dużej liczby typowych zadań. Zdejmuje mikro-zadania z administracji systemu.

To jest szczególnie potrzebne dla tych, którzy dopiero zaczynają swoją drogę lub dla tych, którzy muszą zajmować się dużą automatyzacją.

Jaka jest więc różnica w podejściu opartym na operatorach a innymi systemami? Przecież jest Helm. On również pomaga zainstalować ClickHouse, można narysować wykresy helm, które nawet zainstalują cały klaster ClickHouse. Jakie jest w takim razie różnica między operatorem a na przykład Helm?
Podstawowa fundamentalna różnica polega na tym, że Helm to zarządzanie pakietami, a operator idzie dalej. To wsparcie całego cyklu życia. To nie tylko instalacja, to codzienne zadania, które obejmują skalowanie, sharding, czyli wszystko, co trzeba wykonać w trakcie cyklu życia (jeśli zajdzie taka potrzeba, to i usunięcie) – to wszystko rozwiązuje operator. Dąży do automatyzacji i zarządzania całym cyklem życia oprogramowania. To jest jego fundamentalna różnica w porównaniu do innych dostępnych rozwiązań.

To była część wprowadzająca, przejdźmy dalej.
Jak budujemy swojego operatora? Staramy się podejść do kwestii, aby zarządzać klastrem ClickHouse jako jednym zasobem.
Po lewej stronie schematu mamy dane wejściowe. To YAML ze specyfikacją klastra, który w klasyczny sposób jest przesyłany do Kubernetes przez kubectl. Operator przechwytuje to, wykonuje swoją magię. A na wyjściu otrzymujemy taki schemat. To implementacja ClickHouse w Kubernetes.
I dalej będziemy powoli przyglądać się, jak dokładnie działa operator, jakie typowe zadania można rozwiązać. Rozważymy tylko typowe zadania, ponieważ mamy ograniczony czas. I nie będzie opowiedziane o wszystkim, co może rozwiązać operator.

Opartym na praktyce. Nasz projekt jest całkowicie open source, więc można zobaczyć, jak działa na GitHubie. Jeśli chce się po prostu uruchomić, można zaczynać z Quick Start Guide.
Jeśli chcesz dokładnie zrozumieć, staramy się utrzymywać dokumentację w dość przyzwoitym stanie.

Zacznijmy od praktycznego zadania. Pierwszym krokiem, od którego wszyscy chcemy zacząć, jest uruchomienie pierwszego przykładu w jakikolwiek sposób. Jak uruchomić ClickHouse za pomocą operatora, nawet nie bardzo wiedząc, jak on działa? Piszemy manifest, ponieważ całe komunikowanie się z k8s odbywa się za pośrednictwem manifestów.

Oto tak złożony manifest. To, co mamy zaznaczone na czerwono, to na czym musimy się skupić. Prosimy operatora o utworzenie klastra o nazwie demo.
Na razie to podstawowe przykłady. Przechowywanie jeszcze nie jest opisane, ale do tematu przechowywania wrócimy nieco później. Na razie będziemy na bieżąco śledzić rozwój klastra.
Utworzyliśmy ten manifest. Podajemy go naszemu operatorowi. On popracował, zrobił magię.

Patrzymy na konsolę. Interesują nas trzy komponenty – to Pod, dwa Serwisy i StatefulSet.
Operator zakończył pracę i możemy zobaczyć, co dokładnie stworzył.

Tworzy on mniej więcej taką schemę. Mamy StatefulSet, Pod, ConfigMap dla każdej repliki, ConfigMap dla całego klastra. Niezbędne są również serwisy jako punkty wejścia do klastra.
Serwisy – to centralny Load Balancer Service, a także można stworzyć dla każdej repliki, dla każdego sharda.
Nasz podstawowy klaster wygląda mniej więcej w ten sposób. Składa się z jednej jedynej nody.

Idźmy dalej, będziemy to komplikuować. Musimy shardować klaster.

Nasze zadania rosną, zaczyna się dynamika. Chcemy dodać shard. Obserwujemy rozwój. Zmieniamy naszą specyfikację. Wskazujemy, że chcemy dwa shardy.
To ten sam plik, który dynamicznie rozwija się wraz z rozwojem systemu. Przechowywanie nie jest jeszcze uwzględnione, przechowywanie zostanie omówione później, to osobny temat.
Podajemy YAML-operatorowi i sprawdzamy, co się dzieje.

Operator pomyślał i stworzył następujące byty. Mamy już dwa Pod, trzy Serwisy i niespodziewanie, 2 StatefulSet-y. Dlaczego 2 StatefulSet-y?

Na schemacie wyglądało to tak – to nasz stan początkowy, kiedy mieliśmy jeden pod.

Teraz wygląda to tak. Na razie wszystko jest proste, zostało to skopiowane.

I dlaczego StatefulSet stał się dwa? Tutaj musimy się oderwać i omówić, jak w Kubernetes odbywa się zarządzanie Pod’ami.
Istnieje taki obiekt o nazwie StatefulSet, który pozwala tworzyć zestaw Pod’ów z szablonu. Kluczem jest Template. Można w jednym StatefulSet uruchamiać wiele Pod’ów z jednego szablonu. Kluczową frazą jest tutaj „wiele Pod’ów z jednego szablonu”.
Istniała pokusa, aby stworzyć cały klaster, pakując go w jeden StatefulSet. To będzie działać, nie ma co do tego wątpliwości. Ale jest jeden szczegół. Jeśli chcemy zbudować heterogeniczny klaster, tzn. z kilku wersji ClickHouse, to zaczynają się pytania. Tak, StatefulSet może przeprowadzić rolling update, można tam wprowadzać nową wersję, tłumacząc, że należy spróbować nie więcej niż tyle nodów jednocześnie.
Jednak jeśli ekstrapolować zadanie i powiedzieć, że chcemy stworzyć całkowicie heterogeniczny klaster i chcemy nie zmieniać z wersji starej na nową za pomocą rolling update, a po prostu chcemy stworzyć klaster heterogeniczny, zarówno w kwestii różnych wersji ClickHouse, jak i różnego przechowywania. Chcemy na przykład, aby niektóre repliki znajdowały się na oddzielnych dyskach, na wolnych, generalnie całkowicie zbudować heterogeniczny klaster. I z powodu tego, że StatefulSet tworzy ustandaryzowane rozwiązanie z jednego szablonu, nie ma możliwości tego zrobić.
Po pewnym namyśle podjęto decyzję, że zrobimy to w ten sposób. Każda replika znajduje się w swoim własnym StatefulSet. To rozwiązanie ma pewne wady, ale w praktyce w pełni inkapsuluje to operator. I jest wiele zalet. Możemy budować dokładnie taki klaster, jaki chcemy, na przykład całkowicie heterogeniczny. Dlatego w klastrze, w którym mamy dwa shard'y z jedną repliką, będziemy mieć 2 StatefulSet'y i 2 Pod'y, właśnie dlatego, że wybraliśmy takie podejście z powodu wcześniej wymienionych powodów, aby mieć możliwość zbudowania heterogenicznego klastra.

Wróćmy do praktycznych zadań. W naszym klastrze trzeba konfigurować użytkowników, tzn. trzeba wykonać pewną konfigurację ClickHouse w Kubernetes. Operator do tego zapewnia wszystkie możliwości.

Można bezpośrednio w YAML pisać to, co chcemy. Wszystkie opcje konfiguracyjne są bezpośrednio mapowane z tego YAML do konfiguracji ClickHouse, które następnie są rozkładane po całym klastrze.
Można też pisać w ten sposób. To tylko przykład. Hasło można zrobić zaszyfrowane. Wspierane są absolutnie wszystkie opcje konfiguracyjne ClickHouse. To tylko przykład.
Konfiguracja w klastrze rozprzestrzenia się jako ConfigMap. W praktyce aktualizacja ConfigMap nie przebiega natychmiast, dlatego jeśli klaster jest duży, proces wprowadzania konfiguracji zajmuje trochę czasu. Ale to wszystko jest bardzo wygodne w eksploatacji.

Utrudniamy zadanie. Klaster się rozwija. Chcemy replikować dane. To znaczy, mamy już dwa shard'y, z jedną repliką, skonfigurowani użytkownicy. Rośniemy i chcemy zająć się replikacją.

Czego potrzebujemy do replikacji?
Potrzebujemy ZooKeepera. W ClickHouse replikacja oparta jest na ZooKeeperze. ZooKeeper jest potrzebny, aby różne repliki ClickHouse miały konsensus co do tego, jakie bloki danych są dostępne w którym ClickHouse.
Można używać dowolnego ZooKeepera. Jeśli firma ma zewnętrznego ZooKeepera, można go wykorzystać. Jeśli nie, można zainstalować go z naszego repozytorium. Jest instalator, który to wszystko ułatwia.

I schemat interakcji całego systemu wygląda tak. Mamy Kubernetes jako platformę. Na nim działa operator ClickHouse. ZooKeeper przedstawiłem tutaj. Operator współdziała zarówno z ClickHouse, jak i z ZooKeeperem. To stworzy interakcję.
Wszystko to jest potrzebne, aby ClickHouse skutecznie replikował dane w k8s.

Przyjrzyjmy się teraz samej zadaniu, jak będzie wyglądał manifest do replikacji.
Do naszego manifestu dodajemy dwie sekcje. Pierwsza – to miejsce, skąd brać ZooKeepera, który może być zarówno wewnątrz Kubernetes, jak i zewnętrzny. To po prostu opis. I zamawiamy repliki. To znaczy, chcemy dwie repliki. W sumie na wyjściu powinniśmy mieć 4 pod'y. Pamiętamy o storage, wróci on trochę później. Storage – to osobna historia.

Było tak.

Staje się tak. Dodają się repliki. 4-ta nie mieściła się, wierzymy, że może ich być wiele. I z boku dodaje się ZooKeeper. Schematy się komplikujują.

Nadeszła pora, aby dodać następne zadanie. Będziemy dodawać Persistent Storage.
W przypadku Persistent Storage mamy różne opcje realizacji.
W przypadku, gdy rozmieszczamy się u dostawcy chmurowego, na przykład korzystając z Amazon, Google, istnieje duża pokusa, by skorzystać z chmurowego storage. To bardzo wygodne, to dobrze.
I jest druga opcja. To dla lokalnego storage, gdy mamy lokalne dyski na każdej węźle. Ta opcja jest znacznie trudniejsza do zrealizowania, ale przy tym bardziej wydajna.

Zobaczmy, co mamy w odniesieniu do cloud storage.
Są zalety. To bardzo proste w konfiguracji. Po prostu zamawiamy u dostawcy chmurowego, aby dostarczył nam storage o określonej pojemności i klasie. Klasy są określone przez dostawców samodzielnie.
I jest to wada. Dla kogoś jest to wada niekrytyczna. Oczywiście będą pewne ograniczenia wydajności. Jest to bardzo wygodne w pracy, niezawodne, ale są pewne potencjalne spadki wydajności.

A ponieważ ClickHouse koncentruje się na wydajności, można nawet powiedzieć, że wyciska wszystko, co tylko możliwe, dlatego wielu klientów stara się maksymalnie wykorzystać wydajność.

Aby uzyskać maksymalne wyniki, potrzebujemy lokalnego storage.
Kubernetes oferuje trzy abstrakcje do wykorzystania lokalnego storage w Kubernetes. Są to:
- EmptyDir
- HostPath.
- Local
Zastanówmy się, czym się różnią, a czym są podobne.
Przede wszystkim we wszystkich trzech podejściach mamy storage – to lokalne dyski, które znajdują się na tej samej fizycznej nodzie k8s. Ale mają pewne różnice.

Zacznijmy od najprostszej, czyli emptyDir. Co to praktycznie oznacza? Prosimy naszą specyfikację o to, by system konteneryzacji (najczęściej jest to Docker) zapewnił nam dostęp do folderu na lokalnym dysku.
W praktyce Docker gdzieś tam tworzy tymczasowy folder, nazywając go długim hashem, według własnych ścieżek. I zapewnia interfejs dostępu do niego.
Jak to będzie działać pod względem wydajności? Będzie działać z szybkością lokalnego dysku, to znaczy, że to całkowity dostęp do własnego dysku.
Ale ma to swoją wadę. Trwałość w tym przypadku jest dość wątpliwa. Przy pierwszej zmianie Dockera z kontenerami trwałość jest tracona. Jeśli Kubernetes z jakiegoś powodu zechce przenieść ten Pod na inny dysk, dane zostaną utracone.
Takie podejście jest dobre do testów, ponieważ pokazuje już odpowiednią prędkość, ale na coś poważnego ten wariant się nie nadaje.

Dlatego jest drugie podejście. To HostPath. Gdyby spojrzeć na poprzedni slajd i ten, można zauważyć tylko jedno różnicę. Nasz folder pojawił się z Dockera bezpośrednio na nodzie Kubernetes. Tutaj jest trochę prościej. Bezpośrednio zapisujemy ścieżkę w lokalnym systemie plików, gdzie chcielibyśmy przechowywać nasze dane.
Zalety tego sposobu są. To już prawdziwa trwałość, wręcz klasyczna. Na dysku będą zapisane dane pod jakimś adresem.
Są również wady. To trudności w zarządzaniu. Nasz Kubernetes może chcieć przenieść Pod na inną fizyczną węzeł. I tu do akcji wchodzi DevOps. Musi on poprawnie wytłumaczyć całemu systemowi, że przenosić te pod’y można tylko na takie węzły, na których masz zamontowane coś po tych ścieżkach, i nie więcej niż jeden węzeł naraz. To dość skomplikowane.
Specjalnie w tym celu stworzyliśmy w naszej firmie szablony, aby ukryć całą tę złożoność. Można by po prostu powiedzieć: „Chcę mieć jeden instance ClickHouse na każdą fizyczną węzeł i po takiej ścieżce”.

Ale ta potrzeba nie dotyczy tylko nas, dlatego panowie z samego Kubernetes także rozumieją, że ludzie chcą mieć dostęp do fizycznych dysków, dlatego oferują trzeci poziom.
Nazywa się on local. Różnice z poprzednim slajdem są praktycznie żadne. Tylko wcześniej trzeba było ręcznie przeprowadzać, że tych pod’ów nie można przenosić z węzła na węzeł, ponieważ muszą być podpięte po takiej ścieżce do lokalnego fizycznego dysku, a teraz cała ta wiedza jest inkapsułowana w samym Kubernetes. I konfiguracja staje się znacznie prostsza.

Wracamy do naszego praktycznego zadania. Wracamy do szablonu YAML. Tutaj pojawił się prawdziwy storage. Wróciliśmy do tego. Zadajemy klasyczny szablon VolumeClaim jak w k8s. I opisujemy, jaki storage chcemy.
Po tym k8s poprosi o storage. Przydzieli go nam w StatefulSet. I w efekcie będzie to w dyspozycji ClickHouse.

Mieliśmy taką schemę. Nasz Persistent Storage był czerwony, co sugerowało, że warto go poprawić.

I staje się zielony. Teraz schemat klastra ClickHouse na k8s jest w pełni finalizowany. Mamy shard'y, repliki, ZooKeeper, mamy prawdziwy Persistent, który jest zrealizowany w jakiś sposób. Schemat jest już w pełni funkcjonalny.

Żyjemy dalej. Nasz klaster się rozwija. Andrey się stara i wydaje nową wersję ClickHouse.
Pojawia się praktyczne zadanie – przetestować nową wersję ClickHouse na naszym klastrze. Oczywiście nie chcemy implementować jej w całości, chcemy umieścić nową wersję w jakimś dalekim kącie na jedną replikę, a może nawet nie jedną nową wersję, a od razu dwie, ponieważ często się pojawiają.
Co możemy na ten temat powiedzieć?

Tut mamy dokładnie taką możliwość. To są szablony podów. Możemy je opisać, nasz operator całkowicie pozwala na budowę heterogenicznego klastra. Tzn. konfigurować, zaczynając od wszystkich replik w grupie, kończąc na każdej osobistej replikacji i określając, jaką wersję ClickHouse, jaką wersję magazynu chcemy. Możemy całkowicie skonfigurować klaster według naszych potrzeb.

Nieco zgłębimy temat. Wcześniej mówiliśmy, jak działa ClickHouse-operator w kontekście ClickHouse.
Teraz chciałbym powiedzieć kilka słów o tym, jak w ogóle działa jakikolwiek operator, a także o tym, jak współdziała z K8s.

Rozważmy współdziałanie z K8s na początku. Co się dzieje, gdy wykonujemy kubectl apply? Nasze obiekty pojawiają się w etcd przez API.

Na przykład podstawowe obiekty Kubernetes: pod, StatefulSet, service i tak dalej.
Jednocześnie nic fizycznego jeszcze się nie wydarzyło. Te obiekty muszą zostać zmaterializowane w klastrze.

W tym celu pojawia się kontroler. Kontroler to specjalny komponent k8s, który umie zmaterializować te opisy. Wie, jak i co należy zrobić fizycznie. Wie, jak uruchomić kontenery, co trzeba skonfigurować, aby serwer działał.

I on materializuje nasze obiekty w K8s.
Ale chcemy operować nie tylko podami, StatefulSetami, chcemy stworzyć ClickHouseInstallation, tzn. obiekt typu ClickHouse, aby operować nim jako całością. Na razie takiej możliwości nie ma.

Ale K8s ma następującą przyjemną rzecz. Chcemy, aby pojawiła się gdzieś tam taka skomplikowana jednostka, która byłaby zbudowana z podów i StatefulSet, naszego klastra.

Co należy zatem zrobić? Po pierwsze, na scenę wychodzi Custom Resource Definition. Czym to jest? To opis dla K8s, że będziesz miał jeszcze jeden typ danych, że chcemy dodać do pod, StatefulSet niestandardowy zasób, który będzie skomplikowany w środku. To opis struktury danych.

My również wysyłamy go tam przez kubectl apply. Kubernetes z radością go przyjął.
I teraz mamy w magazynie, przy obiekcie w etcd możliwość zapisania niestandardowego zasobu pod nazwą ClickHouseInstallation.
Ale na razie nic więcej się nie wydarzy. To znaczy, jeśli teraz stworzymy plik YAML, który omawialiśmy z opisem shardów, replik i powiemy "kubectl apply", to Kubernetes go przyjmie, umieści w etcd i powie: "Świetnie, ale nie wiem, co z tym zrobić. Jak zarządzać ClickHouseInstallation, nie wiem".

W związku z tym potrzebujemy kogoś, kto pomoże Kubernetesowi zarządzać nowym typem danych. Po lewej mamy standardowy kontroler Kubernetes, który działa z standardowymi typami danych. A po prawej powinien pojawić się niestandardowy kontroler, który potrafi pracować z niestandardowymi typami danych.
I w inny sposób nazywa się to operatorem. Celowo umieściłem go poza Kubernetes, ponieważ może działać także poza K8s. Najczęściej oczywiście wszystkie operatory działają w Kubernetes, ale nic nie stoi na przeszkodzie, aby działał na zewnątrz, dlatego tutaj został specjalnie wyróżniony.

I z kolei niestandardowy kontroler, czyli operator, współdziała z Kubernetesem przez API. Już potrafi wchodzić w interakcję z API. I wie, jak przekształcić niestandardowy zasób w złożoną strukturę, którą chcemy utworzyć. Tym się dokładnie zajmuje operator.

Jak działa operator? Przyjrzyjmy się prawej części, aby dowiedzieć się, jak to robi. Zobaczymy, jak operator materializuje wszystko i jak później wygląda interakcja z K8s.

Operator to program. Jest zdarzeniowo zorientowany. Operator przy pomocy API Kubernetes zapisuje się na wydarzenia. W API Kubernetes są punkty wejścia, w których można się zapisać na wydarzenia. I jeśli coś się zmienia w K8s, to Kubernetes wysyła powiadomienia do wszystkich zainteresowanych, tj. kto zapisał się na ten punkt API, ten będzie otrzymywał powiadomienia.
Operator zapisuje się na wydarzenia i powinien na nie reagować. Jego zadaniem jest reagować na pojawiające się zdarzenia.

Zdarzenia są generowane przez pewne aktualizacje. Przybywa nasz plik YAML z opisem ClickHouseInstallation. Przeszedł przez kubectl apply do etcd. Tam zadziałał event, w rezultacie ten event dotarł do ClickHouse-operatora. Operator otrzymał ten opis. I musi coś zrobić. Jeśli nadeszła aktualizacja obiektu ClickHouseInstallation, trzeba zaktualizować klaster. A zadaniem operatora jest aktualizowanie klastra.

Co on robi? Po pierwsze, należy opracować plan działań, co zamierzamy zrobić w związku z tą aktualizacją. Aktualizacje mogą być bardzo małe, tzn. małe w realizacji YAML, ale mogą wiązać się z bardzo dużymi zmianami w klastrze. Dlatego operator tworzy plan, a następnie się go trzyma.

Zgodnie z tym planem zaczyna konstruować tę strukturę wewnętrznie, aby zmaterializować pod'y, usługi, czyli robi to, co jest jego głównym zadaniem. To jak budowanie klastra ClickHouse w Kubernetes.

Teraz poruszmy interesujący temat. To podział odpowiedzialności między Kubernetes a operatorem, tzn. co robi Kubernetes, co robi operator i jak współdziałają ze sobą.
Kubernetes odpowiada za rzeczy systemowe, tzn. za podstawowy zestaw obiektów, które można interpretować jako zasięg systemu. Kubernetes wie, jak uruchamiać pod'y, jak restartować kontenery, jak montować wolumeny, jak pracować z ConfigMap, tzn. wszystko, co można nazwać systemem.
Operatorzy operują w dziedzinach tematycznych. Każdy operator jest tworzony dla swojej dziedziny tematycznej. U nas zrobiliśmy dla ClickHouse.
Operator działa właśnie w terminach dziedziny tematycznej, takich jak dodanie repliki, utworzenie schematu, skonfigurowanie monitoringu. Powstaje więc taki podział.

Przyjrzyjmy się praktycznemu przykładzie, jak ten podział odpowiedzialności działa, gdy wykonujemy działanie dodania repliki.
Do operatora trafia zadanie – dodać replikę. Co operator robi? Operator oblicza, że należy stworzyć nowy StatefulSet, w którym należy opisać takie a takie szablony, zgłoszenie wolumenu.

On to wszystko przygotowuje i przekazuje dalej do K8s. Mówi, że potrzebuje ConfigMap, StatefulSet, Wolumen. Kubernetes to realizuje. Zmaterializuje podstawowe jednostki, którymi operuje.

I dalej ponownie wkracza ClickHouse-operator. Ma już fizyczny pod, na którym można coś zrobić. I ClickHouse-operator ponownie działa w terminach dziedziny tematycznej. Tzn. aby włączyć replikę w klastrze ClickHouse, należy najpierw skonfigurować schemat danych, który jest w tym klastrze. Po drugie, tę replikę należy włączyć w monitoring, aby dobrze ją śledzić. To już operator ustawia.

I dopiero po tym wkracza sam ClickHouse, czyli kolejny, wyżej poziomowy byt. To już jest baza danych. Ma swoje instancje, kolejną skonfigurowaną replikę, która jest gotowa dołączenia do klastra.
W ten sposób łańcuch wykonania i podziału odpowiedzialności przy dodawaniu repliki staje się dość długi.

Kontynuujemy nasze praktyczne zadania. Jeśli klaster już istnieje, można wykonać migrację konfiguracji.

Zrobiliśmy tak, że do istniejącego pliku XML, który rozumie ClickHouse, można przesyłać dane bezpośrednio.

Można przeprowadzić dokładną konfigurację ClickHouse. Akurat wdrożenie strefowe – to właśnie to, o czym mówiłem podczas wyjaśniania hostPath, lokalnego magazynu. Jak prawidłowo przeprowadzić wdrożenie strefowe.

Następnym praktycznym zadaniem jest monitorowanie.

Jeśli nasz klaster się zmienia, musimy okresowo ustawiać monitorowanie.
Zobaczmy schemat. Zielone strzałki już omówiliśmy. Teraz przyjrzyjmy się czerwonym strzałkom. To sposób, w jaki chcemy monitorować nasz klaster. Jak metryki z klastra ClickHouse trafiają do Prometheus, a potem do Grafana.

A w czym tkwi trudność w monitorowaniu? Dlaczego jest to traktowane jako jakieś osiągnięcie? Trudność leży właśnie w dynamice. Kiedy mamy jeden klaster, który jest statyczny, można jednorazowo ustawić monitorowanie i więcej się tym nie zajmować.
Ale jeśli mamy wiele klastrów, lub ciągle coś się zmienia, proces staje się dynamiczny. Ciągłe przestawianie monitorowania to strata zasobów i czasu, czyli nawet zwykłe lenistwo. Trzeba to zautomatyzować. Trudność tkwi właśnie w dynamice procesu. I operator bardzo dobrze to automatyzuje.

Jak rozwijał się nasz klaster? Na początku wyglądał tak.

Potem był taki.

W końcu stał się taki.
A monitorowanie jest automatycznie realizowane przez operatora. Jednolite miejsce wejścia.

A my jedynie na wyjściu patrzymy na pulpit nawigacyjny Grafana, jak w środku toczy się życie naszego klastra.
Swoją drogą, pulpit nawigacyjny Grafana również jest dostarczany z naszym operatorem bezpośrednio w źródłach. Można się podłączyć i korzystać. Ten zrzut ekranu otrzymałem od naszych DevOps.

Gdzie chcielibyśmy iść dalej? To:
- Rozwijać automatyzację testowania. Głównym zadaniem jest automatyzacja testowania nowych wersji.
- Chcielibyśmy również zautomatyzować integrację z ZooKeeper. Planujemy zintegrować się z ZooKeeper-operator. Tzn. dla ZooKeeper napisano operatora i logiczne jest, aby dwa operatorzy zaczęli się integrować w celu stworzenia bardziej wygodnego rozwiązania.
- Chcemy zrobić bardziej zaawansowane kontrole żywotności.
- Na zielono wyróżniłem to, co mamy na ukończeniu — dziedziczenie Template – ZREALIZOWANE, tzn. z następną wersją operatora będziemy już mieć dziedziczenie szablonów. To potężne narzędzie, które pozwala budować złożone konfiguracje z kawałków.
- I chcemy zautomatyzować skomplikowane zadania. Główne z nich to Re-sharding.

Podsumujmy dotychczasowe wyniki.

Co otrzymujemy na wyjściu? Czy warto się tym zajmować, czy nie? Czy należy w ogóle próbować przenieść bazę danych do Kubernetes i zastosować operatora w ogóle oraz operatora Alitnity w szczególności.
Na wyjściu uzyskujemy:
- Znaczne uproszczenie i automatyzację konfiguracji, wdrażania oraz utrzymania.
- Natychmiastowy, wbudowany monitoring.
- I gotowe do użycia, skodyfikowane szablony dla złożonych sytuacji. Już działania typy dodanie repliki nie muszą być wykonywane ręcznie. To robi operator.

Pozostało tylko jedno pytanie. Mamy już bazę danych w Kubernetes, wirtualizację. Jak wygląda wydajność tego rozwiązania, szczególnie w kontekście tego, że ClickHouse jest zoptymalizowany pod kątem wydajności?
Odpowiedź – wszystko w porządku! Nie będę szczegółowo opisywał, to temat osobnego wykładu.

Jednak istnieje projekt, taki jak TSBS. Jakie jest jego główne zadanie? To test baz danych pod kątem wydajności. To próba porównania ciepłego z ciepłym, miękkiego z miękkim.
Jak to działa? Generowany jest jeden zestaw danych. Następnie ten zestaw danych jest testowany na różnorodnych bazach danych przy użyciu tych samych testów. I każda baza danych rozwiązując jedną zadanie robi to tak, jak potrafi. A potem można porównywać wyniki.
Już obsługuje wiele baz danych. Wyróżniłem trzy główne. To:
- TimescaleDB.
- InfluxDB.
- ClickHouse.

Przeprowadzono również porównanie z innym podobnym rozwiązaniem. Porównanie z RedShift. Porównanie miało miejsce na Amazonie. ClickHouse również dobrze przeskakuje wszystkich w tej kwestii.

Jakie wnioski można wyciągnąć z tego, co powiedziałem?
- Baza danych w Kubernetes jest możliwa. Prawdopodobnie można używać różnych baz, ale ogólnie wygląda na to, że to zadziała. ClickHouse w Kubernetes na pewno można uruchomić za pomocą naszego operatora.
- Operator pomaga automatyzować procesy i naprawdę ułatwia życie.
- Wydajność jest normalna.
- I wydaje nam się, że to można i trzeba wykorzystać.
Open source – dołącz do nas!
Jak już wspomniałem, operator to całkowicie produkt open source, dlatego byłoby świetnie, gdyby jak najwięcej ludzi z niego korzystało. Dołączcie do nas! Czekamy na wszystkich!
Dziękuję wszystkim!
Pytania

Dziękuję za prezentację! Nazywam się Anton. Jestem z firmy SEMrush. Interesuje mnie logging. O monitoringu słychać, a o logowaniu nic, jeśli mówimy o całym klastrze. U nas na przykład zbudowaliśmy klaster na sprzęcie. I wykorzystujemy zcentralizowane logowanie, zbierając standardowymi narzędziami w jedną całość. A potem wyciągamy z niej interesujące nas dane.
Dobre pytanie, tzn. logowanie jest na liście do zrealizowania. Nasz operator obecnie tego nie automatyzuje. On jest jeszcze w fazie rozwoju, projekt jest stosunkowo młody. Rozumiemy konieczność logowania. To również bardzo ważny temat. I jest zapewne co najmniej tak samo ważny, jak monitoring. Jednak pierwszym elementem do realizacji był monitoring. Logowanie będzie. Oczywiście staramy się zautomatyzować wszystkie aspekty funkcjonowania klastra. Dlatego odpowiedź jest taka – w tej chwili operator, niestety, tego nie potrafi, ale to jest w planach, zamierzamy to zrobić. Jeśli jest chęć, aby dołączyć, to proszę o pull request.
Witam! Dziękuję za prezentację! Mam standardowe pytanie związane z Persistent Volumes. Kiedy tworzymy konfigurację za pomocą tego operatora, jak operator określa, na którym węźle mamy zamontowany dysk lub folder? Musimy mu wcześniej wyjaśnić, że proszę umieścić nasz ClickHouse właśnie na tych węzłach, na których jest dysk?
O ile rozumiem, to pytanie to kontynuacja local storage, szczególnie jego części dotyczącej hostPath. To jak wyjaśnić całemu systemowi, że pod powinien być uruchomiony właśnie na takim węźle, na którym mamy fizycznie podłączony dysk, zamontowany w określonej lokalizacji. To jest cała sekcja, którą poruszyłem bardzo powierzchownie, ponieważ odpowiedź jest dość obszerna.
W skrócie wygląda to tak. Oczywiście musimy dokonać provisioning tych wolumenów. W tej chwili w lokalnej pamięci nie ma dynamicznego provisioningu, więc DevOps muszą samodzielnie podzielić dyski, te wolumeny. Muszą również wyjaśnić Kubernetesowi provisioning, że będą mieć Persistent volumes określonej klasy, które znajdują się na takich a takich nodach. Następnie trzeba będzie wyjaśnić Kubernetesowi, że pod’y, które potrzebują określonej klasy lokalnej pamięci, muszą być planowane tylko na takich a takich nodach. W tym celu operator ma możliwość przypisania jakiejś etykiety i one per host instance. I okaże się, że pod’y będą routowane przez Kubernetes do uruchomienia tylko na nodach spełniających wymagania, etykietami mówiąc prosto. Administratorzy przypisują etykiety, dokonują provisioning dysków ręcznie. A wtedy można to skalować.
I właśnie trzecia opcja lokalna pomaga to nieco ułatwić. Jak już podkreślałem, to żmudna praca nad konfiguracją, która na końcu pomaga uzyskać maksymalną wydajność.
Mam drugie pytanie związane z tym. Kubernetes był zaprojektowany w taki sposób, że nie ma znaczenia, czy stracimy nodę, czy nie. Co w takim przypadku należy zrobić, jeśli utracimy nodę, na której znajduje się shard?
Tak, Kubernetes został początkowo zaprezentowany tak, że nasze relacje z pod’ami – to jak bydło, a tutaj każdy dysk staje się czymś w rodzaju zwierzątka domowego. Jest taki problem, że nie możemy ich po prostu wyrzucić. Rozwój Kubernetes zmierza w kierunku, że nie można całkowicie podchodzić do tego filozoficznie, jak do całkowicie wyrzucanych zasobów.
Teraz praktyczne pytanie. Co zrobić, jeśli stracisz nodę, na której był dysk? Tutaj problem już rozwiązany jest na wyższym poziomie. W przypadku ClickHouse mamy repliki, które działają na wyższym poziomie, tzn. na poziomie ClickHouse.
Jaka zanika dyspozycja? Za to, żeby dane się nie zgubiły, odpowiada DevOps. Musi on prawidłowo skonfigurować replikację i musi kontrolować, żeby replikacja była realizowana. W replikach na poziomie ClickHouse dane muszą być zdublowane. To nie jest zadanie, które rozwiązują operatorzy. I nie to zadanie, które rozwiązuje sam Kubernetes. To na poziomie ClickHouse.
Co zrobić, jeśli twój węzeł serwerowy przestał działać? Należy zainstalować drugi, poprawnie skonfigurować dysk i dodać etykiety. Po tym procesie węzeł spełni wymagania, by Kubernetes mógł uruchomić instancję poda. Kubernetes go uruchomi. Liczba podów jest niewystarczająca do określonej wartości. Przejdzie przez cykl, który pokazałem. A na najwyższym poziomie ClickHouse zrozumie, że dostarczono replikę, która jest jeszcze pusta i należy zacząć transfer danych. Innymi słowy, ten proces jest jeszcze słabo zautomatyzowany.
Dziękuję za prezentację! Kiedy coś złego się dzieje, operator się zawiesza i restaruje, a w tym momencie pojawiają się zdarzenia, czy w jakiś sposób to przetwarzacie?
Co się stanie, jeśli operator zawiesi się i zostanie zrestartowany, tak?
Tak. I w tym momencie pojawiły się zdarzenia.
Zadanie związane z tym przypadkiem jest częściowo rozdzielone między operatorem a Kubernetesem. Kubernetes ma możliwość odtworzenia zdarzeń, które miały miejsce. Odtwarza je. A zadaniem operatora jest zapewnienie, że gdy zostanie na niego odtworzone log zdarzeń, to zdarzenia te będą idempotentne. Oznacza to, że ponowne wystąpienie tego samego zdarzenia nie złamie naszego systemu. I nasz operator dobrze radzi sobie z tym zadaniem.
Dzień dobry! Dziękuję za prezentację! Dmitrij Zawiałow, firma Smiedowa. Czy planowane jest dodanie do operatora możliwości konfiguracji z haproxy? Interesuje mnie jakiś inny balancer oprócz standardowego, który byłby mądry i zrozumiał, że to rzeczywiście ClickHouse.
Czy mówisz o Ingress?
Tak, zamienić Ingress na haproxy. W haproxy można określić topologię klastra, gdzie znajdują się jego repliki.
Na razie o tym nie myśleliśmy. Jeśli potrzebujesz tego i możesz wyjaśnić, dlaczego to jest potrzebne, to możliwe, że uda się to zrealizować, szczególnie jeśli zechcesz wziąć w tym udział. Krótką odpowiedzią jest – nie, w tej chwili nie mamy takiej funkcjonalności. Dziękujemy za sugestię, przyjrzymy się temu. A jeśli jeszcze wyjaśnisz use case i dlaczego to jest potrzebne w praktyce, na przykład stworzysz zgłoszenia na GitHubie, to będzie wspaniale.
Już jest.
Dobrze. Jesteśmy otwarci na wszelkie propozycje. A haproxy zostanie dodane do listy zadań do wykonania. Lista zadań się rozrasta, a nie zmniejsza jak dotąd. Ale to dobrze, to oznacza, że produkt jest poszukiwany.
Źródło: habr.com
