Czasy się zmieniają, typy aplikacji, sposób ich dostarczania, monitorowania, wdrażania też. 20 lat temu instalacja była manualna w większości przypadków. Wdrażaliśmy nową aplikację na IIS czy inny webowy system, sprawdzaliśmy, czy się uruchomiła i tyle. Może raz na kilka dni pukaliśmy ją by się dowiedzieć czy aby na pewno wszystko OK. Jednak przeważnie nawet tego nie robiliśmy. Powiedzmy sobie szczerze, monitorowanie naszych aplikacji nawet teraz pozostawia wiele do życzenia! :)
Tak naprawdę to nie znam nikogo kto by nie miał braku monitoringu działania aplikacji na sumieniu opsi się tym w końcu zajmą. No właśnie nie bałdzo ;) Jak już chyba się zgodziliśmy czasy się zmieniają, sposób wdrażania aplikacji też, ich poziom skomplikowalności, liczba elementów z którymi te aplikacje muszą się komunikować itp. itd. też.
Po co nam te endpointy?
Szczerze, jak powiesz czy Twoja aplikacja teraz działa? Zastanów się i zapisz to sobie na kartce i wróć do tekstu.
Czy w Twoim „teście” odbyło się odpytanie aplikacji by sprawdzić, czy coś odpowiada? A skąd wiesz, czy jak odpowiada to jej zależności (baza danych, inne usługi) są osiągalne? Jeżeli zaś na to odpowiedziałeś, że masz monitorowanie bazy danych i wiesz, kiedy ona leży, to skąd wiesz, że Twoja aplikacja w tej chwili może podłączyć się do bazy danych, bo sieć jest dobrze skonfigurowana albo nie jest obciążona czy też zerwana? Zaczyna być trudniej to przeanalizować. A teraz skoro nasza baza danych leży, to skąd i jak mamy wiedzieć, żeby nasza aplikacja nie przyjmowała w tym momencie ruchu – bo po co ma przyjmować, skoro wiemy od górnie, że jej zależność nie działa?
Zaczyna to być skomplikowany proces korelacji wielu pojedynczych alertów w celu identyfikacji problemu i jego zaradzeniu. W tym czasie kto cierpi? Użytkownicy.
Dodaj do tego teraz informację, że nasze aplikacje działają w kontenerach i działanie kontenera == działaniu aplikacji. Kontener może działać, zaś nasza aplikacja przy ładowaniu wywaliła błąd. Jak to mamy wykryć i jak na to zareagować?
Światło w tunelu
Na szczęście ze zmianami czasów, zmieniają się też narzędzia, które subiektywnie potrafią sobie z takimi problemami radzić jednak tylko wtedy kiedy nasza aplikacja poinformuje te narzędzia o swoim stanie. Oczywiście, nie musimy z tych narzędzi korzystać, jednak, kiedy nasza aplikacja ma być monitorowana to jednak zadaniem naszym jest ułatwić monitorowanie takiej aplikacji, w przeciwnym wypadku będziemy błądzić w ciemnościach. I przy kilku moich ostatnich konsultacjach, najczęściej występującym problem, któremu programiści i opsi próbowali sprostać to monitorowanie stanu i metryk aplikacji. Stan przeważnie był prosto monitorowany – request http i tyle (zresztą tak jak pewnie sam odpowiedziałeś na pytanie wyżej). Metryki zaś, to już był bardzo twardy orzech do zgryzienia, bo jak tu z metryk systemowych wyciągnąć te dotyczące aplikacji? W ogóle jakie ta aplikacja ma wymagania?
No właśnie, co możemy zrobić by zaradzić takim problemom? Zaimplementować dosłownie trzy endopointy które w najprostszej z możliwych opcji pewnie da się zamknąć w 20 linijkach kodu. I samo to da nam bardzo duże benefity.
Endpoint pierwszy: Liveness (czy ja żyję?)
Pierwszym endpointem jest test który sam robiłeś – odpytanie się aplikacji po url i sprawdzenie czy działa (odpowiada HTTP Status Code 200). To nam daje informacje, czy nasza aplikacja wystartowała bez problemów i nie wyłożyła się.
On ma być banalnie prosty i banalnie szybki. Odpytanie – odpowiedź.
Na tej podstawie nasz system monitoringu może spróbować zrestartować aplikację (jak wiemy, restart to najlepsza opcja naprawy błędów ;)). To przeważnie rozwiązuje problem. Jak popatrzymy na Aktor Model w programowaniu, to jest to jeden z sposobów przywracania stabilności systemowi – usunięcie aktora i stworzenie go na nowo.
Jeżeli zaś restarty nie pomagają i dalej mamy problem to znaczy, że jest coś naprawdę źle z naszą aplikacją i należy ją naprawić. Na przykład spowodować by w trakcie startowania nie wywalała wyjątku. Albo by przy jakimkolwiek zapytaniu nie wywalała się.
Zasady po ilu razach postawić na niej krzyżyk czy po ilu próbach i w jakich odstępach dopiero stwierdzić, że coś jest nie tak, jest kwestią dyskusyjną i uzależnioną od aplikacji i jej przeznaczenia.
Endpoint drugi: Readiness (czy mogę świadczyć usługi dla procesu biznesowego?)
Pierwszy endpoint mówił nam, czy nasza aplikacja żyje, drugi zaś sprawdza czy nasza aplikacja może skontaktować się ze wszystkimi usługami, od których jest uzależniona. Usługi te mogą być bazą danych, udziałem sieciowym na który mamy zapisywać dane, usługą online weryfikującą numery PESEL.
Jeżeli nasza aplikacja działa, zaś z jakiś przyczyn nie może się ona podłączyć do bazy danych, czy też do usługi weryfikowania numeru PESEL i są to usługi, bez których nie możemy świadczyć procesu biznesowego udostępnionego przez naszą aplikację to… nasza aplikacja nie powinna przyjmować ruchu.
Ale jak to? No tak to. Nie ma sensu by użytkownik czekał x sekund na to by nasza aplikacja powiedziała mu sory, nie działam. Nie to jako to, jak nasza aplikacja nie działa to nie ma jej. No tak, czasami tak może być, że mamy jedną tylko instancję naszej aplikacji. Wtedy tak, mówimy trudno i dajemy miłą odpowiedź użytkownikowi by spróbował później. Zaś jeżeli mamy wiele instancji naszej aplikacji to po prostu wyłączamy z ruchu tylko jedną z nich.
Jak tylko nasza aplikacja może się skontaktować ponownie usługami, od których jest zależna to włączamy ją ponownie do ruchu.
Czasami Liveness i Readiness są tym samym endpointem, czasami jest to ok. Jednak warto je rozróżnić, bo znaczą one dwie różne rzeczy. Jedno mówi, że aplikacja żyje i odpowiada, drugi, że aplikacja może zapisać dane do bazy danych.
Implementacja Liveness i Readiness
Liveness to nie jest nic trudnego, wystarczy 200, jednak przy readiness problem jest już bardziej skomplikowany. Dlatego istnieją różne biblioteki, które mają nam w tym pomóc. Dla .NET Core jest HealthCheck, gdzie całość prawie można zamknąć w fragmencie kodu:
// void ConfigureServices services.AddHealthChecks() .AddCheck("Xxx", failureStatus: HealthStatus.Unhealthy) .AddCheck("Db", failureStatus: HealthStatus.Unhealthy); // void Configure app.UseEndpoints(endpoints => { endpoints.MapControllers(); // by default if Predicate is not set (is null) it will run all // checks. Therefore ready will execute all readiness checks. endpoints.MapHealthChecks("/healthz/ready", new HealthCheckOptions { ResponseWriter = WriteHealthCheckResponse }); // live will not execute any test and will just return 200 if its // up and running endpoints.MapHealthChecks("/healthz/live", new HealthCheckOptions() { Predicate = _ => false, ResponseWriter = WriteHealthCheckResponse }); });
Dla Java na przykład Spring Boot Actuator dla innych języków też są. Więc nie jest to nic trudnego w tych czasach tylko mało kto to robi. A szkoda. To są dwa najważniejsze endpointy. Trzeci jest też ważny jednak nie informuje on o tym czy aplikacja może przyjąć żądanie.
Endpoint trzeci: metrics (ile ramu zużywa aplikacja? Ile żądań obsłużyła? Itp.?)
Ostatnim endpointem jest endpoint który mówi nam o stanie wewnętrznym aplikacji, informuje o faktycznie użytych zasobach i innych parametrach, które programiści wraz z opsami i biznesem uznają za ważne monitorowania. Może to być liczba przetworzonych zdarzeń na minutę, czy liczba nowych rejestracji w ciągu dnia. Cokolwiek chcemy i jak chcemy.
Tylko teraz jak to zwracać? No właśnie, to jest problem sam w sobie. W jakim formacie takie dane powinny być udostępnione po to by móc je potem jakoś przetworzyć? I tutaj chyba najlepszą na dzisiaj odpowiedzią jest: follow the white rabbit. Większość aplikacji do monitorowania wspiera jeden format – format Prometheusa. I tak jak wcale nie musimy zbierać danych w Prometheusie tak warto je udostępnić w takim formacie by jak najwięcej dostępnych aplikacji na rynku monitorujących aplikacje mogło by skorzystać z tego endpointu.
Implementacja jest już bardziej złożona w zależności od metryk, jednak w najprostszy sposób możemy w .NET zrobić to tak:
// void Configure app.UseMetricServer("/healthz/metrics");
Znów, podlinkowane wyżej biblioteki dla Java i go też umożliwiają wystawianie metryk.
Podsumowanie
Takie trzy endpointy dadzą nam wystarczający pogląd na temat stanu naszej aplikacji, dzięki czemu będziemy mogli podejmować lepsze decyzje co dalej zarówno pod względem tego czy i jak inwestować w sprzęt jak i co robić by nasza aplikacja była bardziej stabilna i czy w ogóle coś trzeba robić? Działanie reaktywne nie jest najlepszym podejściem w XXI wieku. Jednak bez danych możemy jedynie tak działać.
Bonusem jest wsparcie tego typu endpointów przez takie platformy/narzędzia jak Kubernetes, które udostępniają nam procesy reagowania jak których z endpointów nie odpowie tak jak chcemy. W tym automatyczne wypinanie naszej działającej instancji z ruchu jak readiness leży.
A jak u Ciebie wygląda sprawa monitorowania aplikacji w firmie? W projektach po godzinach? Robisz to? Jestem bardzo ciekawy Twoich przemyśleń i procesu, który doprowadził do odpowiedzi tak lub nie, podziel się! Dzięki!
PS.: tak, założyłem, że mowa o aplikacji webowej lub udostępniającej API po http. Jeżeli jednak nie mamy takiej a mamy aplikacja w usłudze windows czy po prostu jakiś exe, to też możemy dalej monitorować jej status w inny sposób. Na przykład poprzez połączenie na porcie TCP albo przez pliki jakie aplikacja wypluwa. Da się to zrobić, nie jest tak prosto jak przy http jednak nie jest to nieosiągalne.
PS2.: /healthz
a nie /health
bo /health
może być poprawnym endpointem w naszej aplikacji zwracającym dane na temat zdrowia.
W przyadku projektu przy którym pracuję, wykorzystujemy Smoke Testy uruchamiane cyklicznie dla poszczególnych modułów. Każdy moduł ma endpoint typu liveness. Dla usług zależnych (DB, API) jest dokładnie tak samo – czyli jest dedykwowany Smoke Test. Twój post jednak dał mi do myślenia, gdyż co jeśli sama aplikacja działa, usługa zależna działa, ale w apce jest jakiś dziwny bug, który powoduje niemożność skorzystania z usługi? Tutaj też przydałby się odpowiedni monitoring.
Ciekawą sprawą jest też sama metodyka budowania testów. Spotkałem się z opiniami, że ciężko jest wymyśleć odpowiednie testy, a wcale tak nie jest. Miałem okazję supportować systemy legacy, gdzie każdy pojawiający się problem, był analizowany i był pisany do niego test, który miał monitorować dany przypadek w przyszłości. Wpadały tutaj tak trywialne rzeczy jak sprawdzenie czy serwer aplikacji ma poprawne DNS’y do API. Dzięki temu jeśli problem się powtórzył, od razu było wiadomo co się stało.
prawda, tworząc test dla case jest łatwo. Gorzej wpaść na ten case wcześniej zanim problem powstanie ;) Jednak budowanie na tej zasadzie testów daje dobre wyjście do późniejszego refactoringu legacy czy też nawet tworzenia nowej wersji systemu
Dobry artykuł! Sam stosuję podobne mechanizmy i świetnie się to sprawdza. Polecam od razu wrzucić to do swoich projektów :)
W pracy – Kubernetes.
Po pracy PM2 (projekty w node.js) – świetne narzędzie z dodatkowo doskonałym panelem do mnie monitorowania i zarządzania aplikacjami. Niestety możliwości są mocno ograniczone w darmowej wersji.
Comments are closed.