Automatyzacja monitoringu dzięki alarmom

Automatyzacja monitoringu – AlertManager

Jest to druga część z serii wpisów o monitorowaniu aplikacji, dlatego, jeżeli jeszcze nie miałeś okazji przeczytać pierwszego tekstu, to gorąco zachęcam do zapoznania się najpierw z wpisem o Prometheus. Znajdziesz tam podstawowe założenia budowanego przez nas nowoczesnego systemu monitoringu oraz wprowadzenie do obsługi samego Promethues. Dziś natomiast skupimy się przede wszystkim na dopracowywaniu i automatyzacji procesów monitorowania aplikacji oraz weryfikacji poprawności jej działania.

[prometheus-monitoring-linki]

W poprzedniej części powiedzieliśmy sobie, jak i dlaczego powinniśmy zbierać różnego rodzaju metryki. Jednak samo gromadzenie danych to w dzisiejszych czasach zdecydowanie za mało – trzeba jeszcze wiedzieć, co z tymi danymi można zrobić.

Jakie decyzje możemy podjąć na podstawie zbieranych metryk?

W idealnej sytuacji powinniśmy mieć zawczasu przemyślane wszystkie potencjalne sytuacje, do jakich może dojść w naszym systemie, oraz – co równie ważne – jak wtedy powinniśmy zareagować. Jeżeli będziemy tylko patrzeć na nasze metryki, to poza mądrze wyglądającymi zbiorami danych niewiele z tego wyniknie.

Jednym z pierwszych kroków w monitoringu, jaki musimy zrobić, jest ustalenie, co właściwie chcemy obserwować i jakie wartości tych metryk uznamy za niepokojące, a jakie za wymagające wręcz natychmiastowej interwencji. Dopiero po tak przygotowanych procedurach można przejść do kolejnego etapu, czyli definiowania alarmów.

Najlepiej, by już na etapie projektowania naszego rozwiązania ustalić te wartości i później tylko lekko je dopracowywać, ponieważ takie założenia mogą wpłynąć na wiele decyzji projektowych – np. czy chcemy zoptymalizować naszą aplikację pod kątem wielokrotnych odczytów i stosunkowo rzadkich modyfikacji danych, czy odwrotnie.

Przeanalizujmy to na trzech prostych przykładach.

Zużycie dysku na serwerze

Jedną z łatwiejszych i bardziej przydatnych rzeczy, jaką możemy monitorować, jest ilość wykorzystywanych zasobów dyskowych. W wielu różnych projektach trafiałem na problemy z brakiem miejsca na dysku. Przykładowo Jenkins działał bezproblemowo od wielu miesięcy, aż tu nagle, bez żadnej dodatkowej aktualizacji czy zmiany ustawień, wszystkie kolejne buildy na wszystkich projektach świecą się na czerwono i straszą developerów.

O ile wydarzy się to w dniu wolnym od pracy lub gdy nie mamy zbyt dużego obciążenia projektowego, to jeszcze pół biedy. Można wtedy na spokojnie poszukać przyczyny problemu i go rozwiązać. Zazwyczaj jednak takie rzeczy, zgodnie z prawem Murphy’ego, przytrafiają się w najgorszym możliwym momencie, czyli np. gdy klient ciągle do nas telefonuje i czeka na pilną poprawkę błędu, który całkowicie zablokował mu system 🙂

Tłumaczenie, że właśnie skończyło nam się miejsce na dysku, nie jest zbyt profesjonalne – zwłaszcza że spokojnie można było zapobiec tego typu sytuacji.

Wystarczy, że będziemy monitorowali ilość zajętego miejsca na dysku i w odpowiednim momencie, np. przy wykorzystaniu 80% dostępnej przestrzeni, wzbudzimy odpowiedni alarm. Jeżeli wystarczająco wcześnie to zrobimy, to takie zgłoszenie może nawet chwilę poczekać, zanim skończymy inne ważniejsze rzeczy – w końcu JESZCZE się nie pali.

Jeżeli natomiast mamy dostępną bardzo dużą, prawie nieograniczoną przestrzeń dyskową i tak warto obserwować ilość zajętego miejsca – by nie płacić niepotrzebnie np. za zalegające stare logi itp.

Wykorzystanie CPU

Kolejnym zasobem, któremu warto się przyjrzeć, jest procesor, a właściwie – poziom jego wykorzystania. Dobrze dostrojone systemy powinny prawie całkowicie wykorzystywać dostępne zasoby i skalować się dopiero, gdy zaczynamy zbliżać się do wartości krytycznych – jak zawsze chodzi o pieniądze, nie ma sensu płacić za niewykorzystywany sprzęt.

Jeżeli jednak po wydaniu nowej wersji aplikacji średnie zużycie procesora wzrasta o 30% lub więcej, a jednocześnie nie widzimy zwiększonego ruchu – to wiedz, że coś się dzieje!

W takiej sytuacji system powinien zeskalować się, dokładając dodatkowe zasoby sprzętowe – tak, by mógł dalej poprawnie działać, jednocześnie nie blokując użytkownika. W tym samym czasie jednak odpowiednie alarmy powinny poinformować developerów, że dzieje się coś złego i trzeba szybko znaleźć przyczynę zwiększonego obciążenia. Dobrze by było, gdybyśmy już na tym etapie byli w stanie ustalić, która aplikacja konkretnie generuje większe obciążenie.

Poza obserwowaniem samych wartości liczbowych warto również przyglądać się trendom – przykładowo, jeżeli z miesiąca na miesiąc nasze odczyty są coraz wyższe, to – mimo iż nie osiągnęliśmy jeszcze wartości alarmowych – można przyjrzeć się sytuacji.

Ilość wywołań API

Jako ostatni przykład niech posłuży nam ilość wywołań naszego API. Załóżmy, że mamy restowe API, które odpowiedzialne jest za obsługę użytkowników w naszym systemie. Mamy zaimplementowane standardowe metody: GET – do pobierania użytkownika, POST – do dodawania, oraz PUT – do aktualizacji itp.

Wiemy również, że statystycznie pobieranie informacji powinno odbywać się przynajmniej 10 razy częściej niż ich aktualizacja. Jest to pierwsza rzecz, którą możemy monitorować, i jeżeli ta statystyka nie zostanie zachowana, może to świadczyć np. o uruchomieniu jakiejś migracji danych lub potencjalnej próbie ataku na nasz system. Natomiast jeżeli te wartości nie będą zachowane przez dłuższy czas, może się okazać, że nasze pierwotne założenia były błędne i powinniśmy zoptymalizować system pod kątem ilości zapisów, a nie odczytów.

W przypadku API restowych warto również obserwować zwracane kody odpowiedzi. W większości wypadków poprawne odpowiedzi w stylu 200, 201, 202 itp. powinny górować nad kodami błędów z kategorii 4** i 5**. Zwiększona ilość błędów może świadczyć o niepoprawnym działaniu naszej aplikacji lub aplikacji klienckiej, która próbuje się z nami połączyć.

Wystarczy przykładów, myślę, że jest to już zrozumiałe. Zobaczmy teraz, w jaki sposób obsługa alarmów została rozwiązana w środowisku Prometheus.

AlertManager – alarmy

AlertManager – alarmy

Alarmy w Prometheus (ang. alerts)

Prometheus definiuje alarmy jako warunek, który musi być spełniony, oraz czas określający, jak długo obserwowany warunek ma trwać by ogłosić alarm. Przykładowo wykorzystanie ponad 80% dysku przez pół godziny lub procesora na 90% przez 5 minut itp.

Każdy alarm składa się z poniższych własności:

  • unikatowa nazwa (ang. alert name) – nazwa alarmu, po której rozpoznamy, co się stało;
  • grupa alarmów (ang. alert group) – możemy np. pogrupować nasze alarmy według aplikacji lub konkretnej funkcjonalności;
  • warunek (ang. expression) – warunek, czyli wymagania, jakie muszą zostać spełnione, żeby podnieść alarm. W tym miejscu posługujemy się wyrażeniem w PromQL (czyli wewnętrznym języku wbudowanym w Prometheus – więcej na ten temat możesz przeczytać w poprzedniej części wpisu);
  • czas (ang. time) – określający, na jakiej przestrzeni czasu powyższy warunek musi być spełniony, by podnieść alarm. Dodatkowy warunek na czas został wprowadzony, by nie generować fałszywie prawdziwych (ang. false positive) alarmów w przypadku chwilowych wahań obserwowanych metryk;
  • etykiety (ang. labels) – czyli wszystkie dodatkowe etykiety, jakimi chcemy oznaczyć nasz alarm, np. poziom alarmu (ang. severity) – INFO, WARN, ERROR itp., lub zespół (ang. team), do którego później taki alarm ma zostać przekierowany. Jest to szczególnie przydatne, jeżeli później chcemy filtrować generowane alarmy.

Listę wszystkich alarmów zdefiniowanych w Prometheus możemy podejrzeć w zakładce /Alerts.

Prometheus alerts

Prometheus alerts

Stan alarmu

Każdy alarm może funkcjonować w jednym z trzech stanów:

  • nieaktywny (ang. inactive) – wszystko dobrze, warunek alarmu nie jest spełniony;
  • nierozstrzygnięty (ang. pending) – została zaobserwowana nieprawidłowość. Warunek jest spełniony, ale jeszcze nie minął czas zdefiniowany w alercie;
  • aktywny (ang. firing) – warunek był spełniony przez przynajmniej założony czas, co skutkuje podniesieniem alarmu i przekazaniem go do dalszych warstw systemu – za chwilę powiemy sobie, co może się stać z takim podniesionym alarmem.

Dodanie nowego alarmu

Żeby zdefiniować w systemie nowy alarm, należy sprawdzić konfigurację w pliku prometheus.yml. W tym miejscu powinna być wskazana ścieżka do pliku przechowującego definicje alarmów – domyślnie jest to plik alert_rules.yml.

rule_files:
   - alert_rules.yml

Poniżej przykładowy plik z definicją alarmu sprawdzającego ilość poprawnie zdeployowanych podów na Kubernetes:

groups:
- name: StormIT
  rules:
  - alert: StormIT-works  
    expr: sum by(job,app)(up) == 0
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: StormIT.pl page doesn't work

Wiemy już, jak definiować własne alarmy oraz kiedy zostaną aktywowane. Zastanówmy się teraz, co może (i co powinno!) się stać po podniesieniu naszego alarmu.

AlertManager jako strażnik i zarządca alarmów

Jednym ze sposobów dalszej obsługi alarmów jest skorzystanie z AlertManagera. AlertManager, mimo iż stanowi niezależną aplikację, dostarczany jest razem z Prometheus. Do jego głównych zadań należy przede wszystkim grupowanie, filtrowanie i wyciszanie alarmów.

AlertManager

AlertManager

Zastanówmy się jednak najpierw, po co nam kolejna aplikacja, która jeszcze bardziej skomplikuje system monitoringu?

Jak to zwykle bywa, na początku wszystko jest proste, jednak z czasem sprawy się komplikują. Wyobraź sobie, że chcemy zająć się monitoringiem trochę większego systemu. Zdefiniowaliśmy piękne alarmy, mamy ich całą masę – każdy aspekt naszej aplikacji jest dokładnie obsłużony.

W tym momencie dochodzi do problemów z siecią na naszym środowisku produkcyjnym, np. z powodu zmiany konfiguracji routera sieciowego aplikacje nie mogą połączyć się z bazą danych. Zobacz, jak zachowałaby się teraz większość alarmów. Przy założeniu, że zdefiniowaliśmy wcześniej czujkę weryfikującą problemy z siecią, zostaniemy o tym poinformowani – jednak dodatkowo zostanie aktywowana bardzo duża ilość innych alarmów. Aplikacja cały czas będzie próbowała bezskutecznie połączyć się z bazą danych, dlatego zaobserwujemy zwiększony czas odpowiedzi z usług. Po pewnym czasie nasze serwisy zaczną zwracać niestandardową ilość błędów itd. Dosłownie zostaniemy zasypani ogromną ilością alarmów, a wszystkie będą efektem jednego błędu w konfiguracji routera.

Właśnie w takich sytuacjach z pomocą przychodzi nam AlertManager. Wróćmy jeszcze na chwilę do jego głównych zadań:

  • grupowanie – pierwsza i podstawowa funkcjonalność tej aplikacji to grupowanie alarmów. Jeżeli jesteśmy zalewani przez ogromną ilość takich samych alarmów, to możemy tak skonfigurować AlertManager, by przepuścił tylko pierwszy alarm danej kategorii, a później wysyłał już zgrupowaną informację, że taka sytuacja występuje np. co 30 minut.
  • filtrowanie – w przedstawionym przykładzie z góry wiemy, że jeżeli będą problemy z dostępem do bazy danych, to usługi zaczną zwracać większą ilość błędów, dlatego nie ma sensu raportować tego faktu, tylko lepiej skupić się na prawdziwym problemie. Możemy tak skonfigurować zależności między alarmami, że jeżeli alarm wyższego poziomu zostanie wzbudzony, to wszystkie zależne od niego będą już ignorowane.
  • wyciszanie – mamy również możliwość wyłączania konkretnych alarmów, np. na czas testów lub – jeżeli uznamy je za błędne – na czas ich naprawy.

Instalacja AlertManager

Przy instalacji AlertManager, podobnie jak podczas instalacji Prometheus, skorzystałem z przygotowanych obrazów Docker. Możesz je pobrać ze strony GitHub projektu lub bezpośrednio z Quay.

docker pull quay.io/prometheus/alertmanager

Konfiguracja AlertManager

W pierwszym kroku musimy wskazać w konfiguracji Prometheus, gdzie znajduje się instancja AlertManager.

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
       - alertmanager:9093

W kolejnym kroku modyfikujemy już konfigurację samego AlertManager w pliku alertmanager.yml.

global:
  resolve_timeout: 5m

route:
  group_by: ['alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 1h
  receiver: 'web.hook'
receivers:
- name: 'web.hook'
  webhook_configs:
  - url: 'http://127.0.0.1:5001/'

Powyższy przykład to podstawowa konfiguracja pozwalająca zgrupować alerty i przekazać je do zewnętrznego endpointu. AlertManager ma wbudowany jednak szereg różnych integracji, można go np. skonfigurować tak, by wysyłał komunikaty na adres e-mail, Hipchat, PagerDutyPushover, SlackOpsGenieVictorOpsWeChat itp. Więcej na ten temat możesz przeczytać w dokumentacji AlertManager Configuration.

W przykładzie pojawiły się aż trzy ustawienia odnośnie czasów, dlatego warto wyjaśnić, co one oznaczają:

  • group_wait – jak długo alarmy z tej samej grupy mają czekać w buforze przed pierwszym wysłaniem;
  • group_interval – jak długo system ma czekać na ponowne wysłanie grupy, jeżeli doszło do niej coś nowego;
  • repeat_interval – po jakim czasie wysłać ten sam alarm, jeżeli dalej jest aktywny.

Podsumowanie AlertManager

Wszelkiego rodzaju automatyzacje i monitoring to już swego rodzaju standard w nowoczesnych rozwiązaniach IT i zdecydowanie warto inwestować swój czas i wysiłek w ich wdrażanie.

Masz przed sobą gotowy przepis, jak można zorganizować monitoring w Twoim projekcie. Mam nadzieję, że na samym przeczytaniu się nie skończy i uda Ci się przynajmniej przemyśleć część przedstawionych tu rozwiązań. Dowiedziałeś się dziś:

  • jak automatyzować monitoring;
  • dlaczego warto monitorować aplikacje;
  • co warto monitorować;
  • jak działa system alarmów w Prometheus;
  • jaki jest cykl życia alarmu;
  • jak zdefiniować nowy alarm;
  • jak zainstalować i skonfigurować AlertManager;
  • jakie są główne zadania AlertManager.

Zastosowanie tej wiedzy w praktyce z pewnością pomoże Ci utrzymać Twoje aplikacje w lepszej kondycji i za sprawą automatyzacji z większym spokojem myśleć o kolejnych aktualizacjach systemu.

W kolejnej części serii przyjrzymy się bliżej możliwościom wizualizacji metryk w Grafana.


20+ BONUSOWYCH materiałów z programowania

e-book – „8 rzeczy, które musisz wiedzieć, żeby dostać pracę jako programista”,
e-book – „Java Cheat Sheet”,
checklista – „Pytania rekrutacyjne”
i wiele, wiele wiecej!

Jak zostać programistą

2 komentarze
Share:

2 Comments

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *