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 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:
Metryka mówi ci, że jest źle. Log mówi, co się zepsuło. Trace mówi, gdzie.
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.System monitorowania stawia się w czterech krokach, w tej kolejności:
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.
Nie każde przekroczenie progu znaczy to samo. Alerty układa się w cztery poziomy:
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.
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}
sprawia, że NestJS przechwyci sygnał i przed zamknięciem wywoła metody enableShutdownHooks()
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.
Centurion czyta raporty, nie ogląda każdego legionisty, @name:
200 - dlatego sprawdzanie samego HTTP jest bezwartościowe,@nestjs/terminus: HealthCheckService składa wynik, MongooseHealthIndicator bada bazę, MemoryHealthIndicator - stertę,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.