Poznaj GitLab CI/CD od podstaw:
Automatyzacja wdrażania systemu jest aktualnym i zdecydowanie adekwatnym trendem. Oprócz szybszego procesu dostarczania nowych wersji aplikacji i możliwości łatwego wycofania zmian w przypadku nieprawidłowości zwiększamy też bezpieczeństwo środowiska — osoba odpowiedzialna za wdrożenie (np. programista) zasadniczo nie potrzebuje w takim podejściu dostępu do serwera.
Jednym z głównych rozwiązań stosowanych w tym celu jest GitLab. To niezwykle rozbudowane środowisko wymagające pewnej wstępnej konfiguracji. Właśnie ta złożoność jest także pewną zaletą — nie potrzebujemy osobno konfigurować repozytorium Git, container registry dla obrazów kontenerów, środowiska CI/CD czy choćby narzędzia do zarządzania projektami. Wszystko otrzymujemy w postaci jednego rozwiązania.
Oznacza to również, iż GitLab znajduje zastosowanie w różnych obszarach IT. Wystarczy przejrzeć dostępne na theprotocol.it oferty pracy, aby zauważyć, iż w wielu specjalizacjach jest to jedna z wymaganych umiejętności.
GitLab możemy uruchomić we własnej infrastrukturze, jak i korzystać z opcji SaaS oferowanych przez wydawcę. Ze szczegółami można zapoznać się na dedykowanej stronie.
W tym artykule omówimy sposób instalacji GitLab w systemie Ubuntu, podstawową konfigurację, integrację z Active Directory, utworzenie container registry dostępnego pod dedykowaną domeną, konfigurację runner’a i oczywiście tworzenie procesów CI/CD (własne skrypty oraz wykorzystanie zewnętrznego narzędzia).
Instalacja GitLab
Podstawowe wymagania sprzętowe to 4 GB pamięci RAM oraz zalecane 4 rdzenie CPU. Potrzebna przestrzeń na dysku jest zależna od wielkości naszych projektów, natomiast sama paczka instalacyjna po wypakowaniu zajmie około 2.5 GB miejsca. Te informacje wraz z pełnym wyjaśnieniem są dostępne w dokumentacji.
GitLab powinien być wdrażany na czystym systemie, ponieważ sama jego architektura jest w pewnym stopniu skomplikowana i złożona z kilku usług. Pozostała kwestia to wydajność, nie możemy oczekiwać całkowicie płynnego działania w przypadku mocno obciążonego środowiska.
Polecam przeznaczyć osobny dysk na katalog /var/opt/gitlab (można utworzyć partycję i zamontować ją przed instalacją), ponieważ w tej lokalizacji zapisywanych jest wiele danych, jak repozytoria i przesłane obrazy Docker. Procedury instalacji w zależności od środowiska zostały przedstawione w tym miejscu. Zamierzamy użyć systemu Ubuntu, czyli wystarczy wykonać poniższe polecenia:
Jako EXTERNAL_URL podajemy domenę, na której GitLab będzie dostępny. jeżeli od razu podamy ją z protokołem HTTPS, nastąpi próba wygenerowania certyfikatu Let’s Encrypt, co nie zadziała dla dostępnej lokalnie domeny. Tak samo jeżeli posiadamy wcześniej zakupiony czy wygenerowany certyfikat i klucz — lepiej na początku podać ten adres z protokołem HTTP.
Po poprawnym zainstalowaniu pakietu gitlab-ce zobaczymy poniższy output.
Wstępna konfiguracja
Część potrzebnych zmian możemy wdrożyć poprzez panel administratora, ale edytowanie plików z poziomu terminala i przeładowywanie konfiguracji poleceniem gitlab-ctl także będzie wymagane w wielu przypadkach. Na początek skonfigurujemy obsługę HTTPS, a także uruchomimy własne container registry pod domeną cr.test.local.
Głównym plikiem konfiguracyjnym jest /etc/gitlab/gitlab.rb i w nim modyfikujemy oraz dodajemy linie:
external_url 'https://gitlab.test.local' letsencrypt['enable'] = false nginx['redirect_http_to_https'] = trueregistry_external_url 'https://cr.test.local' registry_nginx['ssl_certificate'] = "/etc/gitlab/ssl/cr.test.local.crt" registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/cr.test.local.key"Ustawiliśmy adres z prefikem https, wyłączyliśmy opcję korzystania z certyfikatów Let’s Encrypt oraz wymusiliśmy przekierowanie ruchu HTTP na HTTPS. W celu uruchomienia container registry wskazaliśmy domenę, pod którą obrazy będą dostępne do pobrania i wysłania (docker pull, docker push) oraz podaliśmy lokalizacje pliku z certyfikatem i kluczem — musieliśmy jawnie zapisać te ścieżki, ponieważ domena jest inna.
Tworzymy następnie katalog /etc/gitlab/ssl i przenosimy do niego certyfikaty i powiązane klucze. Konfigurację możemy przeładować poleceniem sudo gitlab-ctl reconfigure.
W pliku /etc/gitlab/initial_root_password zapisane będzie hasło dla użytkownika root ważne przez 24 godziny. Korzystając z niego możemy zalogować się do GitLab.
Nie ma obowiązku używania konta root do administracji GitLab. Możemy dodać własnego użytkownika z rolą Administrator. Przechodzimy do listy Your work i wybieramy Admin Area, a następnie Users. Tam z kolei klikamy przycisk New user, po czym zobaczmy formularz dodawania użytkownika. Uzupełniamy pola Name, Username, Email, a jako Access level wybieramy Administrator.
Na wskazany adres e-mail zostanie wysłany link do resetu hasła. jeżeli jednak z jakiegoś powodu musimy manualnie ustawić tymczasowe hasło (po pierwszym zalogowaniu konieczna będzie zmiana hasła), to klikamy przycisk Edit i dwukrotnie wpisujemy hasło.
Integracja z Active Directory — logowanie domenowe
Aby umożliwić użytkownikom logowanie kontem domenowym w pliku /etc/gitlab/gitlab.rb ustawiamy opcję gitlab_rails[’ldap_enabled’] na wartość true oraz uzupełniamy szczegóły konfiguracji Active Directory w opcji gitlab_rails[’ldap_servers’]. Komentarze proponują dodanie konfiguracji bezpośrednio w tym bloku, natomiast wydaje się, iż czytelniej będzie umieścić wszystko w osobnym pliku:
Działająca konfiguracja wygląda zgodnie z poniższym przykładem:
Wartości powinny być ogólnie zrozumiałe i są typowe dla podobnych integracji. label w tym przypadku oznacza nazwę karty do logowania.
Najlepiej wcześniej zadbać o niewygasanie hasła i brak możliwości jego zmiany dla użytkownika podanego w bind_dn. jeżeli zapomnimy zmienić hasło, a ono wygaśnie, pozostali użytkownicy domenowi nie będą mogli uzyskać dostępu do GitLab. Przy tworzeniu użytkownika w dsa.msc wystarczy odznaczyć pierwszą opcję, a zaznaczyć kolejnie dwie.
Zmiany wdrażamy poleceniem sudo gitlab-ctl reconfigure.
Zanim przetestujemy działanie logowania, sugeruję jeszcze przejście do ustawień (Admin Area -> Settings -> General) i w sekcji Account and limit dezaktywację opcji Allow new users to create top-level groups. jeżeli ilość naszych użytkowników będziemy liczyć w setkach i każdy z nich będzie mógł zakładać grupy w „głównej przestrzeni”, to bardzo gwałtownie zostanie wprowadzony nieporządek.
Dodany wcześniej label jest widoczny oprócz domyślnego Standard służącego do logowania dla kont spoza domeny.
Można się domyślić, iż rola administratora w GitLab nie jest równoznaczna przykładowo z członkostwem w grupie Domain Admins AD. Oznacza to, iż jeżeli którykolwiek z użytkowników pochodzących z domeny powinien mieć uprawnienia administratora, musimy je manualnie ustawić z poziomu innego konta administracyjnego po pierwszym zalogowaniu tego użytkownika.
Tworzenie grup i projektów
W grupach możemy tworzyć projekty w pewien logiczny sposób ze sobą powiązane, np. wszystkie pisane w tej samej technologii czy oparte na tym samym systemie CMS. Grupy tworzymy wybierając Create a group ; Create group. Wystarczy podać jej nazwę.
Grupa może mieć członków, którzy po utworzeniu projektów w danej grupie będą do nich automatycznie dodawani. Aby przypisać członków do grupy, z poziomu jej widoku wybieramy Manage; Members; Invite members.
Tworzenie projektów również nie jest skomplikowane. Będąc w widoku grupy wybieramy zwyczajnie Create new project -> Create blank project. jeżeli projekt będziemy dodawać z innego miejsca, to proces jest analogiczny, jednak musimy wtedy określić docelową grupę z listy rozwijanej.
GitLab Runner
Kluczowym elementem procesów CI/CD w GitLab są runner’y. To dedykowane hosty z zainstalowanym narzędziem gitlab-runner i przystosowane do obsługi pipelines. Instalacja runner’a jest wyjątkowo prosta, a instrukcje krok-po-kroku możemy zobaczyć wchodząc w Admin Area -> CI/CD -> Runners i wybierając z listy (ikona trzech pionowych kropek) Show runner installation and registration instructions.
Najpierw warto jednak określić zastosowanie tego runner’a i wcześniej przygotować środowisko. Do dyspozycji mamy kilka rodzajów tzw. executors, np. ssh, shell, virtualbox, docker. Porównanie zostało przedstawione w dokumentacji dostępnej na tej stronie. Osobiście uważam, iż wybranie Docker’a to dobra decyzja, bo zapewnia największą elastyczność. Zwykle sprawniej i łatwiej jest utworzyć obraz kontenera niż cały serwer czy maszynę wirtualną.
Samo tworzenie obrazów, które następnie będą przez nas używane w CI/CD pipelines, może okazać się wartościowym zajęciem. Obrazy powinny być minimalne i zawierać tylko potrzebne narzędzia, co nierzadko wymaga stosowania metody „prób i błędów” oraz szukania różnych rozwiązań na podstawie zdobytych doświadczeń. W IT najważniejszy jest ciągły rozwój własnych kompetencji i zdobywanie nowej wiedzy, np. poprzez lekturę branżowych portali jak blog.theprotocol.it.
Jeśli zainstalowaliśmy Docker, możemy przystąpić do wykonywania poleceń widocznych w instrukcji.
W domyślnej konfiguracji obrazy będą pobierane za każdym razem z container registry, choćby gdy są w pełni aktualne. Nie jest to może najbardziej odpowiednie rozwiązanie, dlatego powinniśmy zmienić adekwatność pull_policy na wartość “if-not-present” w pliku /etc/gitlab-runner/config.toml (należy ją dodać w sekcji runners.docker).
Na koniec wykonujemy polecenie sudo gitlab-runner verify. Runner będzie już widoczny i dostępny do użycia w GitLab.
Korzystanie z container registry
Utworzyliśmy wcześniej własne container registry pod domeną cr.test.local. Możemy teraz przesyłać do niego zbudowane przez nas obrazy. Taką dobrą praktyką jest dodanie dedykowanej grupy, a w niej projektów odpowiadających poszczególnym obrazom. Różne wersje obrazów możemy oznaczać tagami i przechowywać w tych samych projektach. Proces jest dość standardowy.
Lokalizacja obrazu w GitLab jest definiowana na podstawie jego nazwy. jeżeli obraz zamierzamy mieć pod adresem cr.test.local/docker/php-deploy:8.2, to lokalnie nazywamy go właśnie cr.test.local/docker/php-deploy:8.2. Obrazy mogą przesyłać członkowie grupy / projektu z rolą Maintainer. Z kolei jeżeli inny członek chciałby użyć danego obrazu (w ramach CI/CD swojego projektu lub dany obraz pobrać) musi mieć jakąkolwiek rolę w projekcie z obrazem (nawet Guest).
Przesłane obrazy widoczne są po wejściu w Deploy -> Container Registry odpowiedniego projektu.
Tworzenie CI/CD — własne skrypty
GitLab zapewnia wyjątkowo wygodny sposób tworzenia CI/CD pipelines. adekwatnie zapoznanie się już z kilkoma przykładami pozwala zrozumieć ogólne koncepcje, a w konsekwencji umożliwia dodawanie coraz bardziej rozbudowanych przepływów.
Zaczniemy właśnie od podstawowego przeglądu tego procesu na przykładzie systemu CMS Made Simple. Do repozytorium przesłałem kod źródłowy zwykłej witryny dostępnej po zainstalowaniu tego systemu.
Na początku dodamy bardzo prosty stage, który zwyczajnie utworzy archiwum ZIP z zawartością repozytorium. W tym celu do pliku .gitlab-ci.yml (to nazwa głównego pliku konfiguracji CI/CD w GitLab) dodajemy poniższy kod:
Blok stages oznacza listę kolejnych etapów w przepływie CI/CD. Aktualnie jest tylko jeden, ponieważ nie wykonujemy żadnych złożonych kroków, a wyłącznie tworzymy archiwum z repozytorium. W kolejnym bloku określamy już podstawowe „założenia” zdefiniowanego wcześniej stage, czyli piszemy joba. Poszczególne opcje są następujące:
- stage — określenie nazwy stage
- when — tutaj on_success, czyli stage zostanie rozpoczęty automatycznie po każdym kolejnym przesłaniu kodu do repozytorium
- only — zawiera nazwy gałęzi, dla których stage będzie wykonywany
- image — nazwa wykorzystywanego obrazu Docker
- script — polecenia wykonywane w ramach stage’u (tutaj tylko komunikat echo, bo chociaż nie potrzebujemy w tej chwili nic wykonywać, to script musi się znajdować w definicji joba)
- artifacts — w paths podajemy ścieżkę względną do utworzenia archiwum, z kolei exclude zawiera ignorowanych listę plików i katalogów (.git/**/* oznacza całą zawartość katalogu .git)
Po przesłaniu pliku do repozytorium możemy przejść do Build -> Pipelines, aby zobaczyć historię wykonywanych jobów.
Przechodząc do poszczególnych stage będziemy mogli sprawdzić cały przebieg ich wykonywania. Prawdopodobnie będziemy musieli trochę poczekać na rozpoczęcie wykonywania, ponieważ dopiero dodaliśmy runner i będą na niego pobierane potrzebne obrazy wykorzystywane przez GitLab.
Po prawej stronie w sekcji Job artifacts możemy przeglądać i pobierać utworzone archiwa.
Pokazany powyżej zapis pliku YML może z czasem przestać być czytelny, szczególnie gdy będziemy dodawać kolejne złożone etapy do przepływów. YAML posiada możliwość użycia anchors. W naszym przypadku modyfikacja będzie wyglądać w ten sposób:
Wykonanie joba będzie miało dokładnie taki sam efekt.
Po praktycznym zapoznaniu ze strukturą YAML oraz konfiguracji CI/CD pipelines możemy utworzyć bardziej ambitny przykład, w którym na serwer prześlemy kod z repozytorium. Zmodyfikujemy stage build oraz dodamy nowy o nazwie deploy.
Definiujemy więc drugi stage:
W przypadku build komunikat echo zastępujemy poleceniem tar, który utworzy nam paczkę sources.tgz z kodem, wykluczając jednak pewne wrażliwe pliki i katalogi:
Znacznie modyfikujemy też opcję artifacts — zależy nam, aby w drugim etapie deploy była możliwość dostępu do utworzonej paczki.
Dodajemy też całą konfigurację dla deploy:
W tym miejscu wymagane są wyjaśnienia poszczególnych opcji. Można zauważyć, iż dodałem opcję before_script. Różnica pomiędzy tą opcją a script obejmuje nazwę — ich działanie jest analogiczne, wykonują się po kolei od góry do dołu. before_script ma znaczenie z punktu widzenia czytelności zapisu. W naszym przypadku w before_script zawarliśmy polecenia przygotowujące środowisko kontenera do przesłania paczki protokołem SSH. Instalujemy klienta tej usługi, inicjujemy agenta i przekazujemy do niego klucz prywatny. Dodatkowo dodajemy konfigurację do pliku ~/.ssh/config, która zapobiegnie standardowemu „pytaniu” przed pierwszym połączeniem z serwerem SSH — nie mamy możliwości bezpośredniej interakcji z kontenerem.
Natomiast w script wykonujemy już adekwatne polecenia służące do przesłania i wypakowania archiwum na serwerze.
when w jobie deploy ustawiłem na manual, aby nie rozpocząć przypadkowego wdrożenia aplikacji. Da nam to również czas na dodanie widocznych zmiennych connection, path i key dla zdalnego serwera, na którym uruchomimy CMS Made Simple. Dzięki użyciu dependencies GitLab pobierze nam utworzony w poprzednim jobie artefakt sources.tgz.
W Build -> Pipelines zobaczymy, iż job build został wykonany, a deploy czeka na manualne rozpoczęcie.
Należy dodać zmienne connection, path i key po przejściu w Settings -> CI/CD i rozwinięciu karty Variables. Ich zawartości można się domyślić:
- connection — nazwa i adres docelowego hosta, jak przy zwykłym połączeniu SSH (np. cmsms@10.0.0.30)
- path — docelowa lokalizacja, najpewniej document root dla naszej witryny (np. /home/www/cmsms/public_html)
- key — klucz prywatny powiązany z kluczem publicznym dodanym na docelowym serwerze
Teraz możemy uruchomić job i zobaczyć efekty działania.
Dane aplikacji zostały przesłane na serwer. Wystarczy zaimportować bazę danych oraz dodać zawartość pliku config.php. tar przy wypakowywaniu nadpisuje jedynie pliki zawarte w archiwum (tutaj w repozytorium), więc wszelkie dane dodane przez nas pozostaną nienaruszone. CMS Made Simple został z powodzeniem wdrożony.
Plik .gitlab-ci.yml jest znacznie dłuższy niż na początku. Ilość linii będzie się zwiększać wraz z dodawaniem nowych stage’y. Polecam wyodrębnić wszystkie dodawane etapy do innych plików zawartych np. w katalogu .gitlab. Służy do tego opcja include. Czyli w .gitlab-ci.yml pozostawiamy wyłącznie:
Natomiast do plików .gitlab/build.yml i .gitlab/deploy.yml przenosimy definicję jobów odpowiednio dla build i deploy.
Nie ma potrzeby, aby wszystkie polecenia prowadzące do wdrożenia aplikacji zapisywać w opcji script. Świetnym rozwiązaniem jest napisanie własnego skryptu, ustawienie prawa do wykonywania (można to osiągnąć zarówno w opcji script, jak i bezpośrednio na pliku w lokalnym repozytorium) i jego uruchomienie. Zmiany dodamy na osobnym branchu, a następnie wykonamy merge do głównej gałęzi master.
W celu utworzenia brancha wybieramy przycisk + i New branch.
Tworzymy plik deploy.sh ze skryptem do wdrożeń:
W deploy.yml zastępujemy całą opcję script jednym uruchomieniem skryptu deploy.sh. Z kolei w build.yml dodajemy plik do –exclude polecenia tar. Po wysłaniu zmian na branch deploy możemy utworzyć merge request.
Oczekujące merge request’y można sprawdzić w odpowiedniej zakładce w projekcie. Po zaakceptowaniu zmiany zostaną dodane do głównej gałęzi.
Tworzenie CI/CD — Deployer
Nie zawsze skrypty podobne do tego z powyższych przykładów będą wystarczające. Przy większych projektach zachodzi potrzeba użycia bardziej zaawansowanych rozwiązań. Bardzo dobrym wyborem będzie Deployer. Pomimo tego, iż jest napisany w PHP, nic nas nie ogranicza przed użyciem Deployer do wdrożeń aplikacji napisanych w innych językach — jedynie kontener używany do deploy’a będzie musiał obsługiwać PHP i posiadać zainstalowany rsync.
Deployer zawiera szereg gotowych konfiguracji (nazwanych recipes) dla najpopularniejszych CMS czy frameworków. Możemy również napisać własne recipes, co nie jest zbyt skomplikowane, aczkolwiek przyda się podstawowa znajomość instrukcji PHP. Sam Deployer ma wbudowanych kilka poleceń. Zachęcam do ich przetestowania we własnym zakresie, natomiast pełna wiedza wszystkich funkcjonalnościach nie jest absolutnie wymagana do użycia Deployer z GitLab CI/CD.
Podczas wdrożenia z wykorzystaniem Deployer tworzona jest charakterystyczna struktura katalogów:
- releases — zawiera dziesięć (domyślna wartość) ostatnio wdrożonych wersji aplikacji oznaczonych kolejnymi liczbami
- shared — katalog na dane “przechodzące” pomiędzy kolejnymi wydaniami, np. pliki konfiguracyjne czy katalogi przeznaczone na przesłane grafiki
- .dep — katalog pomocniczy Deployer zawierający historię wydań
- release — tymczasowy link symboliczny do aktualnie wdrażanej wersji z katalogu releases
- current — link symboliczny do aktualnej wersji z katalogu releases (w idealnych przypadkach to zawsze najnowsza wersja)
Powyższa struktura jest zakładana w katalogu zdefiniowanym jako deploy_path. Ma to znaczenie w kontekście ustawiania document root dla naszej witryny. jeżeli z jakiegoś powodu nie mamy możliwości zmiany tej lokalizacji, zawsze można utworzyć symlink do <deploy_path>/current.
Spróbujemy zautomatyzować wdrożenia systemu WordPress. Deployer w projekcie zainstalujemy poprzez Composer. Toteż tworzymy plik composer.json.
Zawartość .gitlab-ci.yml będzie taka sama jak poprzednio:
W pliku build.yml jedyne zmiany to dodanie deploy.php do –exclude polecenia tar i (ewentualnie) zmiana paths artefaktu.
GitLab posiada kilka wbudowanych zmiennych. $CI_JOB_NAME jest jedną z nich.
Zdecydowanie więcej modyfikacji wymaga deploy.yml.
Mój obraz php-deploy:8.2 oparłem na oficjalnym obrazie php:8.2. Zainstalowałem pakiety git, unzip, libzip-dev i rsync, rozszerzenie php-zip (w Docker wystarczy użyć docker-php-ext-install zip) oraz Composer. To wystarczy do pobrania Deployer poprzez Composer i wykonania wdrożenia.
W script używamy sed do modyfikacji pliku deploy.php (kod widoczny poniżej). Nie powinniśmy zapisywać bezpośrednio w nim tych trzech wartości zmiennych.
Ustawienie http_user nie jest niezbędne, ponieważ Deployer na podstawie listy procesów będzie próbował sam ustalić nazwę użytkownika, na którego prawach działa proces serwera WWW. jeżeli jednak widoczność procesów została ograniczona, to sprawdzenie się nie powiedzie, dlatego w tym wypadku należało zapisać tę nazwę w deploy.php. Celem tego sprawdzenia jest ustawienie własności katalogów zdefiniowanych jako writable_dirs (w recipe); domyślnie używane jest do tego polecenie setfacl.
Domyślnie też Deployer zwyczajnie używa git archive w funkcji update_code. Nam jednak zależy na przesłaniu danych utworzonych przez GitLab opcją artefacts, dlatego konieczne było nadpisanie tej funkcji. upload stosuje rsync do przesłania archiwum code.tgz, następnie poleceniem tar wyodrębniamy zawartość, a na koniec usuwamy archiwum i plik composer.json ze zdalnego serwera.
Dodajemy variables do naszego projektu. Tym razem zmienną connection z poprzednich przykładów zastąpiliśmy osobnymi user i host — w PHP można co prawda użyć funkcji explode wyodrębnienia potrzebnych informacji z jednej zmiennej, ale przedstawiony sposób jest łatwiejszy. Proszę zwrócić uwagę na zmienną path — w deploy_path dodajemy do podanej w variables ścieżki /wp, więc path nie zawiera ostatecznej lokalizacji.
Job powinien zakończyć się sukcesem.
Po uzupełnieniu pliku wp-config.php (z katalogu wp/shared) i zaimportowaniu bazy danych widzimy, iż WordPress został poprawnie wdrożony.
Możemy też potwierdzić istnienie opisanej wcześniej struktury katalogów Deployer.
Podsumowanie
GitLab to świetne narzędzie, które spełnia swoją rolę. Umiejętność tworzenia CI/CD pipelines zawarta w naszym CV i potwierdzona w praktyce na pewno zostanie doceniona przez pracodawców, także z obszaru security.