Po raz pierwszy naprawdę potrzebujesz obserwowalności, nie wtedy, gdy spokojnie patrzysz na pulpit nawigacyjny. Dzieje się tak, gdy użytkownik napisze „zamawianie jest powolne”, wykres błędów wygląda normalnie, a w logach znajduje się tylko rząd niepołączonych wiadomości.
OpenTelemetry powstało, aby uniknąć tego momentu: nie po to, aby mieć więcej grafiki, ale aby połączyć elementy. Żądanie wchodzi do API, wywołuje bazę danych, przechodzi przez zewnętrznego dostawcę, umieszcza zadanie w kolejce i może zakończyć się niepowodzeniem trzech usług później. Bez rozproszonego śledzenia rekonstruujesz tę historię ręcznie. Przy OpenTelemetry przynajmniej masz mapę.
Nie chodzi o trace, tylko o historię
A trace jest ciągiem span. Ujmując to, brzmi zimno. W praktyce każde span to fragment historii: POST /checkout, SELECT inventory, call payment provider, publish order.created.
Wartość pojawia się, gdy zaczynasz odpowiadać na prawdziwe pytania:
- która usługa zewnętrzna zwalnia?
- czy błędy pochodzą z konkretnej wersji?
- czy problem dotyczy wszystkich, czy tylko jednego najemcy?
- czy ponowna próba ukrywa przekroczenie limitu czasu?
- zadanie asynchroniczne rozpoczyna się, ale potem kończy się gdzie indziej?
Tych pytań nie da się rozwiązać rzucając w pośpiechu console.log. Rzeczywiście, często dziennik dodany w sytuacji awaryjnej pomaga dzisiaj, a jutro staje się hałasem.
Jak umieścić to w aplikacji Node.js
Najzdrowsza konfiguracja jest prosta: aplikacja generuje dane telemetryczne, a Collector decyduje, gdzie je wysłać.
Node.js app -> OpenTelemetry Collector -> backend di observability
Dlaczego nie eksportować bezpośrednio do dostawcy? Ponieważ na początku wydaje się to szybsze, później zdajesz sobie sprawę, że każda usługa ma inną konfigurację, różne próby, różne filtry i brak centralnego punktu, w którym można usunąć wrażliwe dane lub zmienić miejsce docelowe.
Collector jest nudne pod każdym względem. Otrzymuje OTLP, wykonuje przetwarzanie wsadowe, może filtrować, może pobierać próbki, może dodawać wspólne atrybuty i może eksportować do wielu systemów.
Samoinstrumentacja: dobra, ale niewystarczająca
W Node.js zacząłbym od autoinstrumentacji. Zapewnia natychmiastowy wgląd w protokół HTTP, obsługiwane platformy, bazy danych i popularne biblioteki.
npm install @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ @opentelemetry/exporter-trace-otlp-http
Następnie inicjujesz SDK przed resztą aplikacji:
import { NodeSDK } from '@opentelemetry/sdk-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, }), instrumentations: [getNodeAutoInstrumentations()], }); sdk.start();
Jednak to widzi ramy, a nie Twój produkt. Wie, że wykonałeś zapytanie, ale nie wie, że zapytanie było w „kolejności utworzenia” lub „odnowienia subskrypcji”. Do tego potrzebne są span podręczników w punktach, w których liczy się dominacja.
const span = tracer.startSpan('checkout.create_order'); try { span.setAttribute('cart.items_count', input.items.length); const order = await createOrder(input); span.setAttribute('order.id', order.id); return order; } catch (error) { span.recordException(error as Error); throw error; } finally { span.end(); }
Nie umieściłbym nigdzie instrukcji span. Umieściłbym je tam, gdzie o trzeciej nad ranem chciałbym zrozumieć, co się stało, bez czytania połowy bazy kodu.
Trzy zasady, które pozwalają uniknąć dużego chaosu
Pierwsza zasada: każda usługa musi mieć service.name, środowisko i wersję. Wydaje się to banalne, ale bez tych atrybutów trace jest znacznie mniej przydatne. Gdy wdrożenie coś zepsuje, w ciągu dwóch sekund chcesz filtrować według wersji.
Druga zasada: nie umieszczaj wrażliwych danych w atrybutach. E-maile, tokeny, ładunki w postaci liczb całkowitych i adresy nie powinny przypadkowo trafiać do backendu umożliwiającego obserwowanie. Jeśli chcesz zidentyfikować użytkownika, rozważ wewnętrzne identyfikatory, hashowanie lub mniej wrażliwe pola.
Trzecia zasada: zwracaj uwagę na kardynalność. user.id jako atrybut trace może mieć sens. Jako etykieta metryczna może zniszczyć Twoje koszty i wydajność.
Wskaźniki: kilka, ale dobre
Zacząłbym od bardzo praktycznych wskaźników:
- stawki, błędy i czas trwania żądań;
- opóźnienie zależności zewnętrznych;
- liczba przekroczeń limitu czasu i ponownych prób;
- głębokość ogonów;
- czas trwania pracy;
- procent błędów na wersję.
Resztę dodaje się w razie potrzeby. Dashboardy pełne wykresów, na które nikt nie patrzy, to meble, a nie obserwowalność.
Dzienniki: nadal przydatne, ale połączone
Logi nie znikają. Po prostu stają się znacznie bardziej przydatne, gdy niosą trace_id i span_id. Możesz więc zacząć od dziennika błędów i otworzyć trace lub zacząć od wolnego trace i czytać tylko logi utworzone w tej ścieżce.
Bez korelacji szukasz igieł. Dzięki korelacji przynajmniej wiesz, w której szufladzie szukać.
Lista kontrolna, której użyłbym przed stwierdzeniem „jesteśmy objęci”
- trace w rzeczywistości obejmuje wiele usług.
- Dzienniki obejmują
trace_idispan_id. - Collector jest skonfigurowany z limitami przetwarzania wsadowego i pamięci.
- Błędy są rejestrowane w span.
- Istnieje polityka pobierania próbek.
- Metryki kontrolowały liczność.
- Wrażliwe dane są filtrowane.
- Alerty zaczynają się od objawów użytkownika, a nie losowych wykresów.
Wniosek
OpenTelemetry samo w sobie nie rozwiązuje problemów produkcyjnych. Ale sposób, w jaki sobie z nimi radzisz, zmienia się. Zamiast na ślepo dodawać logi, zaczynasz podążać rzeczywistą ścieżką żądania.
Dla mnie znak, że to działa, jest prosty: kiedy coś się dzieje, zespół przestaje pytać „gdzie szukamy?” i zaczyna pytać „dlaczego ten utwór jest powolny?”. W tym miejscu obserwowalność staje się narzędziem, a nie zbiorem dashboardów.