Kurs NestJS · Moduł 9: Deployment i infrastruktura
CI/CD - automatyczna kuźnia legionów
W tej lekcji6
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 - mainTrigger, 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:latestKolejność 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:
- 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/checkoutpobiera kod,actions/setup-nodeustala wersję Node.js jednakową dla każdego przebiegu,npm ciinstaluje dokładnie to, co wpackage-lock.json;npm installmoż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.
Kod do tej lekcji: .github/workflows/ci.yml
1# CI/CD Pipeline - Automatyczna Stocznia Imperium
2
3name: Roman Empire CI/CD
4
5on:
6 push:
7 branches: [main, develop]
8 pull_request:
9 branches: [main]
10
11# Zmienne srodowiskowe dla calego workflow
12env:
13 NODE_VERSION: '18'
14 REGISTRY: ghcr.io
15
16jobs:
17 # === JOB 1: Testy ===
18 test:
19 name: Run Tests
20 runs-on: ubuntu-latest
21
22 steps:
23 - name: Checkout code
24 uses: actions/checkout@v4
25
26 - name: Setup Node.js
27 uses: actions/setup-node@v4
28 with:
29 node-version: '18'
30 cache: 'npm'
31
32 - name: Install dependencies
33 run: npm ci
34
35 - name: Run linter
36 run: npm run lint
37
38 - name: Run unit tests with coverage
39 run: npm run test:cov
40
41 - name: Run E2E tests
42 run: npm run test:e2e
43
44 # === JOB 2: Build ===
45 build:
46 name: Build Application
47 needs: test
48 runs-on: ubuntu-latest
49
50 steps:
51 - uses: actions/checkout@v4
52
53 - name: Build Docker image
54 run: docker build -t roman-api:latest .
55
56 - name: Run container health check
57 run: |
58 docker run -d --name test-api -p 3000:3000 roman-api:latest
59 sleep 10
60 curl -f http://localhost:3000/health || exit 1
61 docker stop test-api
62
63 # === JOB 3: Deploy ===
64 deploy:
65 name: Deploy to Production
66 needs: build
67 runs-on: ubuntu-latest
68 if: github.ref == 'refs/heads/main'
69
70 steps:
71 - uses: actions/checkout@v4
72
73 - name: Deploy
74 run: echo "Deploying Roman Empire API..."
75
76 - name: Verify health
77 run: echo "Checking health endpoint..."
78
79# CI = Continuous Integration (testy przy kazdym pushu)
80# CD = Continuous Deployment (automatyczny deploy po merge)
81Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. CI/CD oznacza:
2. GitHub Actions w kontekście CI/CD:
Zadania praktyczne w grze
- Edytor kodu
Stwórz .github/workflows/deploy.yml z krokami: install, lint, test, build, deploy
- Układanie w pionie
Uporządkuj etapy pipeline CI/CD od pierwszego do ostatniego:
- Układanie w pionie
Ułóż składnię triggera GitHub Actions workflow: