Skalowanie i wysoka dostępność GitLab #2

Jak już wiemy GitLab posiada spore możliwości, jeżeli chodzi o obsługę sporej ilości użytkowników. W poprzednim wpisie wspomniałem, że po zwiększeniu vCPU czy pamięci operacyjnej RAM, możemy osiągnąć duży przeskok w postaci obsługiwanych użytkowników jednocześnie. Przy zastosowaniu już 4 rdzeni i 8GB RAM GitLab jest w stanie obsłużyć 2000 użytkowników. Idąc dalej 16 rdzeni i 32 GB RAM to już około 8000 użytkowników. Należy jednak pamiętać o dyskach na jakich leży GitLab. Przy większej ilości użytkowników warto się zastanowić czy nie zainwestować w dyski SSD. Jeżeli podane wielkości są dla nas wystarczające, sprawa wygląda na załatwioną.

W przypadku gdy chcemy zadbać o wysoką dostępność podczas awarii maszyny czy też serwerowni, możemy wykorzystać wbudowaną w GitLaba geo replikacje. Replika GitLab’a może być wykorzystywana do klonowania i pobierania projektów. Takie rozwiązanie przyspiesza pracę z dużymi repozytoriami na duże odległości. Gdy geo replikacja jest włączona, oryginalna instancja działa jako primary node, a zreplikowana jest w trybie read-only i działa jako secondary node.

Zasada wydaje się być prosta. Niestety są pewne ograniczenia i rzeczy, które musimy wykonać ręcznie, ale o tym za chwilę.

Jak wspomniałem wcześniej, cała architektura bazuje na primary i secondary node. Zapisy mogą być wykonywane tylko w pierwszym node. W drugim nodzie baza danych jest replikowana za pomocą tzw. Streaming replication (SR). Jeżeli wykorzystujemy serwer LDAP, również powinien być on replikowany na potrzeby Disaster Recovery. Do synchronizacji z pierwszym nodem wykorzystywana jest autoryzacja zabezpieczona przez JWT (JSON Web Token). Repozytoria są klonowane/aktualizowane przez Git za pomocą protokołu HTTPS. Załączniki, obiekty LFS (Lagre File Storage) i inne pliki są pobierane również za pomocą HTTPS, ale w tym przypadku wykorzystywane jest do tego interfejs API.

W przypadku najnowszej wersji GitLab’a istnieje kilka ograniczeń, o których należy pamiętać przy wybieraniu tego typu architektury. Oczywiście jeżeli używacie starszych wersji te ograniczenia mogą się różnić. Pierwszym ograniczeniem jest przekierowanie dla HTTP lub proxy (SSH) zapytań z drugiego do pierwszego noda podczas wykonywania git push. Drugie ograniczenie jest takie, że pierwszy nod musi być online, aby logowanie OAuth działało. Ograniczenia dotyczą również samej replikacji. Replikacji nie podlegają m.in. strony GitLab’a oraz sam GitLab nie dba o replikację treści w pamięciach obiektowych. W przypadku korzystania z Amazon S3 możemy wykorzystać do tego CRR (Cross-Region Replication), a w przypadku Google Cloud Storage możemy wykorzystać Multi-Region Storage. W innych przypadkach musimy korzystać z harmonogramu zadań i skryptów synchronizujących zawartość.

Zalecanym sposobem instalacji i konfiguracji georeplikacji jest użycie paczek Omnibus. Wszystkich, którzy uwielbiają instalacje ze źródeł muszę zmartwić. Korzystanie z Geo w instalacjach źródłowych było przestarzała i zostanie usunięte w przyszłej wersji. Dlatego warto rozważyć migrację do instalacji z pakietów Omnibus. Aby uruchomić geo replikacje musimy spełnić kilka warunków. Jedną z najbardziej istotnych jest posiadanie licencji. Wymagana jest licencja w wersji Premium lub wyższa. Na pierwszym serwerze musimy posiadać już zainstalowanego i skonfigurowanego GitLaba. Na drugim serwerze przeprowadzamy tylko „czystą” instalacje. Oznacza to, że po samej instalacji nie zmieniamy nawet hasła root’a. Oczywiście między serwerami musi działać komunikacja oznacza to, że porty 80, 443, 22 oraz 5432 muszą zostać dodane do wyjątków w naszych zaporach sieciowych. Gdy to wszystko posiadamy, przechodzimy do konfiguracji replikacji bazy danych.

Zaczynamy od pierwszego serwera. Logujemy się do niego za pomocą SSH.

  1. Na pierwszym serwerze logujemy się jako root:
sudo -i
  1. Wykonujemy polecenie definiujące, że to właśnie ten serwer będzie pierwszym serwerem
gitlab-ctl set-geo-primary-node
  1. Generujemy hasło dla użytkownika gitlab.
gitlab-ctl pg-password-md5 gitlab
  1. Należy pamiętać, że to hasło musi być ustawione w naszej bazie danych. Zazwyczaj jest ono puste, dlatego też należy się zalogować do bazy danych i nadać hasło użytkownikowi gitlab. W tym celu należy wykonać polecenia:
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h /var/opt/gitlab/postgresql -d gitlabhq_production
\password gitlab
select usename,passwd from pg_shadow where usename='gitlab';
  1. Przechodzimy do edycji pliku /etc/gitlab.gitlab.rb w nim zmieniamy:
postgresql['sql_user_password'] = 'fca0b89a972d69f00eb3ec98a5838484'
gitlab_rails['db_password'] = 'mypassword'
  1. Omnibus GitLab posiada już użytkownika do replikacji. Musimy mu nadać hasło ręcznie i w tym celu wpisujemy:
gitlab-ctl set-replication-password
  1. Kolejnym etapem jest konfiguracja Postgres’a aby nasłuchiwał na odpowiednim interfejsie oraz dodanie adresów IP, które mogą się łączyć w celu replikacji. Wszystko zmieniamy w pliku /etc/gitlab.gitlab.rb. W moim przypadku adres pierwszego serwera to 10.0.0.3, a drugiego 10.0.0.4. Gdy posiadamy więcej niż jeden serwer, do którego będzie odbywać się replikacja należy go wpisać jako kolejna pozycja przy ‘md5_auth_cidr_addresses’
roles ['geo_primary_role']
postgresql['listen_address'] = '10.0.0.3'
postgresql['md5_auth_cidr_addresses'] = ['10.0.0.3/32','10.0.0.4/32']
postgresql['max_replication_slots'] = 1
gitlab_rails['auto_migrate'] = false
  1. Po wszystkich tych zabiegach wykonujemy rekonfiguracje GitLab’a oraz restart serwera PostgreSQL.
gitlab-ctl reconfigure
gitlab-ctl restart postgresql
  1. W pliku /etc/gitlab.gitlab.rb włączamy migracje. W tym celu zmieniamy na true opcję:
gitlab_rails['auto_migrate'] = true
  1. Wykonujemy ponownie rekonfiguracje GitLaba.

W tym momencie mamy skonfigurowany pierwszy serwer na potrzeby replikacji. Po przejściu tych kroków warto sprawdzić, czy aby na pewno serwer PostgreSQL nasłuchuje na odpowiednim interfejsie i porcie. Jeżeli wszystko jest ok, należy skopiować plik z certyfikatem z pierwszego na drugi serwer. Certyfikat ten został automatycznie wygenerowany podczas konfiguracji i będzie wykorzystywany do zabezpieczenia połączenia z bazą danych.

  1. Przechodzimy do konfiguracji drugiego serwera. Proces rekonfiguracji jest tutaj trochę krótszy. Posiadając już skopiowany certyfikat z pierwszego serwera logujemy się jako root i zatrzymujemy serwer aplikacji i Sidekiq.
gitlab-ctl stop unicorn
 gitlab-ctl stop sidekiq
  1. Sprawdzamy czy możemy nawiązać połączenie z pierwszym serwerem poleceniem:
gitlab-rake gitlab:tcp_check[10.0.0.3,5432]

Jeżeli połączenie się nie powiedzie musimy sprawdzić adresy IP na jakich działają usługi oraz czy połączenie nie jest blokowane przez zaporę sieciową.

  1. Instalujemy certyfikat na potrzeby zabezpieczenia połączenia między serwerami.
install -D -o gitlab-psql -g gitlab-psql -m 0400 -T server.crt ~gitlab-psql/.postgresql/root.crt
  1. Wykonujemy test połączenia z bazą danych na pierwszym serwerze za pomocą polecenia:
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql --list -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W -h 10.0.03

W tym kroku podajemy hasło użytkownika gitlab_replicator Jeżeli jako wynik polecenia pojawi się lista baz danych z pierwszego serwera oznacza to, że połączenie jest nawiązane bez problemu.

  1. Uruchamiamy mechanizm FDW na potrzeby PostgreSQL’a oraz konfigurujemy adresy IP na jakich ma działac serwer bazy danych oraz hasła jakie zdefiniowaliśmy na pierwszym serwerze. Tutaj należy pamiętać, że hasło użytkownika gitlab jest puste i należy je ustawić jak w przypadku pierwszego serwera. Wszystkie zmiany wykonujemy w pliku /etc/gitlab.gitlab.rb.
roles ['geo_secondary_role']
postgresql['listen_address'] = 10.0.0.4
postgresql['md5_auth_cidr_addresses'] = ['10.0.0.4/32']
postgresql['sql_user_password'] = 'fca0b89a972d69f00eb3ec98a5838484'
gitlab_rails['db_password'] = 'mypassword'
geo_secondary['db_fdw'] = true
  1. Na końcu uruchamiamy rekonfiguracje GitLab’a oraz restartujemy serwer bazodanowy i znów rekonfigurujemy GitLab’a aby wszystkie zmiany zostały wdrożone.
gitlab-ctl reconfigure
gitlab-ctl restart postgresql
gitlab-ctl reconfigure
  1. Po tych wszystkich zabiegach inicjujemy replikacje pamiętając, że jeżeli posiadamy bardzo dużą bazę taka replikacja może zająć nawet 30min. Należy wtedy wydłużyć interwał oczekiwania na zakończenie replikacji. Replikacje inicjujemy poleceniem:
gitlab-ctl replicate-geo-database --slot-name=nazwa_replikacji --host=10.0.0.3

Przechodzimy do kolejnego etapu jakim jest autoryzacja do naszych serwerów. Standardowo demon SSH wyszukuje klucze do autoryzacji przez wyszukiwanie liniowe. Oznacza to, że przy dużej ilości użytkowników operacja ta może zająć sporo czasu. Jeżeli użytkownik często dodaje lub usuwa klucze, system operacyjny może nie mieć możliwości buforowania pliku authorized_keys, co powoduje wielokrotny odczyt z dysku. GitLab Shell rozwiązuje ten problem, zapewniając sposób autoryzacji użytkowników SSH poprzez szybkie indeksowane wyszukanie w bazie danych GitLab. Domyślnie GitLab zarządza plikiem authorized_keys, gdzie zapisane są wszystkie klucze publiczne użytkowników mających dostęp do GitLab’a. W przypadku geo replikacji wymagana jest konfiguracja opierająca się na wyszukiwaniu odcisku palca w bazie danych. W ramach konfiguracji poniższe kroki należy wykonać na obu serwerach, ale pole Write to “authorized keys” file musi być odznaczone tylko na pierwszym serwerze. Jeżeli działa replikacja bazy danych, wszystko zostaje zreplikowane na drugi serwer.

Po zalogowaniu na serwer czy to pierwszy czy to drugi edytujemy plik sshd_configi znajdujący się w /etc/ssh/ i dodajemy do niego zawartość

AuthorizedKeysCommand /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k
AuthorizedKeysCommandUser git

GitLab przechowuje w pliku /etc/gitlab/gitlab-secrets.json kilka wartości, które muszą być takie same na wszystkich węzłach. Dlatego też należy ten plik skopiować z pierwszego na drugi serwer. Wcześniej należy wykonać sobie kopię zapasową pliku na drugim serwerze. Na drugim serwerze wykonujemy kolejno plecenia:

mv /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab-secrets.json.`date +%F`

Kopiujemy w dowolny sposób plik z pierwszego serwera (możemy to zrobić za pomocą scp, rsync lub jakiegokolwiek innego narzędzia, które umożliwi nam przeniesienie pliku z jednego serwera na drugi. Gdy już mamy plik w docelowym miejscu nadajemy mu odpowiednie uprawnienia oraz uruchamiamy rekonfiguracje GitLab’a oraz restartujemy wszystkie usługi z nim związane.

chown root:root /etc/gitlab/gitlab-secrets.json
chmod 0600 /etc/gitlab/gitlab-secrets.json
gitlab-ctl reconfigure
gitlab-ctl restart

W przypadku wykonania przełączenia awaryjnego administrator systemu może wypromować drugi serwer, aby stał się podstawowym. W tym przypadku DNS musi zostać zaktualizowany, dodatkowo wszystkie żądania SSH do nowo promowanego węzła zostaną odrzucone z powodu niezgodności klucza hosta SSH. Aby temu zapobiec, klucze z pierwszego serwera muszą zostać zreplikowane do serwera zapasowego.

  1. W tym celu na zapasowym serwerze wykonujemy kopię zapasową plików za pomocą polecenia:
find /etc/ssh -iname ssh_host_* -exec cp {} {}.backup.`date +%F` \;
  1. Kopiujemy pliki z pierwszego serwera na drugi oraz nadajemy im odpowiednie uprawnienia
scp root@primary-node-fqdn:/etc/ssh/ssh_host_*_key* /etc/ssh
chown root:root /etc/ssh/ssh_host_*_key*
chmod 0600 /etc/ssh/ssh_host_*_key*
  1. Sprawdzamy na obu serwerach czy odciski palca są identyczne. Weryfikujemy również poprawność kluczy publicznych i prywatnych
for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done
for file in /etc/ssh/ssh_host_*_key; do ssh-keygen -lf $file; done
for file in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf $file; done
  1. Restartujemy usługę SSH
sudo service ssh reload

Teraz przechodzimy do długo wyczekiwanego momentu, czyli dodania drugiego serwera jako replika. Logujemy się na pierwszy serwer i już z graficznego interfejsu dodajemy zapasowy serwer.

  1. Przechodzimy do Admin Area > Geo

  1. Po dodaniu serwera logujemy się na drugą instancje i restartujemy usługę GitLab’a. Do sprawdzenia czy wszystko działa lub czy występują jakieś problemy z replikacją wykonujemy na obu serwerach polecenie gitlab-rake gitlab:geo:check. Poniżej przykładowe wyniki poleceń.
  2. Używanie Hashed Storage znacząco poprawia replikację geograficzną. Nazwy projektów i grup nie wymagają już synchronizacji między węzłami. Dlatego też warto ją uruchomić. Wchodzimy w Admin Area > Settings > Repository i zaznaczamy w Repository storage opcję Use hashed storage path for newly created and renamed projects.

Na koniec jeszcze uruchamiamy dostęp do Git’a za pomocą http/HTTPS. Znajdziemy ją w Admin Area > Settings

Teraz na pierwszym serwerze, możemy zobaczyć, czy wszystko działa poprawnie.

Te małe pomarańczowe ostrzeżenia to informacje o tym, że korzystam z http, a nie https, który jest zalecany. A co się dzieje na naszym drugim serwerze?

Samo uruchomienie replikacji między serwerami nie jest specjalnie trudne, ale czasem może przysporzyć bólu głowy. Możemy w ramach georeplikacji uruchomić kilka replik, co wydaje się być dobrym zabezpieczeniem. Po zakończeniu konfiguracji samo działanie serwerów jakby nieznacznie zwolniło. Z moich obserwacji widać, że podczas uruchamiania obu serwerów bardzo mocno zostaje obciążony CPU. Podobnie dzieje się w trakcie replikacji, ale w tym wypadku obciążenie jest mniejsze. Rozwiązanie jest bardzo ciekawe, jeżeli mówimy o rozproszeniu serwerów GitLab’a po cały świecie.

Na szczęście tego typu skalowanie nie jest jedyną możliwością GitLab’a. Kolejne możliwości skalowania w kolejnym wpisie!

Część III