Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

CI/CD - automatyczna kuźnia legionów

Wdrożenie ręczne wygląda zawsze tak samo: ktoś loguje się na serwer, pobiera kod, uruchamia budowanie, restartuje aplikację. Działa - dopóki ten ktoś pamięta wszystkie kroki, robi je w tej samej kolejności i nie wdraża w piątek o siedemnastej.

Kuźnia legionowa nie polegała na pamięci kowala. Miała ustalony porządek: ta sama stal, ta sama temperatura, ten sam hartunek, ten sam znak na każdym ostrzu. Dzięki temu miecz wykuty w Galii pasował do pochwy zrobionej w Rzymie. CI/CD to ten porządek zapisany w pliku.

Co znaczy ten skrót

CI/CD oznacza Continuous Integration / Continuous Deployment - ciągłą integrację i ciągłe wdrażanie. Nie „Container", nie „Component", nie „Code": obie litery odnoszą się do procesu, nie do tego, co się przetwarza.

Każda połowa rozwiązuje inny problem. Continuous Integration to automatyczne budowanie i testowanie kodu po każdej zmianie - żeby błąd wyszedł na jaw w ciągu minut, a nie po tygodniu, gdy nikt już nie pamięta, czyja zmiana go wprowadziła. Continuous Deployment to automatyczne wdrożenie zmiany, która przeszła weryfikację - żeby droga od gotowego kodu do produkcji nie zależała od tego, czy ktoś ma czas.

GitHub Actions

Narzędzi jest wiele; zostaniemy przy jednym. GitHub Actions automatyzują build, test i deployment po każdym push i pull requeście (PR) - to cała ich rola.

Trzy nieporozumienia warto odsunąć od razu. Nie służą wyłącznie do zarządzania zgłoszeniami (issues) - to osobna funkcja GitHuba. Nie są dostępne tylko dla repozytoriów prywatnych; publiczne korzystają z nich za darmo i bez limitu minut. I nie zastępują Dockera - przeciwnie, to właśnie w kroku workflow wywołujesz

docker build
i
docker push
, więc jedno korzysta z drugiego.

Konfiguracja to plik YAML w katalogu

.github/workflows/
:

1# .github/workflows/deploy.yml
2name: Deploy
3
4on:
5  push:
6    branches:
7      - main

Trigger, czyli kiedy to ma się uruchomić

Powyższy fragment to trigger - wskazanie zdarzenia uruchamiającego workflow. Zapisuje się go w czterech zagnieżdżeniach, zawsze w tej kolejności:

on:
otwiera sekcję zdarzeń,
push:
wskazuje konkretne zdarzenie,
branches:
zawęża je do wybranych gałęzi, a
- main
jest pozycją listy tych gałęzi.

W YAML-u znaczenie ma wcięcie - każdy kolejny poziom przesuwa się o dwie spacje w prawo i to ono buduje strukturę. Myślnik przed

main
oznacza element listy, więc kolejne gałęzie dopisujesz w następnych liniach.

Zdarzeń jest więcej niż jedno.

pull_request:
uruchomi sprawdzenie przy każdym PR - i to jest zwykle ważniejsze niż
push
, bo pozwala wychwycić błąd zanim zmiana trafi do głównej gałęzi.

Cztery etapy pipeline'u

Właściwa praca dzieje się w zadaniu (

job
), a jego kroki mają ustaloną kolejność:

1jobs:
2  build:
3    runs-on: ubuntu-latest
4    steps:
5      - uses: actions/checkout@v4
6
7      - uses: actions/setup-node@v4
8        with:
9          node-version: 20
10
11      - run: npm ci
12      - run: npm test
13      - run: npm run build
14      - run: docker push registry.imperium.rome/legion-api:latest

Kolejność etapów nie jest kwestią gustu: instalacja zależności (

npm ci
) → linter i testy (
npm test
) → budowanie aplikacji (
npm run build
) → wdrożenie na produkcję (
docker push
)
.

Rządzi nią jedna zasada: im tańszy etap, tym wcześniej. Linter kończy się w kilka sekund, testy w kilka minut, budowanie obrazu w kilkanaście. Gdyby budowanie szło przed testami, każdy literówkowy błąd kosztowałby pełny czas budowania - a i tak zakończyłby się porażką.

Dwa kroki na początku są przygotowaniem, nie etapem pipeline'u.

actions/checkout
pobiera kod repozytorium do maszyny, na której workflow działa - bez tego katalog jest pusty.
actions/setup-node
instaluje wskazaną wersję Node.js i tym samym rozwiązuje spór „u mnie działa": wersja jest zapisana w pliku, więc każdy przebieg dostaje tę samą.

Zwróć uwagę na

npm ci
zamiast
npm install
. Pierwsze instaluje dokładnie to, co zapisano w
package-lock.json
, i przerywa pracę przy rozjeździe z
package.json
. Drugie może po cichu podnieść wersję zależności - a wtedy CI testuje coś innego niż to, co masz u siebie.

Gdy etap zawiedzie

Kroki wykonują się po kolei i pierwszy niepowodzenie zatrzymuje resztę. To zachowanie domyślne i pożądane: skoro testy nie przechodzą, budowanie obrazu i wysyłanie go do rejestru byłoby tylko marnowaniem czasu, a w najgorszym razie wdrożeniem zepsutej wersji.

Stąd praktyczny podział. Sprawdzenia - instalacja, linter, testy - uruchamiaj przy każdym push i każdym PR, na każdej gałęzi. Wdrożenie ogranicz do gałęzi głównej, przez

branches: - main
z poprzedniej sekcji. Dzięki temu praca nad funkcją jest sprawdzana od pierwszego commita, ale nic nie trafia na produkcję, dopóki zmiana nie zostanie scalona.

Podsumowanie

Kuźnia hartuje każde ostrze tak samo, @name:

  • CI/CD oznacza Continuous Integration / Continuous Deployment - nie Container, nie Component, nie Code,
  • CI to automatyczne budowanie i testowanie po każdej zmianie, CD - automatyczne wdrożenie tego, co przeszło weryfikację,
  • GitHub Actions automatyzują build, test i deployment po każdym push i PR - nie służą tylko do issues, nie są zarezerwowane dla repozytoriów prywatnych i nie zastępują Dockera,
  • konfiguracja leży w
    .github/workflows/
    i jest plikiem YAML, w którym strukturę buduje wcięcie,
  • składnia triggera:
    on:
    push:
    branches:
    - main
    ;
    pull_request:
    sprawdza zmianę przed scaleniem,
  • kolejność etapów:
    npm ci
    npm test
    npm run build
    docker push
    , od najtańszego do najdroższego,
  • actions/checkout
    pobiera kod,
    actions/setup-node
    ustala wersję Node.js jednakową dla każdego przebiegu,
  • npm ci
    instaluje dokładnie to, co w
    package-lock.json
    ;
    npm install
    może po cichu zmienić wersje,
  • pierwszy nieudany krok zatrzymuje pozostałe - dlatego sprawdzenia biegną wszędzie, a wdrożenie tylko z gałęzi głównej.

Do CI/CD wrócimy w module o infrastrukturze, przy budowaniu na wielu wersjach naraz, artefaktach i cache'owaniu zależności. A na razie zapamiętaj: pipeline nie jest po to, żeby wdrażać szybciej. Jest po to, żeby wdrażać tak samo - a szybkość wychodzi przy okazji.

Przejdź do CodeWorlds