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:
- Push uruchamia prepush który puszcza testy jednostkowe. Testy zakończone sukcesem umożliwiają push kodu do repozytorium.
- Push uruchamia testy jednostkowe na zewnętrznej maszynie (może to być serwer, chmura albo komputer leżący dwa biurka dalej)
- Po przejściu testów uruchamia się build, który buduje z kodu źródłowego aplikację
- Często po buildzie na środowisku testowym/stagingowym uruchamiają się testy automatyczne E2E
- 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
withoutworkflow
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:
Dodatkowo jeśli nie chcecie korzystać z gotowego plugina, można poczytać dokładną instrukcję jak samemu napisać podobny workflow:
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?