Skalowanie i wysoka dostępność GitLab #4

Po dłuższej przerwie wracamy do naszego GitLab’a i jego skalowania. W poprzednim artykule uruchamialiśmy GitLab’a na jednym serwerze, na którym działał Docker. Oczywiście całość korzystała z wolumenów. Dzięki temu mieliśmy dane aplikacji poza kontenerem co ułatwiało jego ponowne uruchomienie podczas awarii hosta. Niestety dalej byliśmy ograniczeni właśnie do jednego serwera wirtualnego czy fizycznego. W razie jego awarii aplikacje przestawały działać. Z pomocą przychodzi nam Docker Swarm. Na początku trochę teorii o samym Docker Swarm i jak działa, następnie pokażę jak w szybki prosty sposób uruchomić GitLab’a w ramach klastra Swarm.

Docker Swarm pozwala nam grupować jeden albo wiele nodów Docker’owych i pozwala zarządzać nimi jako klastrem. Po tak zwanym „Out-of-the-box” otrzymujemy rozproszone zaszyfrowane klastry, szyfrowaną sieć, dwukierunkowy TLS, zabezpieczony klaster za pomocą tokenów, dzięki którym możemy dodawać kolejne serwery  oraz PKI pozwalające zarządzać certyfikatami. Na froncie ogólnie pojętej orchestracji swarm dostarcza nam bogate API, które pozwala nam na rozmieszczanie i zarządzanie skomplikowanymi mikro serwisami z dużą łatwością. Możemy definiować własne pliki z konfiguracją i uruchamiać je za pomocą wbudowanych poleceń Docker’a. Możemy również w ten sposób wdrażać aktualizacje, cofać się do poprzednich wersji i oczywiście skalować za pomocą prostych poleceń.

W przeszłości Docker Swarm był osobnym produktem, a od wersji Docker 1.12 został w pełni z nim zintegrowany i możemy go uruchomić za pomocą pojedynczego polecenia. W przypadku klastra swarm, jak wspomniałem wcześniej może to być jeden lub więcej nodów z Dockerem na pokładzie. Nie ma to znaczenia czy to jest fizyczny serwer, wirtualna maszyna czy instancja w chmurze. Podstawowym wymaganiem jest aby wszystkie serwery miały możliwość komunikacji ze sobą. W klastrze wyróżniamy dwa rodzaje serwerów – managers i workers. Jak same nazwy wskazują serwery typu manager są zarządcą klastra a worker’y akceptują zadania od serwerów zarządzających i je wykonują. Konfiguracja i stan roju jest przechowywana w bazie etcd zlokalizowanej na każdym z serwerów manager. Jest przechowywana w pamięci podręcznej i jest zawsze up-to-date. Najlepszym elementem tego rozwiązania jest brak jakiejkolwiek konfiguracji. Po prostu jest to instalowane i sam klaster dba o ten element.

Przechodzimy teraz do samych kontenerów. W przypadku Swarm, gdy uruchamiamy kontener jako usługę możemy go nazywać zadaniem lub repliką, a konstrukcja usługi dodaje mu takie rzeczy jak skalowanie, możliwość wdrażania aktualizacji i proste cofniecie do poprzedniej wersji. Poniżej uproszczony schemat działania klastra Swarm:

Teraz, wiedząc jak działa Swarm przechodzimy do utworzenia naszego klastra. Musimy pamiętać, że w naszym klastrze powinniśmy posiadać nieparzystą liczbę managerów. Oczywiście nie przesadzamy z ich ilością, zalecane jest 3 lub 5 managerów w klastrze. Jak wspomniałem wcześniej każdy z nodów musi posiadać zainstalowanego Docker’a i mieć możliwość komunikacji z reszta serwerów. Do komunikacji powinny zostać odblokowane porty na firewall’u:

  • 2377/tcp – na potrzeby zabezpieczonego połączenia client-swarm
  • 7946/tcp i 7946/udp – na potrzeby zarządzania
  • 4789/udp – na potrzeby VXLAN overlay networks

Jeżeli chodzi o zasadę działania sieci w przypadku Dockera, jest to temat na dość obszerny artykuł.
OK, to zaczynamy. Logujemy się na pierwszy system operacyjny aby zainicjować nasz nowy rój serwerów. Wykonujemy polecenie poniżej:

docker swarm init –-advertise-addr 10.0.0.1:2377 --listen-addr 10.0.0.1:2377

Po poprawnym wykonaniu polecenia posiadamy już pierwszy serwer działający jako manager. Teraz kilka słów co robi powyższe polecenie:

• docker swarm init – mówi Dockerowi aby zainicjować nowy klister swarm oraz aby aktualny serwer był pierwszym managerem • --advertise-addr – jest to adres IP, który powinien być wykorzystywany przez inne serwery do połączenia z tym managerem • --listen-addr – definuje adres IP i port jaki na jakim serwer bedzie nasłuchiwał ruchu swarm

Zalecane jest wykorzystanie obu flag. Listujemy sobie serwery za pomocą polecenia:

docker node ls

Kolejnym krokiem jest dodanie następnych serwerów albo jako worker, albo jako manager.  Polecenie wykonujemy na serwerze, który chcemy dodać do klastra i różni się tokenem, który określa rolę w klastrze. Poniżej polecenie odpowiednio dla workera i managera. 

docker swarm join --token SWMTKN-1-5ecj443mr2i214r7atqlco4fcwh2a2qkfvdntvpznan3zrymwc- 7v5e3rkwbkm4alw3qo5u2znlh 10.0.0.10:2377

docker swarm join --token SWMTKN-1-5ecj443mr2i214r7atqlco4fcwh2a2qkfvdntvpznan3zrymwc ei3lninfluc10khbva212se2b 10.0.0.10:2377

Jak widzimy wszystko jest ok.

Serwery typu manager wspierają natywnie technologię wysokiej dostępności (HA). Co to dla nas oznacza? Nic innego jak to, że jeden lub więcej serwerów może być niedostępnych z różnych powodów, a całość i tak będzie działać. Swarm posiada implementacje multi-manager w trybie active-passive. Oznacza to, że tylko jeden serwer będzie aktywnym i nazywamy go tzw. Liderem. Tylko on potrafi przetwarzać polecenia w klastrze. Jeżeli inny pasywny manager odbierze polecenia przeznaczone dla swarm’a zadziała w trybie proxy i przekaże je do aktualnego lidera. Mechanizm działania obrazuje schemat poniżej.

Jak widzimy w ramach klastra posiadamy nody oznaczone jako leaders albo follwers. Taka terminologia pojawia się ponieważ Swarm korzysta z algorytmu Raft. Po więcej informacji odsyłam do strony poświęconej temu algorytmowi – https://raft.github.io/. Jak widzimy samo uruchomienie Docker Swarm nie jest skomplikowaną procedurą. Mając zainstalowane  systemy operacyjne jesteśmy w stanie przygotować wszystko w ciągu 5 minut. Jeżeli robimy tego typu klastry bardzo często w bardzo łatwy i szybki sposób możemy sobie napisać skrypty, które zrobią to za nas lub możemy gotowe znaleźć w Internecie (warto sprawdzić ich działanie).

Ok, teraz czas na naszą wisienkę na torcie w postaci GitLab’a. Skonfigurowanie GitLab’a  na potrzeby klastra Docker Swarm jest niezwykle szybkim zabiegiem, jeżeli mówimy o podstawowej konfiguracji. Do tego celu tworzymy pliki z konfiguracją naszej aplikacji. Dobrym pomysłem jest również wykorzystanie zewnętrznego zasobu dyskowego w celu przechowywania plików. W moim przypadku będzie to zasób NFS. Poniżej przykładowy prosty plik z konfiguracją naszego GitLab’a, zawierający kolejno wersje, porty, volumeny z których ma korzystać, dodatkową konfiguracje środowiska GitLab, samą konfiguracje volumenów  i na końcu pliki dodatkowe, które mają być wykorzystane podczas startu serwisu.

version: "3.6" services: gitlab: image: gitlab/gitlab-ce:latest ports: - "22:22" - "80:80" - "443:443" volumes: - gitlab_data:/var/opt/gitlab - gitlab_logs:/var/log/gitlab - gitlab_config:/etc/gitlab environment: GITLAB_OMNIBUS_CONFIG: "from_file('/omnibus_config.rb')" configs: - source: gitlab target: /omnibus_config.rb secrets: - gitlab_root_password gitlab-runner: image: gitlab/gitlab-runner:alpine deploy: mode: replicated replicas: 4 volumes: gitlab_data: driver: local driver_opts: type: nfs4 o: "addr=10.0.0.4" device: ":/gitlab-swarm/gitlab/data" gitlab_logs: driver: local driver_opts: type: nfs4 o: "addr=10.0.0.4" device: ":/gitlab-swarm/gitlab/logs" gitlab_config: driver: local driver_opts: type: nfs4 o: "addr=10.0.0.4" device: ":/gitlab-swarm/gitlab/config" configs: gitlab: file: ./gitlab.rb secrets: gitlab_root_password: file: ./root_password.txt

Oczywiście jest sporo sposobów na uruchomienie aplikacji w klastrze. Możemy wykorzystać loadbalancery, zewnętrzne bazy danych, inne typy volumenów oraz podzielić aplikacje na mniejsze części aby każda usługa działa jako osobny kontener. Ze względu na skalowanie najczęściej właśnie jako podstawa działania wykorzystywany jest Docker. Dodatkowo możemy wykorzystać Docker Machine do automatycznego skalowania, co czasem bywa przydatne. Oczywiście, kontenery to nie jedyna opcja. Możemy również wykorzystać instancje GitLab’a bezpośrednio zainstalowane w systemie operacyjnym. Wykorzystujemy również do tego celu zasób w postaci NFS oraz zewnętrznej bazy danych.

Na koniec jeszcze kilka przykładów wysoko dostępnej architektury GitLab’a. Wybór rozwiązania jak zawsze zależy od skali i naszych wymagań. Komponenty poniżej muszą zostać uwzględnione w środowisku skalowanym lub wysoce dostępnym. W wielu przypadkach komponenty można łączyć w tych samych węzłach, aby zmniejszyć złożoność.

  • Unicorn/Workhorse – Web-requests (UI, API, Git over HTTP)
  • Sidekiq – Asynchronous/Background jobs
  • PostgreSQL – Database
  • Consul – Database service discovery and health checks/failover
  • PGBouncer – Database pool manager
  • Redis – Key/Value store (User sessions, cache, queue for Sidekiq)
  • Sentinel – Redis health check/failover manager
  • Gitaly – Provides high-level RPC access to Git repositories

W wielu przypadkach wystarczy jeden serwer PostgreSQL, jeden serwer Redis, NFS, 2 lub więcej nodów z aplikacją GitLab oraz jeden node do monitorowania. Takie rozwiązanie zapewnia nam już pewną elastyczność, ale niekoniecznie wysoką dostępność. Skalowanie horyzontalne jest jedną z najprostszych sposobów zapewnienia wysokiej dostępności i wydajności aplikacji. Niestety tego typu rozwiązania wymagają kilku lub kilkunastu serwerów wirtualnych lub fizycznych.

Tak jak na powyższym schemacie widać, 3 serwery PostgreSQL, 2 serwery Redis, 3 serwery Consul/Sentinel, 3 serwery aplikacyjne, 1 serwer NFS oraz 2 loadbalancery. Taka architektura z zupełnością wystarczy niemal każdemu, ale oczywiście jeżeli wymagania są większe, istnieje taka możliwość. Pełna architektura skalowana jest do setek tysięcy użytkowników i projektów. Przynajmniej tak twierdzi sam producent. Jest to bazowa architektura samego GitLab.com. Niestety wymaga ona wielu elementów, które należy powielić, aby zapewnić nam wysoką dostępność i wydajność całego systemu. Poniżej widzimy schemat takiej architektury.

Jak widać skala zaczyna się robić ogromna. Doskonale widzimy ile pracy i jakim dużym przedsięwzięciem jest tworzenie takiego serwisu jak GitLab.com. Dodatkowo widzimy na ile sposobów możemy uruchomić GitLab’a w naszym środowisku. Wszystko jak zwykle zależy od nas, czy użyjemy modnych teraz kontenerów, czy będziemy używać bezpośrednio działających aplikacji/usług na serwerach. Widzimy również jak z małej usługi/aplikacji może nam urosnąć środowisko tak duże, że trudno jest nad nim zapanować dlatego też warto zastanowić się dwa razy zanim wbijemy po raz pierwszy łopatę.