Podstawy Continuous Integration&Delivery na przykładzie Github Actions

Continuous Integration i Continous Delivery to terminy, które powinny być znane każdemu programiście. Continuous Integration to praktyka regularnego mergowania kodu dzięki której powstało mnóstwo świetnych praktyk i narzędzi. Jest związana z pojęciem Continous Delivery, które odnosi się do regularnego wydawania na produkcję małych funkcjonalności. Dzisiaj spróbuję początkującym wyjaśnić z czym to się je :)

Jeszcze blok reklamowy przed wpisem i jedziemy!

Po pierwsze: mój Instagram (można tam znaleźć zapowiedzi wpisów, dodać jakieś sugestie i przywitać się :) Jestem również na devenv.pl, szczególnie zapraszam na mój artykuł o tej trudniejszej części TypeScripta.

Wystartowałam tez w wersji alpha z newsletterem :) Jeśli chcecie dostawać ciekawe linki i zadania z programowania to zapraszam do zapisania się :)

Trochę teorii dla początkujących

Zacznijmy od podstaw. Chociaż CI w każdym projekcie wygląda inaczej, jeśli chodzi o web development można wyodrębnić popularne praktyki takie jak:

  • używanie repozytorium kodu z systemem kontroli wersji - najpopularniejszym jest git
  • testy (zwykle jednostkowe) których poprawne przejście jest warunkiem koniecznym do pushowania kodu
  • automatyzacja budowania aplikacji - push na konkretnego brancha powinien wywołać proces budowania się aplikacji
  • zbudowana aplikacja podlega automatycznym testom - push na konkretnego brancha powinien uruchamiać testy na serwerze
  • po wykonaniu testów oraz builda, aplikacji powinna zostać zreleasowana na odpowiednie środowisko (albo na staging skąd w przyszłości pójdzie promote - czyli wierna kopia - na produkcję)

Prosty przykład dobrze zaimplementowanego CI/CD:

  1. Push uruchamia prepush który puszcza testy jednostkowe. Testy zakończone sukcesem umożliwiają push kodu do repozytorium.
  2. Push uruchamia testy jednostkowe na zewnętrznej maszynie (może to być serwer, chmura albo komputer leżący dwa biurka dalej)
  3. Po przejściu testów uruchamia się build, który buduje z kodu źródłowego aplikację
  4. Często po buildzie na środowisku testowym/stagingowym uruchamiają się testy automatyczne E2E
  5. Aplikacja zostaje zreleasowana (udostępniona publicznie)

Dla początkującego programisty, który zna podstawy gita z pewnością pojawi się pytanie: Jak to się stało, że git push uruchomił akcję na serwerze?

Na to pytanie odpowiem dzisiaj :)

Github Actions

Trochę zastanawiałam się nad idealnym przykładem. W pracy używam Drone'a jednak nie jest on open source'owym rozwiązaniem ani nawet - od jakiegoś czas - darmowym. Okazja nadarzyła się sama: Ghost w wersji 3.0 obsługuje customowe integracje!

W końcu się udało i nie będę musiała już budować szablonu lokalnie oraz wysyłać do na serwer. Wspólnie zautomatyzujemy ten proces :)

Po pierwsze potrzebujemy projektu do zbudowania na githubie. Moim będzie szablon bloga o nazwie technozaur.

Maszyną która wykona dla nas builta będzie serwer githuba, a dokładnie usługa Github Action. Istnieją alternatywne CI takie jak Drone, Jenkins, CircleCI, Buddy i wiele innych, jednak ta - ze względu na brak kosztów - będzie idealnym wyjściem. Dodatkowo autentykację mamy załatwioną, bo Github Actions jest częścią Githuba.

We wszystkich popularniejszych rozwiązaniach zadania dla usługi CI tworzy się poprzez plik w formacie yml (lub yaml) który umieszczamy w repozytorium kodu. Będzie on źródłem informacji o tym co i jak powinien zrobić serwer.

Nasz będzie wyglądał następująco:

name: Deploy Theme
on:
  push:	
    branches:	
      - master
jobs:
  deploy:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@master
      - uses: TryGhost/action-deploy-theme@v1.0.0
        with:
          api-url: https://solutionchaser.com 
          api-key: ${{ secrets.CHASER_PROD_API_KEY }}

Dokładne formaty znajdziesz w dokumentacji poszczególnych narzędzi, ale zasada działania będzie podobna.

W pierwszej linijce podajemy name czyli nazwę joba (akcji do wykonania), czuj się swobodnie puścić wodze fantazji, to jest nazwa własna i nie ma znaczenia :)

Druga linijka jest bardzo ważna, bowiem triggerem (czyli elementem wywołującym joba) może być mnóstwo rzeczy: zmiana labelki, stworzenie Pull Requesta czy zgłoszenie buga. W tym przypadku będzie to push do brancha master.

Następnie pod kluczem jobs definiujemy akcje będą składowymi joba, w tym przypadku jest to tylko deploy (to też nazwa własna w yml) na produkcję, który wykonamy na obrazie ubuntu-18.04. Github Actions udostępnia kilka typów maszyn z różnymi systemami operacyjnymi. Jeśli zastanawia cię co to obraz (czy kontener) to zapraszam do przeczytania wpisu o Dockerze.

Warto wiedzieć, że jest w pełni wyizolowane, postawione na chwilę (dosłownie na wywołanie tego joba) środowisko, które zostanie usunięte wraz z zakończeniem pracy.

Następnie podajemy kroki, które chcemy wykonać (pod kluczem steps). Tutaj mamy dwa:

  • pobierz najnowszą wersję szablonu z mastera
  • zbuduj szablon Ghosta i umieść na produkcji
      // fragment yml
      - uses: actions/checkout@master
      - uses: TryGhost/action-deploy-theme@v1.0.0

Pierwsze jest łatwe, bo przecież znamy gita i wystarczy tylko użyć pluginu z Github Actions. Drugie już mniej a to po prostu napisany skrypt w js i wrzucony do repozytorium https://github.com/TryGhost/action-deploy-theme/.

Szczegóły można zobaczyć tutaj: https://github.com/TryGhost/action-deploy-theme/blob/master/index.js.

Pisanie takich skryptów jest monotonne: Normalnie użylibyśmy kontenera z nodejs, potem zrobili npm install, a następnie npm build (pod którym w przypadku szablonu ghosta byłby gulp zip), aż w końcu skopiowali otrzymany plik do katalogu na serwerze poleceniem cp.

Jednak w większości przypadków będziecie używać gotowych rozwiązań, które robią to za nas :)

Zmienne środowiskowe i sekrety

Skoro już wiemy jak wywołać kolejne akcje to teraz objaśnijmy sobie klucz with. Odpowiada on za zmienne środowiskowe, które będą dostępne w czasie przetwarzania kroku. Zwykle są to zewnętrzne dane takie jak środowisko, ścieżki, rodzaj builda albo hasła.

W naszym przykładzie podaliśmy tam URL mojego bloga. Akurat ten plugin wymaga takich danych do poprawnego działania.

Pewnie zastanawia was kolejna zmienna środowiskowa ${{ secrets.GHOST_ADMIN_API_KEY }}. To całkiem proste: klamerki poprzedzone dolarem to oznaczanie zmiennej, a w środku mamy jej nazwę.

Tylko skąd się wzięła zmienna secrets? I co to w ogóle jest?

Co to sekrety?

Sekretami w programowaniu nazywamy informacje, które nie powinny być dostępne publicznie: takie jak hasła i - jak w tym przypadku - klucze dostępu. Lokalnie przechowywane są w powłokach lub plikach, których nie pushuje się do repozytorium a serwery korzystają z narzędzi przechowujących klucze pod postacią klucz-wartość. Rzadko jest to zwykła baza, powstało wiele dedykowanych ku temu narzędzi.

Na szczęście Github ma swoje, dostępne w Settingsach każdego repozytorium. Najprościej go znaleźć przez klienta webowego Githuba czyli github.com. Wejdź do repozytorium swojego szablonu, przyciśnij Settings i z pionowego menu po lewej wybierz Secrets. Ścieżka powinna być mniej więcej taka https://github.com/KamilaBrylewska/technozaur/settings/secrets/.

Umieściłam tam klucz który pobrałam po tworzeniu customowej integracji w panelu ghosta: Ghost Admin -> Settings -> Inegrations -> Add Custom Integration. Klucz który cię interesuje to Admin API Key i to właśnie jego wartość dodałam po kluczem CHASER_PROD_API_KEY na githubie.

Gotowe!

Teraz udostępnijmy yml serwerowi pushując plik do repozytorium:

git add .github/workflows/deploy-theme.yml
git commit -m "Github Actions - Deploy"
git push
Jeśli autoryzujecie się do githuba przez customowe integracje np.: OAuth to możecie napotkać błąd 'refusing to allow an OAuth App to create or update workflow .github/workflows/main.yml without workflow scope'. Jest to celowe działanie, które ma zapobiegać aktualizacji workflow poprzez zewnętrzne aplikacje. Więc przygotujcie klucze do działania!

Teraz musicie wejść na https://github.com/TwojaNazwa/NazwaTwojeRepozytorium/actions i zobaczyć swój pierwszy deploy :)

Po kliknięciu na szczegóły będziecie mogli podejrzeć jakie dokładnie kroki się wykonały.

Fajne linki do dokumentacji

Ten wpis jest coraz dłuższy, a informacji wciąż sporo do przekazania, więc dla dociekliwych przygotowałam kilka ciekawych linków. Jeśli chcecie wiedzieć co można wpisać w runs-on, co oznaczają poszczególne klucze w YAML oraz jakie zmienne środowiskowe są dostępne, zapraszam do odwiedzenia poniższych linków:

Using Node.js with GitHub Actions - GitHub Help
You can create a continuous integration (CI) workflow to build and test your Node.js project.
Using environment variables - GitHub Help
GitHub sets default environment variables for each GitHub Actions workflow run. You can also set custom environment variables in your workflow file.

Dodatkowo jeśli nie chcecie korzystać z gotowego plugina, można poczytać dokładną instrukcję jak samemu napisać podobny workflow:

Workflow syntax for GitHub Actions - GitHub Help
A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration.

Podsumowanie

Mam nadzieje, że się podobało :) Obecnie automatyzacja jest niezbędna do zwinnej produkcji oprogramowania i obecnie nie wyobrażam sobie pracy bez niej. Pomijając wyniosłe idee tak naprawdę to po prostu szereg powtarzalnych zadań, których nie muszę robić. A to tygryski lubią najbardziej :)

A wy z jakich CI korzystacie? Możecie coś polecić w rozsądnej cenie dla developera-hobbysty?

Kamila

Dziękuję za poświęcony czas, będzie mi bardzo miło jak zostawisz komentarz :)