GitLab pipelines

Michał Nowak

Ostatnimi czasy bardzo popularne jest podejście CI/CD – chcemy jak najszybciej wypuszczać i wdrażać nasze zmiany. Dzięki mikroserwisom i dzieleniu projektów na mniejsze, niezależne części jest to znacznie łatwiejsze. GitLab pipelines sprawia, że możemy mieć wszystko w jednym miejscu, a także budować, testować, wdrażać itp. W tym artykule pokażę, jak działają pipeline’y na przykładzie prostego projektu Spring Boot.

Przewidywania dla projektu

Stworzę prosty projekt Spring Boot, aby zobaczyć, jak działają GitLab pipelines. Będzie miał on kilka testów, co pozwoli nam zobaczyć, jak wszystko jest procesowane.

  • Jeśli nie korzystasz z GitLab, możesz założyć darmowe konto pomocne w nauce. Darmowy plan daje wystarczającą liczbę funkcji do prowadzenia ćwiczeń i zrozumienia CI/CD.

Aplikacja Spring Boot Rest

Mam prostą aplikację Maven. Teraz chcę przygotować plik jar i uruchomić unit test. Lokalnie na urządzeniu można to zrobić za pomocą polecenia maven mvn install. Co jednak, jeśli chcę mieć zautomatyzowany proces budowania, testowania i wdrażania aplikacji? Wtedy mogę utworzyć strukturę CI/CD przy użyciu GitLab pipelines.

  • Pipeline to najwyższy poziom komponentu ciągłej integracji, dostarczania i wdrażania.
  • Pipeline’y obejmują:
    • Joby – definiują, co należy zrobić, np. kompilujące lub testujące kod.
    • Stages – definiują, kiedy uruchamiać joby, np. uruchamiające testy po etapie kompilacji kodu.
  • Każdy pipeline, który będzie działał, potrzebuje środowiska zwanego runnerRunner jest pewnego rodzaju systemem, w którym można uruchamiać nasze pipeline’y. Oczywiście zależy to od typu aplikacji (np. Java, Terraform, Nodejs). Potrzebujemy różnych środowisk, dlatego w tym celu używamy obrazów Docker.
  • Runner to wirtualny system operacyjny, np. rodzaj Linuksa, na którym zainstalowałem Dockera, a teraz, kiedy uruchamiam swoje pipeline’y, mogę użyć każdego obrazu Dockera do ich uruchomienia.

Oto prosta struktura:

Oczywiście możemy mieć więcej niż jednego runnera. Dzięki temu możemy uruchamiać wiele pipeline’ów w tym samym czasie i równolegle jobów na tym samym etapie (jeśli nie ma między nimi zależności).

Cała konfiguracja naszych potoków jest zdefiniowana w pliku .gitlab-ci.yml.

Zobacz przykładowy pipeline .gitlab-ci.yml:

stages:
  - build

maven-build-jdk-11:
  image: maven:3-jdk-11
  stage: build
  script: "mvn package -B"
  artifacts:
    paths:
      - target/*.jar

W tym przypadku mam stage build z jednym jobem maven-build-jdk-11. Uruchamiam więc ten pipeline na runnerze. Następnie runner pobierze obraz Dockera z Javą i Mavenem:

image: maven:3-jdk-11

Co dalej? Na uruchomionym kontenerze uruchamiam polecenie…

script: "mvn package -B"

…i używam paths, aby połączyć się z miejscem docelowym dockera na zewnątrz (podobnie jak w przypadku docker volume).

artifacts:
    paths:
      - target/*.jar

Robię to, ponieważ chcę, aby inne joby miały dostęp do utworzonego pliku jar, ponieważ po zakończeniu joba kontener dokera traci wszystkie dane.

Cały proces możesz śledzić w konsoli GitLab:

Jeśli klikniesz na job (2), zobaczysz wszystkie joby:

Jeśli teraz klikniesz na job, zobaczysz cały proces:

Wiele jobów w jednym stage’u

Teraz pokażę następny przykład z wieloma jobami w jednym stage’u. Aby go uprościć, joby będą łatwymi poleceniami. Spójrz:

stages:
  - build

maven-build-jdk-11:
  image: maven:3-jdk-11
  stage: build
  script: "mvn package -B"
  artifacts:
    paths:
      - target/*.jar

job_2:
  stage: build
  script: "ls -l"

W tym kodzie joby były uruchamiane równolegle, ponieważ nie było między nimi zależności.

Wynik job2:

Pora, aby rozważyć podobny przypadek z zależnością. Jak to zrobić? Dodaj do job2 słowo kluczowe, czyli needs:

job_2:
  stage: build
  script: "ls -l"
  needs:
    - maven-build-jdk-11

Teraz job czeka na wystartowanie, dopóki maven-build-jdk-11 nie zostanie ukończony.

Zobacz wynik. Ponieważ czekamy na maven-build-jdk-11, mamy folder docelowy:

Stages

Przyszedł moment, aby przenieść job2 do innego stage’u. Na tym etapie wrzucę utworzony plik jar z poprzedniego etapu build do bucketa AWS S3. Wcześniej muszę skonfigurować zmienne CI z uprawnieniami do mojego AWS. Aby to osiągnąć, trzeba najpierw wprowadzić ustawienia zmiennych CI:

Muszę dodać trzy zmienne:

  • AWS_ACCESS_KEY_ID;
  • AWS_SECRET_ACCESS_KEY;
  • AWS_DEFAULT_REGION.

Wartości dla tych zmiennych zostaną nadane przez IAM w AWS.

Zobacz teraz, jak wygląda nowy stage job:

stages:
  - build
  - deploy

maven-build-jdk-11:
  image: maven:3-jdk-11
  stage: build
  script: "mvn package -B"
  artifacts:
    paths:
      - target/*.jar

job_2:
  image:
    name: amazon/aws-cli
    entrypoint: [""]
  stage: deploy
  before_script:
    - export AWS_ACCESS_KEY_ID=$TEST_AWS_ACCESS_KEY_ID
    - export AWS_SECRET_ACCESS_KEY=$TEST_AWS_SECRET_ACCESS_KEY
    - export AWS_DEFAULT_REGION=$TEST_AWS_DEFAULT_REGION
  script:
    - aws --version
    - aws s3 cp foo.bar s3://bucketmn/foo.txt

W tym przypadku w job_2 użyję obrazu docker amazon/aws-cli, aby mieć AWS cli. W tej części kodu będzie to wyglądać następująco:

  before_script:
    - export AWS_ACCESS_KEY_ID=$TEST_AWS_ACCESS_KEY_ID
    - export AWS_SECRET_ACCESS_KEY=$TEST_AWS_SECRET_ACCESS_KEY
    - export AWS_DEFAULT_REGION=$TEST_AWS_DEFAULT_REGION

Musiałem skopiować poprzedni zestaw zmiennych do kontenera docker. Następnie wywołuję polecenia związane z AWS cli:

    - aws --version
    - aws s3 cp foo.bar s3://bucketmn/foo.txt

Dwa etapy:

Jak widać, mam teraz dwa stage. Wynik uruchomionego joba to:

Skopiowałem jar wygenerowany w poprzednim jobie do bucketa s3 z nową nazwą spring.jar.

Zmienne

Wcześniej ustawiłem wartości uwierzytelniania AWS jako zmienne. Tego typu zmienne są ustawione na stałe i uzyskuje się do nich dostęp za każdym razem, gdy w projekcie uruchamiany jest pipeline. Można jednak ręcznie ustawić inną zmienną przed każdym uruchomieniem pipeline’u. Jak to zrobić? Wystarczy kliknąć Run Pipeline:

Tutaj wybieram branch, na którym chcę uruchomić pipeline i ustawiam zmienną:

Aby uzyskać wartość tej zmiennej, używam $. Tę zmienną można wykorzystać np. do uruchomienia lub pominięcia niektórych jobów. Zobacz przykład:

.go_release:
  rules:
    - if: '$release != "true"'
      when: never

job_3:
  stage: release
  script:
    - echo "$release"
  rules:
    - !reference [.go_release, rules]

W tym przypadku użyłem nowej funkcji GitLab o nazwie !reference [.go_release, rules]. Dzięki niej można oddzielać i łączyć warunki, co czyni kod znacznie łatwiejszym i czytelniejszym. Stworzyłem warunek:

.go_release:
  rules:
    - if: '$release != "true"'
      when: never
    - if: '$release == "true"'

Sprawdza on poprzednio ustawioną zmienną release. Jeśli nie jest ona ustawiona, ten job nigdy nie zostanie uruchomiony. Gdy ustawię ją na true, job zostanie uruchomiony i pojawi się wynik:

Ciekawi Cię wynik tego joba? Oto on:

GitLab API

Kolejną świetną rzeczą, którą chcę pokazać w tym artykule, jest możliwość wywoływania jobów przez API. Dzięki temu można użyć innej aplikacji do komunikacji z GitLab pipeline’ami. Kiedy się to przydaje? Gdy zmieniamy coś w konfiguracji i wymaga to wdrożenia nowej wersji aplikacji lub gdy chcemy mieć np. zewnętrzny system do zarządzania pipeline’ami.

Tworzenie API jest bardzo proste. Można to zrobić, konfigurując trigger token w ten sposób:

Tutaj masz dokładny opis tego, jak wywołać pipeline:

Spróbuję wywołać pipeline przez curl. W tym przypadku TOKEN to nasz wygenerowany token, a REF_NAME to nazwa brancha.

curl -X POST --fail -F token=3017969a1f58913a35b3c37af8b162 -F ref=main https://gitlab.com/api/v4/projects/32464279/

W odpowiedzi otrzymuję:

{
   "id":440317423,
   "iid":24,
   "project_id":32464279,
   "sha":"43730518e4fb78e1fc66b58c0c447c36ba6a685b",
   "ref":"main",
   "status":"created",
   "source":"trigger",
   "created_at":"2022-01-03T10:01:35.475Z",
   "updated_at":"2022-01-03T10:01:35.475Z",
   "web_url":"https://gitlab.com/michaltomasznowak/spring_rest_application/-/pipelines/440317423",
   "before_sha":"0000000000000000000000000000000000000000",
   "tag":false,
   "yaml_errors":null,
   "user":{
      "id":10528456,
      "username":"michaltomasznowak",
      "name":"michaltomasznowak",
      "state":"active",
      "avatar_url":"https://secure.gravatar.com/avatar/300316e59189a08aa73f74298eb5a1ea?s=80\u0026d=identicon",
      "web_url":"https://gitlab.com/michaltomasznowak"
   },
   "started_at":null,
   "finished_at":null,
   "committed_at":null,
   "duration":null,
   "queued_duration":null,
   "coverage":null,
   "detailed_status":{
      "icon":"status_created",
      "text":"created",
      "label":"created",
      "group":"created",
      "tooltip":"created",
      "has_details":true,
      "details_path":"/michaltomasznowak/spring_rest_application/-/pipelines/440317423",
      "illustration":null,
      "favicon":"/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png"
   }
}

Z tego komunikatu można odczytać "status": "created". Oznacza to, że job został uruchomiony.

Uruchamianie pipeline’u między wieloma projektami

Czasami zdarzają się sytuacje, w których podczas jednego wywołania pipeline’u chcemy zainicjować pipeline w innych projektach. W pipeline możemy to łatwo zrobić za pomocą słowa kluczowego trigger. Na potrzeby tego testu stworzyłem kolejny projekt spring-rest-application-to-trigger (projekt jest dostępny tutaj: https://gitlab.com/michaltomasznowak/spring-rest-application-to-trigger). Z projektu spring-rest-application chcę wywołać projekt spring-rest-application. Aby to zrobić, muszę dodać kod:

trigger:
  stage: trigger
  trigger:
    project: michaltomasznowak/spring-rest-application-to-trigger
    branch: main

W GUI można zobaczyć wywołany pipeline z innego projektu:

Podsumowanie

W tym artykule pokazałem, jak utworzyć prosty pipeline dla projektu. Przedstawiłem też kilka podstawowych sposobów pracy ze stages i jobs, a także jak używać zmiennych i połączyć się z AWS. Ta wiedza powinna pozwolić Ci zrozumieć związek między runnerami a dockerami. W wyniku kodu napisanego jako nasz pipeline możesz wykonywać polecenia na runnerach. Przykład? Dzięki słowu kluczowemu script możesz wywołać i uruchomić wszystkie polecenia obsługiwane przez pobrany obraz Dockera na runnerze.

Żródło

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami