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

Production Best Practices - zasady prawdziwego centuriona

Na produkcji nie zmienia się kod. Zmienia się to, kto patrzy: nikt. Aplikacja działa nocą, w niedzielę i w święta, a jedyne, co o niej wiesz, to sygnały, które sama o sobie wysyła. Ta lekcja jest o tym, jak je zorganizować.

Centurion nie sprawdzał każdego legionisty osobiście. Miał trzy źródła wiedzy: raporty liczbowe o stanie kohort, zapisy z wart o pojedynczych zdarzeniach, i meldunki gońców pokazujące, gdzie utknął rozkaz po drodze. Ten sam podział obowiązuje do dziś.

Trzy filary observability

Trzy filary observability to metrics, logs i traces - metryki, logi i ślady. Nie frontend, backend i baza danych, bo to warstwy aplikacji. Nie CPU, pamięć i dysk, bo to zasoby - jeden z wielu rodzajów metryk. I nie development, testy i produkcja, bo to środowiska.

Każdy filar odpowiada na inne pytanie, więc żaden nie zastąpi pozostałych:

  • Metryki mówią, ile i jak często - liczba żądań na sekundę, czas odpowiedzi, użycie pamięci. Są zagregowane i tanie w przechowywaniu, więc trzymasz je miesiącami i na nich stawiasz alerty.
  • Logi mówią, co dokładnie się stało w jednym konkretnym przypadku - z treścią błędu i identyfikatorem żądania. Są drogie, bo jest ich dużo, ale tylko one odpowiadają na pytanie „dlaczego akurat to żądanie".
  • Ślady (traces) mówią, gdzie poszedł czas - pokazują drogę jednego żądania przez wszystkie serwisy. Bez nich przy trzech usługach po drodze wiesz tylko, że odpowiedź przyszła po dwóch sekundach; nie wiesz, która z nich je zużyła.

Metryka mówi ci, że jest źle. Log mówi, co się zepsuło. Trace mówi, gdzie.

Health check

Orkiestrator - Kubernetes, Docker Swarm, czy zwykły load balancer - musi wiedzieć, czy do tej instancji wolno kierować ruch. Pyta o to endpointem zdrowia:

1@Controller('health')
2export class HealthController {
3  constructor(
4    private health: HealthCheckService,
5    private db: MongooseHealthIndicator,
6    private memory: MemoryHealthIndicator,
7  ) {}
8
9  @Get()
10  @HealthCheck()
11  check() {
12    return this.health.check([
13      () => this.db.pingCheck('database'),
14      () => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024),
15    ]);
16  }
17}

Pakiet

@nestjs/terminus
dostarcza gotowe elementy:
HealthCheckService
zbiera wyniki i składa z nich odpowiedź,
MongooseHealthIndicator
sprawdza połączenie z bazą, a
MemoryHealthIndicator
pilnuje zużycia sterty. Wskaźników jest więcej - dla Redisa, dla dysku, dla dowolnego zewnętrznego HTTP.

Health check ma sprawdzać dostępność bazy danych, Redisa i zależnych serwisów. Nie aktualność kodu źródłowego - to nie jego rola. Nie liczbę zalogowanych użytkowników - to metryka biznesowa, nie sygnał zdrowia. I na pewno nie tylko to, czy serwer HTTP odpowiada.

Ostatnie rozróżnienie jest sednem. Aplikacja, której baza padła, nadal odpowiada na HTTP - proces żyje, port jest otwarty,

200 OK
wraca bez zwłoki. Health check, który sprawdza wyłącznie to, mówi orkiestratorowi „wszystko dobrze" o instancji, która nie potrafi obsłużyć ani jednego prawdziwego żądania. Sprawdzaj zależności, bez których aplikacja i tak nic nie zrobi.

Konfiguracja monitoringu

System monitorowania stawia się w czterech krokach, w tej kolejności:

  1. Zainstaluj agenta monitorowania - to on zbiera dane z maszyny i aplikacji.
  2. Skonfiguruj zbieranie metryk - zdecyduj, co mierzysz i jak często.
  3. Ustaw reguły alertingu - określ progi, po przekroczeniu których ktoś ma zostać powiadomiony.
  4. Stwórz dashboardy wizualizacyjne - wykresy do patrzenia, gdy już wiesz, że coś się dzieje.

Kolejność bywa odwracana i to jest najczęstszy błąd przy stawianiu monitoringu. Dashboardy są na końcu, bo dopóki metryki nie płyną, nie wiesz, co miałbyś na nich narysować. Alerty są przed nimi, bo alert przychodzi sam, a na dashboard trzeba spojrzeć - a o trzeciej w nocy nikt nie patrzy.

Poziomy eskalacji alertów

Nie każde przekroczenie progu znaczy to samo. Alerty układa się w cztery poziomy:

  1. Warning (80% pojemności) - powiadomienie. Nic jeszcze nie jest zepsute, ale zbliżasz się do granicy; masz czas zareagować spokojnie.
  2. Critical (95% pojemności) - pilna reakcja. Zapas prawie się skończył, awaria jest kwestią godzin albo minut.
  3. Emergency (powyżej 100%) - natychmiastowa interwencja. Granica została przekroczona, użytkownicy już to widzą.
  4. Post-incident - analiza i wnioski. Po opanowaniu sytuacji ustalasz przyczynę i to, co zmienić, by nie wróciła.

Zwróć uwagę, że dwa pierwsze progi leżą poniżej stu procent. To celowe: alert przy 100% jest już nie ostrzeżeniem, tylko powiadomieniem o awarii. Sensowny monitoring daje czas na reakcję, a nie relację z pożaru.

Czwarty poziom bywa pomijany, a jest jedynym, który cokolwiek zmienia na przyszłość. Bez analizy po incydencie ten sam alert zadzwoni znowu za miesiąc.

Graceful shutdown

Ostatnia zasada dotyczy wyłączania. Przy wdrożeniu nowej wersji stara instancja dostaje sygnał

SIGTERM
- i to, co zrobi w ciągu następnych sekund, decyduje o tym, czy ktoś zobaczy błąd:

1async function bootstrap() {
2  const app = await NestFactory.create(AppModule);
3
4  app.enableShutdownHooks();
5
6  await app.listen(3000);
7}

enableShutdownHooks()
sprawia, że NestJS przechwyci sygnał i przed zamknięciem wywoła metody
onModuleDestroy
i
beforeApplicationShutdown
w modułach. Dzięki temu aplikacja zdąży dokończyć żądania w toku, zamknąć połączenia z bazą i wyrejestrować się z rejestru usług.

Bez tego wdrożenie przerywa obsługiwane właśnie żądania. Przy dziesięciu wdrożeniach dziennie to dziesięć serii błędów, których nikt nie łączy z deploymentem - bo w logach wyglądają jak przypadkowe zerwania połączenia.

Podsumowanie

Centurion czyta raporty, nie ogląda każdego legionisty, @name:

  • trzy filary observability to metrics, logs i traces - nie frontend/backend/baza, nie CPU/pamięć/dysk, nie środowiska,
  • metryka mówi że jest źle, log co się zepsuło, trace gdzie poszedł czas,
  • health check sprawdza dostępność bazy danych, Redisa i zależnych serwisów - nie aktualność kodu, nie liczbę użytkowników i nie tylko to, czy HTTP odpowiada,
  • aplikacja z padniętą bazą nadal zwraca
    200
    - dlatego sprawdzanie samego HTTP jest bezwartościowe,
  • @nestjs/terminus
    :
    HealthCheckService
    składa wynik,
    MongooseHealthIndicator
    bada bazę,
    MemoryHealthIndicator
    - stertę,
  • konfiguracja monitoringu: zainstaluj agenta → skonfiguruj zbieranie metryk → ustaw reguły alertingu → stwórz dashboardy,
  • poziomy eskalacji: Warning (80%) → Critical (95%) → Emergency (>100%) → Post-incident,
  • progi poniżej 100% dają czas na reakcję; alert przy 100% to już relacja z awarii,
  • enableShutdownHooks()
    pozwala dokończyć żądania w toku przy wyłączaniu instancji.

Na tym kończymy przegląd zasad produkcyjnych. A na razie zapamiętaj: aplikacja na produkcji jest tak dobra, jak sygnały, które o sobie wysyła - bo nikt nie będzie zgadywał, co się z nią dzieje.

Przejdź do CodeWorlds