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

Deployment - wyprowadzenie fortu na pole bitwy

Obóz ćwiczebny wybacza wszystko. Wał jest niski, bo i tak nikt nie szturmuje, prowiant przywożą codziennie, a jak coś się zawali, stawia się to od nowa po obiedzie. Fort na granicy nie wybacza niczego - i to nie dlatego, że zbudowano go inaczej, tylko dlatego, że nikt tam nie stoi obok, żeby poprawić.

Deployment to ta chwila przeprowadzki. Aplikacja, którą uruchamiałeś poleceniem

yarn start:dev
, ma zacząć działać sama, przez wiele dni, dla ludzi, których nie znasz. Ta lekcja jest o czterech rzeczach, które trzeba zrobić, zanim to się stanie.

nest build - kompilacja

W trybie deweloperskim NestJS tłumaczy TypeScript na bieżąco, przy każdym zapisie pliku. Na produkcji tłumaczymy raz, z góry:

1{
2  "scripts": {
3    "build": "nest build",
4    "start:dev": "nest start --watch",
5    "start:prod": "node dist/main"
6  }
7}

Polecenie

nest build
kompiluje TypeScript do JavaScriptu w katalogu
dist/
- i tylko tyle. Nie generuje dokumentacji Swagger (tę dodaje się osobno, dekoratorami i
SwaggerModule
), nie instaluje zależności z
package.json
(od tego jest
yarn install
), nie uruchamia trybu watch (to
start:dev
z flagą
--watch
).

Po kompilacji uruchamiasz

node dist/main
- zwykły Node.js na zwykłym pliku
.js
. Na serwerze produkcyjnym TypeScript nie jest już potrzebny, bo nie ma czego tłumaczyć.

NODE_ENV - jeden napis, wiele skutków

NODE_ENV
to zmienna środowiskowa, którą czytają niemal wszystkie biblioteki ekosystemu Node.js. Ustawienie
NODE_ENV=production
włącza optymalizacje produkcyjne i wyłącza szczegółowe logowanie.

To jedyne, co robi. Nie uruchamia testów automatycznych - te uruchamia

yarn test
. Nie włącza trybu hot-reload; przeciwnie, hot-reload to sprawa środowiska deweloperskiego. Nie generuje dokumentacji API. Zmiana jednego napisu nie dodaje aplikacji funkcji - ona tylko przestawia to, co już w niej jest, z trybu „pomagaj programiście" na tryb „obsługuj ruch".

Skutki bywają zaskakująco duże: biblioteki pomijają kosztowne sprawdzenia, cache szablonów zostaje włączony, a stos wywołań przestaje trafiać do odpowiedzi błędu - bo użytkownikowi jest niepotrzebny, a atakującemu bardzo przydatny.

Zmienne środowiskowe - to, co różni fort od obozu

NODE_ENV
jest tylko jedną z nich. Adres bazy danych, sekret do podpisywania tokenów, klucz do bramki płatności - to wszystko zmienne środowiskowe: wartości, które podaje się aplikacji z zewnątrz, zamiast wpisywać do kodu.

1NODE_ENV=production
2PORT=3000
3DATABASE_URL=postgres://legion:haslo@db.limes.internal:5432/tributes
4JWT_SECRET=zmien-mnie-przed-wymarszem

Powód jest praktyczny: ten sam zbudowany

dist/
musi działać na twoim komputerze, na środowisku testowym i na produkcji. Różni je wyłącznie zestaw zmiennych. Gdyby adres bazy siedział w kodzie, każde środowisko wymagałoby osobnej kompilacji - a wtedy to, co przetestowałeś, nie byłoby tym, co uruchamiasz.

Dlatego konfiguracja zmiennych to pierwszy krok przygotowania, przed jakąkolwiek kompilacją.

Produkcyjny main.ts

Plik

main.ts
startuje aplikację. W wersji deweloperskiej ma zwykle dwie linijki; w produkcyjnej dokłada się do niego cztery rzeczy, w tej kolejności:

1async function bootstrap() {
2  const app = await NestFactory.create(AppModule);
3
4  app.use(helmet());
5  app.use(compression());
6  app.enableCors({ origin: 'https://legion.imperium.rome' });
7
8  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
9
10  const port = process.env.PORT || 3000;
11  await app.listen(port);
12}

Kolejność nie jest przypadkowa:

const app = await NestFactory.create(AppModule)
musi być pierwsze, bo dopiero wtedy istnieje obiekt aplikacji;
app.use(helmet())
i pozostałe middleware ustawia się przed obsługą ruchu;
app.useGlobalPipes(new ValidationPipe())
rejestruje walidację;
await app.listen(port)
otwiera port i od tej chwili aplikacja przyjmuje żądania. Cokolwiek dopiszesz po
listen
, wykona się już przy działającym serwerze.

Każdy z tych czterech elementów odpowiada za co innego:

  • helmet
    ustawia nagłówki HTTP bezpieczeństwa
    -
    Content-Security-Policy
    (CSP),
    X-Frame-Options
    ,
    Strict-Transport-Security
    i kilkanaście innych. Nie szyfruje bazy danych, nie kompresuje plików statycznych i nie zarządza sesjami użytkowników; to hełm dla odpowiedzi, nie dla danych.
  • compression
    pakuje odpowiedzi algorytmem gzip, zanim wyjdą na sieć. Kilkusetkilobajtowy JSON potrafi zejść do kilkudziesięciu kilobajtów.
  • enableCors
    rozstrzyga, ze których stron przeglądarka może wołać twoje API. Na produkcji podaje się konkretny adres, nie gwiazdkę.
  • ValidationPipe
    to znany ci już strażnik danych wejściowych, tu włączony globalnie.

Cztery kroki

Cała droga na produkcję układa się w kolejność, której nie da się poprzestawiać:

  1. Skonfiguruj zmienne środowiskowe - bo od nich zależy, do jakiej bazy aplikacja się połączy.
  2. Dodaj
    helmet
    ,
    compression
    i CORS
    - bo to zmiany w kodzie, a kod zaraz zamrozimy.
  3. Uruchom
    nest build
    - powstaje katalog
    dist/
    , od tej chwili niezmienny.
  4. Uruchom
    node dist/main
    w produkcji
    - startuje to, co zbudowałeś, z tymi zmiennymi, które zastanie.

Zamiana kroków drugiego i trzeciego to najczęstszy błąd tej listy: zbudowany

dist/
nie zna zmian, które dopisałeś po kompilacji. Aplikacja wstanie, nie zgłosi żadnego błędu i będzie działać bez nagłówków bezpieczeństwa.

Podsumowanie

Fort rusza na granicę, @name:

  • nest build
    kompiluje TypeScript do JavaScriptu w katalogu
    dist/
    - nie generuje Swaggera, nie instaluje zależności, nie uruchamia trybu watch,
  • na produkcji uruchamiasz
    node dist/main
    ; TypeScript nie jest tam już potrzebny,
  • NODE_ENV=production
    włącza optymalizacje produkcyjne i wyłącza szczegółowe logowanie
    - nie uruchamia testów, nie włącza hot-reloadu, nie generuje dokumentacji,
  • zmienne środowiskowe pozwalają uruchomić ten sam zbudowany katalog na każdym środowisku - dlatego konfiguruje się je jako pierwsze,
  • kolejność w
    main.ts
    :
    const app = await NestFactory.create(AppModule)
    app.use(helmet())
    app.useGlobalPipes(new ValidationPipe())
    await app.listen(port)
    ,
  • helmet
    ustawia nagłówki HTTP bezpieczeństwa (CSP, X-Frame-Options i inne)
    - nie szyfruje bazy, nie kompresuje plików statycznych, nie zarządza sesjami,
  • compression
    pakuje odpowiedzi gzipem,
    enableCors
    wskazuje dozwolone źródła żądań,
  • cztery kroki przygotowania: zmienne środowiskowe →
    helmet
    ,
    compression
    , CORS →
    nest build
    node dist/main
    .

W następnej lekcji zamkniemy fort w kontenerze, żeby jego wnętrze wyglądało tak samo na każdej maszynie. A na razie zapamiętaj: produkcja nie jest trudniejszą wersją środowiska deweloperskiego. Jest tą samą aplikacją bez nikogo, kto poprawi.

Przejdź do CodeWorlds