Skalowanie i wysoka dostępność GitLab #3

Zenon Matuszyk
26. marca 2019
Reading time: 12 min
Skalowanie i wysoka dostępność GitLab #3

Wszyscy, albo prawie wszyscy uwielbiamy konteneryzację. Co więcej coraz więcej aplikacji jest przygotowywana, aby je uruchomić w kontenerze. Nie jest inaczej w przypadku GitLab’a. Dostępny jest on jako gotowa paczka do uruchomienia w środowiskach Docker czy Kubernetes. Wróćmy jeszcze do samych kontenerów. Myślę, że warto przypomnieć czym są i jak działają.

Kontenerem określamy jakąś jednostkę oprogramowania, która „pakuje” kod i wszystkie jego zależności. Dzięki takiemu zabiegowi aplikacja działa szybko i niezawodnie, niezależnie od środowiska. Sam obraz takiego kontenera jest małym, samodzielnym pakietem zawierającym wszystko co jest mu potrzebne do uruchomienia aplikacji np. kod, środowisko wykonawcze, narzędzia systemowe, biblioteki i ustawienia.  Same obrazy kontenerów stają się kontenerami w czasie wykonywania. Kontenery izolują oprogramowanie od środowiska i zapewniają jego identyczne działanie niezależnie od infrastruktury. Zasady działania kontenerów i maszyn wirtualnych są zbliżone do siebie. W obu przypadkach ich zaletą jest izolacja zasobów i alokacji, ale działają inaczej, ponieważ kilka kontenerów może działać na tym samym komputerze i współużytkować jądro systemu z innymi kontenerami. W przypadku maszyn wirtualnych jest wirtualizowany sprzęt, aby uruchomić na nim system operacyjny. Dzięki temu kontenery są bardziej przenośne i wydajne. Tak przy okazji słowo Docker, bo jego właśnie ten opis dotyczy pochodzi od brytyjskiego potocznego znaczenia dock worker – kogoś kto prowadzi rozładunek i załadunek statków. Podobnie jak w przypadku GitLab’a, Docker posiada również darmową wersję (CE – Community Edition) i płatną posiadającą więcej możliwości (EE – Enterprise Edition). Darmowa wersja posiada siedmiomiesięczny tryb wsparcia. Hotfixy i aktualizacje są wydawane dla najnowszej wersji, a jeżeli posiadamy jakiś problem to musimy się zwrócić to społeczności na forum Docker’a. Płatna wersja to już 24-miesięczny lub dłuższy okres wsparcia dla oprogramowania. Wszelkie aktualizacje i hotfixy są dostarczane dla wszystkich wersji i co może być przydatne – jeżeli mamy problem możemy zwrócić się bezpośrednio do supportu, z którym na pewno w jakiś sposób znajdziemy rozwiązanie.

Wróćmy teraz do GitLab’a. Jak już wspomniałem wcześniej, GitLab posiada dedykowany obraz na potrzeby Docker’a. Oba obrazy GitLab CE i EE są dostępne w Docker Hub. Możemy je „pobrać” za pomocą poleceń:

docker pull gitlab/gitlab-ce
docker pull gitlab/gitlab-ee

Obraz GitLab Docker jest obrazem jednolitym zawierającym GitLab’a oraz wszystkie niezbędne usługi, aby uruchomić go w pojedynczym kontenerze. Jeżeli chcemy użyć najnowszego obrazu zmieniamy nazwę na gitlab/gitlab-ee:latest. Jeśli chcemy używać najnowszego obrazu RC (Release Candidate) wystarczy użyć gitlab/gitlab-ee:rc lub gitlab/gitlab-ce:rc.

Obraz GitLab Docker możemy uruchomić na kilka sposobów:

  • Uruchomienie obrazu w Docker Engine;
  • Instalacja GitLab w klastrze;
  • Instalacja GitLab używając docker-compose.

Przechodząc do samego uruchomienia/instalacji GitLab’a należy wcześniej zapoznać się z samymi wymaganiami instalacji Docker’a i GitLab’a. Zaleca się wykorzystanie natywnej instalacji, a nie Docker Toolbox, aby móc używać wolumenów. Oczywiście jeżeli chcemy uruchomić Docker for Windows możemy napotkać wiele problemów np. jak zrealizować uprawienia do wolumenów. Dlatego też oficjalnie GitLab nie wspiera takiej instalacji. Tutaj odsyłam do forum, IRC itp. Tam na pewno znajdziecie ludzi, którzy wam pomogą.

Samo powołanie do życia kontenera i tym samym naszej instancji GitLab’a jest niezwykle proste. Wystarczy wykonać polecenie:

docker run --detach 
  --hostname gitlab.lab 
  --publish 443:443 --publish 80:80 --publish 3022:22 
  --name gitlab 
  --restart always 
  --volume /Users/gitlab/Desktop/gitlab/config:/etc/gitlab 
  --volume /Users/gitlab/Desktop/gitlab/logs:/var/log/gitlab 
  --volume /Users/gitlab/Desktop/gitlab/data:/var/opt/gitlab 
  gitlab/gitlab-ce:latest

lub gdy wykorzystujemy SELinux

docker run --detach 
  --hostname gitlab.lab 
  --publish 443:443 --publish 80:80 --publish 3022:22 
  --name gitlab 
  --restart always 
  --volume /Users/gitlab/Desktop/gitlab/config:/etc/gitlab:Z 
  --volume /Users/gitlab/Desktop/gitlab/logs:/var/log/gitlab:Z 
  --volume /Users/gitlab/Desktop/gitlab/data:/var/opt/gitlab:Z 
  gitlab/gitlab-ce:latest

Powyższe polecenia pobiorą nam i uruchomią GitLab’a CE w kontenerze. Zostaną również opublikowane porty na potrzeby uzyskania dostępów do SSH, HTTP i HTTPS. Wszystkie dane będą przechowywane jako podkatalogi /srv/gitlab/. Kontener zostanie również automatycznie uruchomiony po restarcie maszyny, na której się znajduje. Opcja –detach spowoduje uruchomienie kontenera w tle i wyświetlenie jego ID. Należy pamiętać o odpowiednich uprawnieniach dla procesu Docker, aby mógł utworzyć pliki konfiguracyjne w zamontowanych wolumenach. Teraz przyjrzyjmy się danym, gdzie są składowane. Kontener GitLab wykorzystuje wolumeny zamontowane na hoście do przechowywania danych „trwałych”. Poniżej tabela prezentująca podstawowe lokalizacje używane w kontenerze oraz lokalnie. Wszystkie te dane można dowolnie modyfikować w zależności od potrzeb.

Lokalizacja lokalna Lokalizacja w kontenerze Wykorzystanie
/srv/gitlab/data /var/opt/gitlab Przechowywanie danych aplikacji
/srv/gitlab/logs /var/log/gitlab Przechowywanie logów
/srv/gitlab/config /etc/gitlab Przechowywanie konfiguracji

Nasz kontener wykorzystuje oficjalnie paczkę Omnibus GitLab. Dlatego też całą rekonfigurację przechowuje w pliku konfiguracyjnym /etc/gitlab/gitlab.rb. Aby uzyskać dostęp do tego pliku możemy wykorzystać sesję powłoki w kontekście działającego kontenera. Tego typu rozwiązanie zapewni nam dostęp do plików wewnątrz kontenera oraz wykorzystanie narzędzi, z których chcemy korzystać np. do edycji plików konfiguracyjnych. Poniżej polecenie, które należy wykonać, aby dostać się to kontenera.

docker exec -it gitlab /bin/bash

W przypadku edycji pliku gitlab.rb należy pamiętać o ustawieniu external_url na właściwy URL, który nas interesuje. Kontener GitLab’a nie posiada wbudowanego serwera SMTP, dlatego też, jeżeli chcemy mieć możliwość wysyłania powiadomień, należy skonfigurować opcje poniżej.

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.server"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = "smtp user"
gitlab_rails['smtp_password'] = "smtp password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com'

Dodatkowo warto włączyć HTTPS. Można wykorzystać swój certyfikat lub można skonfigurować darmowy certyfikat Let’s Encrypt oraz zautomatyzować jego odnowienie. Oczywiście po wszystkich zmianach wymagany jest restart kontenera przez polecenie:

docker restart gitlab

W przypadku uruchomienia kontenera możemy wykonać jego konfigurację już podczas samego powoływania do życia. Dodając odpowiednie zmienne środowiskowe podczas wykonywania polecenia docker run możemy ustawić zewnętrzny URL czy uruchomić LFS. Zmienna, o której mówię to GITLAB_OMNIBUS_CONFIG. Może zawierać dowolne ustawienia, które zostaną sprawdzone przed załadowaniem pliku gitlab.rb do kontenera. W taki sposób możemy tworzyć dowolną konfigurację np. na potrzeby szablonów. Należy pamiętać, że zmienne zawarte w GITLAB_OMNIBUS_CONFIG nie zostaną zapisane w pliku konfiguracyjnym. Dlatego też muszą one zostać podane każdorazowo przy uruchomieniu kontenera. Opcje te też nie są zachowywane między kolejnymi uruchomieniami.  Poniżej przykładowe polecenie uruchamiające kontener z naszymi zmiennymi środowiskowymi.

docker run --detach 
  --hostname gitlab.lab 
  --env GITLAB_OMNIBUS_CONFIG="external_url 'http://localhost/'; gitlab_rails['lfs_enabled'] = true;" 
  --publish 443:443 --publish 80:80 --publish 3022:22 
  --name gitlab 
  --restart always 
  --volume /Users/gitlab/Desktop/gitlab/config:/etc/gitlab 
  --volume /Users/gitlab/Desktop/gitlab/logs:/var/log/gitlab 
  --volume /Users/gitlab/Desktop/gitlab/data:/var/opt/gitlab 
  gitlab/gitlab-ce:latest

Po pierwszym uruchomieniu kontenera wchodzimy na adres IP lub URL, który podaliśmy w konfiguracji. Ze względu na to, że proces uruchomienia takiego kontenera trwa dłuższą chwilę, musimy uzbroić się w cierpliwość. Dodatkowo żadne informacje na temat stanu uruchomienia nie są wyświetlane, dlatego też warto podejrzeć log, w którym widzimy co się dzieje i jakie ewentualne problemy pojawiają się podczas startu. Wykorzystujemy do tego celu polecenie poniżej. Jeżeli wszystko przejdzie bezproblemowo przy pierwszym logowaniu zostaniemy poproszeni o nadanie hasła root’a do GitLaba.

docker logs -f gitlab

Przejdźmy teraz do aktualizacji naszego kontenera. Sprawa wygląda i jest bardzo prosta. Ze względu na to, że dane przechowywane są poza kontenerem (ogólna koncepcja kontenerów polega właśnie na tym, aby kontener był bezstanowy, czyli nie posiadał żadnych stałych danych zapisanych wewnątrz) wystarczy kolejno zatrzymać kontener, następnie usunąć go i „pobrać” najnowszy obraz GitLab’a. Kolejno polecenia niżej.

docker stop gitlab
docker rm gitlab
docker pull gitlab/gitlab-ce:latest

Uruchamiamy ponownie kontener z określonymi parametrami (polecenie poniżej) i przy pierwszym uruchomieniu GitLab zostanie wszystko automatycznie skonfigurowane i zaktualizowane.

docker run --detach 
 --hostname gitlab.lab 
 --publish 443:443 --publish 80:80 --publish 3022:22 
 --name gitlab 
 --restart always 
 --volume /Users/gitlab/Desktop/gitlab/config:/etc/gitlab 
 --volume /Users/gitlab/Desktop/gitlab/logs:/var/log/gitlab 
 --volume /Users/gitlab/Desktop/gitlab/data:/var/opt/gitlab 
 gitlab/gitlab-ce:latest

Wszystko to co teraz przedstawiłem dotyczyło pojedynczego kontenera zawierającego aplikację GitLab. Teraz przejdźmy do instalacji i uruchomienia GitLab’a za pomocą Docker compose. Compose jest narzędziem, za pomocą którego możemy zdefiniować i uruchomić aplikacje z wieloma kontenerami Docker. Do konfiguracji aplikacji wykorzystujemy plik YAML. Następnie za pomocą jednego polecenia tworzymy i uruchamiamy wszystkie usługi z naszej konfiguracji. Compose można wykorzystywać we wszystkich środowiskach: produkcyjnych, testowych czy developerskich jako CI (continuous integration). Wykorzystanie Compose składa się przeważnie z trzech etapów: definiowanie środowiska aplikacji, zdefiniowanie usługi, które tworzą aplikacje i skomponowanie i uruchomienie całej aplikacji. Zdefiniowanie środowiska aplikacji polega na utworzeniu odpowiedniego i elastycznego środowiska, aby można było go uruchomić w dowolnym miejscu poprzez zdefiniowanie usługi rozumiemy uruchomienie wszystkich aplikacji, które są wymagane w odizolowanym środowisku, a ostatnim etapem jest uruchomienie tych wszystkich elementów, aby tworzyły całość. Compose posiada wszystkie niezbędne elementy do zarządzania całym cyklem życia aplikacji, tj.:

  • Uruchom, zatrzymaj i przebuduj usługę;
  • Wyświetl stan uruchomionych usług;
  • Wyświetl log uruchomionych usług;
  • Uruchom jednorazowe polecenie w usłudze.

Podstawowymi funkcjami, które oferuje nam Compose’r jest uruchomienie wielu odizolowanych środowisk na pojedynczym hoście, zachowanie danych na wolumenie, kiedy kontener jest tworzony, wykorzystanie pamięci podręcznej konfiguracji do utworzenie kontenera, czy wykorzystanie plików konfiguracyjnych do szybkiej modyfikacji usług na potrzeby innych środowisk lub użytkownika. Docker-compose możemy wykorzystywać w środowiskach deweloperskich do automatycznego testowania środowisk (jest ważną częścią CI lub CD), czy do wdrożeń na pojedynczych hostach, o czym już wspomniałem wcześniej.

Teraz już wiemy coś na temat narzędzia, więc przejdźmy do jego użycia. Oczywiście musimy przeprowadzić instalacje Docker Compose jeżeli nie posiadamy go w systemie. Poniżej kilka poleceń jak szybko pobrać i wykorzystać to narzędzie. Przykładem będzie instalacja na systemie Linux, ponieważ narzędzie to jest już zintegrowane z instalatorami na system operacyjny Windows czy Mac.

curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version

Pobieramy z github.com najnowszą wersję i wrzucamy ją w odpowiednie miejsce. Nadajemy uprawnienia, podlinkowujemy katalog, aby polecenie było widoczne w systemie i sprawdzamy czy wszystko nam działa poprzez sprawdzenie aktualnej wersji docker-compose. Jeżeli to już nam działa, to przechodzimy do utworzenie pliku yml na potrzeby GitLab’a. Poniżej przykładowa zawartość pliku docker-compose.yml.

web:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   hostname: 'localhost'
   environment:
     GITLAB_OMNIBUS_CONFIG: |
       external_url 'https://localhost’
   ports:
     - '80:80'
     - '443:443'
     - '3022:22'
   volumes:
     - '/Users/gitlab/Desktop/gitlab/config:/etc/gitlab'
     - '/Users/gitlab/Desktop/gitlab/logs:/var/log/gitlab'
     - '/Users/gitlab/Desktop/gitlab/data:/var/opt/gitlab'

Aby uruchomić aplikację należy znajdować się w tej samej lokalizacji co plik yml i wykonać polecenie docker-compose up -d. Oczywiście parametry w opcji GITLAB_OMNIBUS_CONFIG możemy dowolnie zmieniać, podobnie jak resztę konfiguracji. W przypadku aktualizacji GitLab’a za pomocą Docker Compose należy wykonać polecenia docker-compose pull i docker-compose up -d. To spowoduje pobranie nowych wersji i aktualizację instancji GitLab.

Te wszystkie konfiguracje i uruchomienie GitLaba jako kontener miały nam przybliżyć możliwości samych kontenerów i ich wspaniałego działania. Dzięki temu, że wiemy jak działają kontenery, możemy przejść do czegoś większego, czyli do uruchomienia instancji GitLaba na kilku hostach. Do tej pory działaliśmy w obrębie jednego serwera z uruchomionym Dockerem. Uruchamialiśmy aplikacje tak, aby w razie problemu była zawsze uruchamiana. Niestety jak zwykle takie rozwiązania oparte na jednym serwerze mają to do siebie, że gdy zawiedzie sprzęt lub system upadnie na kolana i nie będzie się mógł z nich podnieść, nie mamy dostępu do naszych cennych kodów źródłowych. Tracimy czas na postawienie serwera na nogi zamiast pisać kolejne linie kodu, aby zarabiać $$$. Dlatego też z pomocą przychodzi nam np. Docker Swarm. Kontenery uruchomione w „roju” (ciekaw jestem kto wymyśla te nazwy) rozrzucone są na kilka serwerów, co prowadzi do większej niezawodności i mniejszego przestoju. Ale to już temat na kolejny artykuł…