[GlusterFS] Tworzymy macierz na RaspberryPi dla klastra Docker-Swarm!

Tytuł jest nieco na wyrost, ponieważ do profesjonalnej macierzy naszej konstrukcji jeszcze daleko, jednak mamy bardzo ciekawą opcję zapoznania się jak działa rozdystrybuowany system plików oraz jakie zasady są istotne podczas tworzenia macierzy, a przy okazji skonstruujemy nieźle działający system serwerowy w cenie kilkuset złotych. Zaczynajmy! 🙂

Tworzenie wolumenu GlusterFS w Raspberry Pi na potrzeby klastra Docker Swarm.

Cel:

Naszym celem jest zbudowanie klastra swarmowego dla dockera odpornego na awarie co najmniej jednego węzła. O ile sam Swarm Mode radzi sobie nieźle z przełączeniem i otrzymujemy to praktycznie z pudełka o tyle problem stanowi współdzielony storage. Ponieważ dane muszę być dostępne dla każdego węzła, tzn.

  1. Mamy 5 jednostek RaspberryPi z uruchomionym Dockerem w trybie klastra swarm.
  2. Na dockerze hostujemy 5 kontenerów, niech będzie to Mysql, Nginix, WordPress, Rabbitmq i Redis. Każdy z kontenerów operuje na danych. Dane te umieszczamy na wolumenach w systemie plików.
  3. Dane musza być więc dostępne dla każdego węzła klastra Docker-swarm, czyli każdy mini-komputer powinien mieć do nich dostęp. Nazywamy to współdzieleniem magazynu danych.

Metody współdzielenia danych pomiędzy węzłami Dockera

Metod mamy co najmniej kilka, rozważmy te najbardziej oczywiste:

Współdzielony magazyn – z dokumentacji Dockera
NFS – Network File System

Najbardziej oczywista metoda to udostepnienie współdzielonego magazynu np. poprzez NFS, jest to rozwiązanie z którego pewnie skorzystalibyśmy w środowisku serwerowym z prawdziwego zdarzenia. W naszym przypadku (RaspberryPi) oczywiście również możemy to zrobić, poprzez:

  1. Dedykowanie jednego mini-komputera jako serwer NAS, uruchomienie na nim Openmediavault i udostępnienie wolumenu do wszystkich węzłów dockera. Zadziała, ale ma wadę: pojedynczy punkt awarii. Jeśli nasz „autorski serwer NAS” ulegnie awarii to wszystkie węzły Dockera przestaną działać, gdyż stracą dostęp do danych swoich kontenerów. Oczywiście możemy minimalizować to ryzyko poprzez backupy, czy w ostateczności poprzez stworzenie klastra NAS na Raspberry Pi.
  2. Zakup profesjonalnego, lub „domowego” serwera NAS, rozwiązanie bardzo dobre, szczególnie jeśli kupimy NAS z nieco wyższej półki z dwoma kontrolerami dyskowymi i wbudowanym kontrolerem RAID, jednak cena takiego serwera to koszty większe niż budżet naszego projektu.

Przy takiej konfiguracji montujemy wolumen NFS do wszystkich hostów Dockera i… działa.

CIFS – SMB

Wg moich testów na Raspberry jest to sposób nieco mniej wydajny (wolniejszy); jednak sporym benefitem jest możliwość łatwego podpięcia do systemu Windows. W scenariuszy w którym chcemy zarządzać/modyfikować pliki umieszczone na wolumenie z którego korzystają nasze Docker’y także z systemu Windows – można to rozważyć. Ja raczej odradzam: Docker oczywiście wspiera SMB, ale lepiej po prostu łączyć się z NFS za pomocą dedykowanej usługi lub WinSCP (który dodatkowo ma wiele zalet, jak np. automatyczna synchronizacja w tle).

Storage w chmurze

Również bardzo dobre i profesjonalne rozwiązanie, przede wszystkim wydajne i nowoczesne. Jednak kosztowne. W profesjonalnym rozwiązaniu – zalecane, w naszym przypadku – raczej jako ciekawostka.
Zarówno Azure jak i Amazon dostarczają różnego typu dyski praktycznie każdą dostępną metodą. Ja polecam Azure Storage udostępnione na SMB 3.0 i plugin Azure CloudStor dla Dockera.

Rsync i rozwiązania autorskie

W ostateczności możemy tworzyć przeróżne rozwiązania autorskie, jak choćby kopiowanie plików pomiędzy poszczególnymi węzłami klastra za pomocą mechanizmów typu Rsync. Można sobie wyobrazić scenariusz, w którym do każdego Raspberry Pi podłączamy dysk USB, montujemy go w systemie operacyjnym w dokładnie tej samej ścieżce na każdym serwerze, w ścieżce tej umieszczamy dane naszych kontenerów, a następnie za pomocą Rsync dbamy o spójność danych pomiędzy dyskami. Trudne, ryzykowne, nieco skomplikowane, ale możliwe i czasami spotykane…

GlusterFS

Czyli współdzielony, rozdystrybuowany pomiędzy poszczególnymi węzłami system plików.

Na nim się skupimy!

Logo GlusterFS

GlusterFS

GlusterFS możemy w uproszczeniu opisać jako system plików rozproszony pomiędzy poszczególne serwery wchodzące w skład klastra GlusterFS. Klaster ten udostępnia pliki. Następnie za pomocą dedykowanego klienta lub pluginu dockerowego korzystamy z danych udostępnionych przez Gluster.

Zalety
  1. Klaster można zbudować na dowolnej liczbie serwerów, najlepiej 3 lub więcej. Nawet jeśli nasze środowisko Docker-Swarm liczy 5 serwerów, kontener Gluster może zostać zbudowany na 3 z nich, będą one udostępniały dane pozostałym.
  2. Udostępniany na zasadach wolnej licencji OpenSource.
  3. Stanowi warstwę nadrzędną (a właściwie warstwę abstrakcji) dla lokalnego, fizycznego systemu plików (np. NTFS lub ext3/ext4).
  4. Daje możliwość tworzenia różnych rodzajów wolumenów – zależnie od potrzeb, podstawowe typy to:
  • Distributed: udostępnia wolumen pomiędzy serwerami. Brak redundancji. Używamy gdy chcemy dodać przestrzeń dyskową do naszego serwera, ale nie potrzebujemy redundancji.
  • Replicated: replikuje dane pomiędzy poszczególnymi wolumenami. Używamy w rozwiązaniach krytycznych, w których zależy nam na bezpieczeństwie danych.
  • Dispersed: podobne do RAID5 ale nieco ciekawsze: każdy dysk fizyczny wchodzący w skład wolumenu zawiera „kody wymazywania” (erasure codes) w których przechowywane są informacje nt. plików na innych dyskach. Co oznacza, że jeśli np. jeden dysk ulegnie awarii to dane będą nadal dostępne ponieważ „brakująca część” znajdująca się na uszkodzonym wolumenie zostanie odzyskana/wyliczona z erasure codes.
    Administrator może skonfigurować na awarię ilu dysków ma być odporne nasze środowisko. Przekłada się to na wielkość obszarów erasure codes na każdym dysku.
    UWAGA: Fizyczne foldery na dysku, które przechowują dane naszego wolumeny nazywają się w nomenklaturze Gluster: bricks.
  • Dalej pozostają nam wariacje typu: Distributed-Replicated i Distributed-Dispersed

Konfigurujemy!

Zakładamy, że mamy pięć sztuk Raspberry Pi w klastrze Docker swarm i na 3 z nich konfigurujemy GlusterFS. Dlaczego na 3? Ponieważ konfiguracja oparta na 2 serwerach może być niestabilna w przypadku przełączenia, restartu lub awarii, musielibyśmy wtedy dodać „arbitra”, który stwierdzałby, jak rozwiązywać ewentualne konflikty.

Zatem do każdego Raspberry Pi podłączamy po jednym dysku SSD poprzez USB 3.0, tzn. kupujemy dyski SSD np. 128GB, do tego obudowy USB 3.0 i podłączamy bezpośrednio do „niebieskich portów USB” naszej Maliny. Dysk SSD oprócz tego, że jest szybszy i lepiej radzi sobie z współbieżnością to przede wszystkim ma zdecydowanie niższy pobór prądu podczas startu i Raspberry spokojnie udźwignie go na standardowym zasilaniu.
Dyski HDD często sprawiają kłopot i bywa, że trzeba je podłączać poprzez aktywne HUBy USB – to kolejny punkt awarii więc wole go redukować.

Zaczynamy od udostepnienia naszych Raspberry po nazwie.
Na każdym z pięciu serwerów edytujemy plik etc/hosts i dodajemy alias zgodny z naszą adresacją i wybraną przez nas nazwą (u mnie maszyny nazywam dockerX, a adresacja zajmuje zakres 192.168.1.111 do 192.168.1.115):

nano /etc/hosts
192.168.1.111 docker1
192.168.1.112 docker2
192.168.1.113 docker3
192.168.1.114 docker4
192.168.1.115 docker5

Na wszelki wypadek możemy sprawdzić pingiem czy działa.

Instalujemy GlusterFS i uruchamiamy
sudo apt-get install glusterfs-server

sudo systemctl enable glusterd

sudo service glusterd start
Przygotowujemy nasze dyski USB

Podłączamy nasze (puste) dyski USB do każdego z serwerów i konfigurujemy (to samo na każdym serwerze w klastrze GlusterFS).

Zaczynamy od sprawdzenia czy nasze dyski są widoczne w systemie operacyjnym, przydate komendy:

lsusb - lista urządzeń USB
lsblk - lista dostępnych dysków z punktami montowania
blkid  - lista dysków dostępnych z identyfikatorami
df -h    - lista dysków zamontowanych 

Powinniśmy widzieć nasz dysk USB oraz jego identyfikator. Formatujemy.

sudo mkfs -t ext4 /dev/sdb1

Oczywiście wstawiamy ścieżkę naszego dysku.

Tworzymy punkt montowania naszych dysków USB:

mkdir -p /mnt/usbForGv

Montujemy (pamiętamy aby wpisać właściwe ścieżki, zgodnie z tym jak to wygląda u nas):

sudo mount /dev/sdb /mnt/usbForGv

Pamiętamy aby w fstab dodać opcję montowania za każdym startem systemu:

sudo nano /etc/fstab
#dodajemy:
dev/sdb  /mnt/usbForGv ext4 defaults 0 0

Upewniamy się, że montowanie przebiegło pomyślnie np. za pomocą wspomniajej komendy lsblk lub po prostu mount.

W końcu konfigurujemy GlusterFS

Zaczynamy od dodania poszczególnych węzłów do klastra:

Na każdym z serwerów wywołujemy:

docker1sudo gluster peer probe docker1
docker2sudo gluster peer probe docker2
docker3sudo gluster peer probe docker3
Dodawanie maszyn do klastra

Po wydaniu każdej komendy weryfikujemy czy zakończyła się powodzeniem, powinniśmy utrzymać informacje „Probe successful„.

Tworzymy współdzielony wolumen – w końcu! :

gluster volume create dockerData replica 3 docker1:/mnt/usbForGv 
docker2:/mnt/usbForGv docker3:/mnt/usbForGv

Zauważcie, że:

  • nasz wolumen nazywa się dockerData
  • wskazujemy folder, w którym będą przechowywane dane naszego wolumenu na każdym węźle klastra, w moim przypadku jest to ta sama ścieżka na każdym serwerze (/mnt/usbForGv), gdyż właśnie tam są zamontowane dyski USB, jednak nie musi tak być. To bardzo wygodne.
  • Tworzymy wolumen typu Replicated.

Następnie startujemy nasz wolumen:

gluster volume start dockerData 
Montowanie wolumenu na klientach

Wolumen mamy już wystartowany pora podłączyć go na każdym hoście Dokera.
Na każdym spośród 5 serwerów. Pamiętamy, że Gluster zbudowaliśmy z użyciem 3 serwerów, a klientów będzie 5. Oczywiście klientami będą również serwery wchodzące w skład klastra GlusterFS, jednak nie musi tak być. Jeśli chcemy Gluster może być hostowany oddzielnie, a klienci mogą działać na całkiem innych serwerach. W środowisko „profesjonalnym” tak pewnie byśmy zrobili, jednak jeśli budujemy niskobudżetowe środowisko „home-lab”, nie ma przeszkód aby na serwerach Glustera funkcjonował również klient, a także Docker. Raspberry Pi w wersji 4 spokojnie podoła takiemu zdaniu.

Na każdym serwerze tworzymy foldery, które będzie stanowił punkty montowania klastra:

mkdir -p /CLU/gv/

Instalator serwera GlusterFS zawiera również moduł kliencki nie musimy więc nic dodatkowo instalować na hostach wchodzących w skład klastra GlusterFS, montujemy:

sudo mount -t glusterfs docker1:/dockerData /CLU/gv/

Zwróćcie proszę uwagę na jedną zależność: musimy odróżnić fizyczne storage na których przechowujemy dane klastra od punktu montowania samego klastra:

/CLU/gv/Punkt montowania wolumenu GlusterFS w systemie plików
/mnt/usbForGv/Punkt montowania dysku USB, na którym będziemy składować dane wolumenu Gluster
dockerDatawewnętrzna nazwa naszego wolumenu w systemie GlusterFS
Rozróżnienie używanych nazw

Do danych odwołujemy się poprzez punkt montowania klastra, a nie poprzez fizyczną lokalizację „wewnętrznych danych” klastra. W naszym przypadku to /CLU/gv/

Automatyczne montowanie klastra

Aby klaster Gluster montowany był automatycznie za każdym uruchomieniem systemu, musimy ponownie edytować fstab i na każdym serwerze dodać oddzielny wpis zgodnie z poniższą tabelą:

sudo nano /etc/fstab
docker1docker1:/dockerData /CLU/gv/ glusterfs defaults,_netdev,noauto,x-systemd.automount 0 0
docker2docker2:/dockerData /CLU/gv/ glusterfs defaults,_netdev,noauto,x-systemd.automount 0 0
docker3docker3:/dockerData /CLU/gv/ glusterfs defaults,_netdev,noauto,x-systemd.automount 0 0
Montowanie klastra na maszynach wchodzących w jego skład
Montowanie na pozostałych maszynach

Na maszynach, które nie uczestniczą w hostowaniu klastra GlusterFS musimy zainstalować klienta:

sudo apt-get install glusterfs-client

Montowanie odbywa się w sposób analogiczny, nie ma większego znaczenia, którego serwera użyjemy, gdyż jest to tylko wskazanie, o który wolumen nam chodzi, może to być np. docker1, zatem dodajemy do fstab:

docker1:/dockerData  /CLU/gv/ glusterfs defaults,_netdev,noauto,x-systemd.automount 0 0
Gotowe!

Gotowe. Nasz rozproszony system plików powinien działać i powinien być odporny na awarie nawet dwóch serwerów w klastrze. W tym momencie możemy umieścić dane poszczególnych kontenerów Dockera w lokalizacji:

/CLU/gv/

na każdym z serwerów. Gluster będzie replikował dane pomiędzy serwerami i w chwili gdy Swarm zdecyduje o przełączeniu kontenera na inny host, odbędzie się to bezproblemowo gdyż mamy współdzielony system plików.

Na koniec musimy pamiętać o kilku kwestiach:

  • Pliki nie mogą być otwierane współbieżnie. Co oznacza, że jeśli jeden węzeł klastra otworzy dany plik, drugi nie ma do niego dostępu. To oczywiste i oczekiwane(!) działanie. Synchronizacja odbywa się na poziomie pliku, a nie jego zawartości.
  • Musimy pamiętać o bezpieczeństwie. O ile to my decydujemy o hostach, które dołączą jako węzły naszego klastra GlusterFS o tyle klienci powinni się zautoryzować. Pamiętamy, że klientem może być maszyna spoza klastra. Tutaj proponuję zainteresować się, atrybutem auth.allow, jest to jednak temat na oddzielny wpis:
gluster volume set NAZWA_WOLUMENU auth.allow 10.1.1.*,10.5.5.1, itp
  • Wydajność. Wydajność jest wystarczająca do domowego użytku, aby hostować np. kolejki MQ, czy Domoticz’a, natomiast przy dużej ilości małych plików zdecydowanie maleje. Istnieje kilka metod aby to zminimalizować, jednak spadek wydajności przy działaniu na dużej ilości małych plików jest zauważalny.
  • Sugeruje utrzymywać co najmniej 3 serwery Glustera. W przypadku dwóch, jeśli zrestartujemy je równocześnie istnieje duże prawdopodobieństwo, że mechanizm Glustera nie będzie potrafił ocenić, który plik zawiera najnowsze zmiany i może zawiesić wolumen oczekując na ręczną synchronizację. Zabieg taki nazywa się rebalancingiem i realizuje się go komendą:
sudo gluster volume rebalance NAZWA_WOLUMENUfix-layout start

Gluster to zaawansowany, bardzo pomocny, dobrze konfigurowalny, rozproszony system plików. Znajdziemy dla niego szerokie zastosowania zarówno w projektach domowych jak i „pół-profesjonalnych”, są i duże firmy, które swoje storage opierają właśnie o Gluster’a.

Leave a Reply