
Cześć wszystkim, nazywam się Aleksander, pracuję w CIAN jako inżynier i zajmuję się zarządzaniem systemami oraz automatyzacją procesów infrastrukturalnych. W komentarzach do jednego z wcześniejszych artykułów poproszono nas o opowiedzenie, skąd bierzemy 4 TB logów dziennie i co z nimi robimy. Tak, mamy dużo logów, a do ich przetwarzania stworzyliśmy osobny klaster infrastrukturalny, który pozwala nam szybko rozwiązywać problemy. W tym artykule opowiem, jak przez rok dostosowaliśmy go do pracy z ciągle rosnącym strumieniem danych.
Od czego zaczynaliśmy

W ciągu ostatnich kilku lat obciążenie na cian.ru rosło bardzo szybko, a w trzecim kwartale 2018 roku odwiedzalność zasobu osiągnęła 11,2 mln unikalnych użytkowników miesięcznie. W krytycznych momentach traciliśmy do 40% logów, przez co nie mogliśmy szybko zajmować się incydentami i traciliśmy bardzo dużo czasu i energii na ich rozwiązanie. Często też nie mogliśmy znaleźć przyczyny problemu, a on powtarzał się po pewnym czasie. To był koszmar, z którym trzeba było coś zrobić.
W tym czasie do przechowywania logów używaliśmy klastra złożonego z 10 węzłów danych z ElasticSearch w wersji 5.5.2 z typowymi ustawieniami indeksów. Został on wprowadzony ponad rok temu jako popularne i dostępne rozwiązanie: wtedy strumień logów nie był tak duży, więc nie było sensu wymyślać niestandardowych konfiguracji.
Przetwarzanie nadchodzących logów zapewniał Logstash na różnych portach na pięciu koordynatorach ElasticSearch. Jeden indeks, niezależnie od rozmiaru, składał się z pięciu shardów. Była zorganizowana godzinowa i dzienna rotacja, w wyniku czego co godzinę w klastrze pojawiało się około 100 nowych shardów. Dopóki logów było niewiele, klaster sobie radził i nikt nie zwracał uwagi na jego ustawienia.
Problemy z szybkim wzrostem
Wielkość generowanych logów rosła bardzo szybko, ponieważ na siebie nakładały się dwa procesy. Z jednej strony użytkowników serwisu przybywało coraz więcej. Z drugiej strony zaczęliśmy aktywnie przechodzić na architekturę mikrousług, dzieląc nasze stare monolity na C# i Pythona. Kilkadziesiąt nowych mikrousług, zastępujących części monolitu, generowało znacznie więcej logów dla infrastrukturalnego klastra.
To właśnie skalowanie doprowadziło do tego, że klaster stał się praktycznie niezarządzalny. Kiedy logi zaczęły przychodzić z prędkością 20 tysięcy komunikatów na sekundę, częsta bezużyteczna rotacja zwiększyła liczbę shardów do 6 tysięcy, a na jeden węzeł przypadało ponad 600 shardów.
Prowadziło to do problemów z przydzielaniem pamięci operacyjnej, a przy awarii węzła rozpoczynała się równoczesna migracja wszystkich shardów, co zwiększało ruch i obciążało pozostałe węzły, co praktycznie uniemożliwiało zapis danych w klastrze. W tym czasie pozostawaliśmy bez logów. A przy problemie z serwerem traciliśmy 1/10 klastra. Dodatkowo, duża liczba indeksów małych rozmiarów wprowadzała dodatkowe trudności.
Bez logów nie rozumieliśmy przyczyn incydentów i mogliśmy prędzej czy później natknąć się na te same problemy ponownie, a w ideologii naszego zespołu było to niedopuszczalne, ponieważ wszystkie mechanizmy naszej pracy są nastawione na to, aby nigdy nie powtarzać tych samych problemów. Potrzebowaliśmy pełnego zestawu logów i ich dostarczania praktycznie w czasie rzeczywistym, ponieważ zespół inżynierów dyżurnych monitorował alerty nie tylko z metryk, ale również z logów. Dla zrozumienia skali problemu — w tym czasie całkowita ilość logów wynosiła około 2 TB dziennie.
Postawiliśmy sobie zadanie — całkowicie wyeliminować utratę logów i skrócić czas ich dostarczania do klastra ELK maksymalnie do 15 minut w przypadku sytuacji awaryjnych (na tę liczbę w dalszej części opieraliśmy się jako na wewnętrznym KPI).
Nowy mechanizm rotacji i hot-warm węzły

Transformację klastra rozpoczęliśmy od aktualizacji wersji ElasticSearch z 5.5.2 do 6.4.3. Nasz klaster wersji 5 kolejny raz się zawiesił, więc zdecydowaliśmy się go wyłączyć i całkowicie zaktualizować — logów i tak nie było. Tak więc tę migrację przeprowadziliśmy w zaledwie kilka godzin.
Największą zmianą na tym etapie było wprowadzenie na trzech węzłach z koordynatorem jako bufor pośredni Apache Kafka. Broker wiadomości uwolnił nas od utraty logów podczas problemów z ElasticSearch. Jednocześnie dodaliśmy do klastra 2 węzły i przeszliśmy na architekturę hot-warm z trzema „gorącymi” węzłami, umiejscowionymi w różnych szafach w centrum danych. Na nich kierowaliśmy logi, których nie można było stracić w żadnym wypadku — nginx oraz logi błędów aplikacji. Na pozostałe węzły przesyłane były logi mniejsze — debug, warning itp., a także po 24 godzinach przenoszone były „ważne” logi z „gorących” węzłów.
Aby nie zwiększać liczby małych indeksów, przeszliśmy z rotacji czasowej na mechanizm rollover. Na forach było wiele informacji na temat tego, że rotacja według rozmiaru indeksu jest bardzo niewiarygodna, dlatego postanowiliśmy użyć rotacji według liczby dokumentów w indeksie. Przeanalizowaliśmy każdy indeks i ustaliliśmy liczbę dokumentów, po której powinno nastąpić rotacja. W ten sposób osiągnęliśmy optymalny rozmiar sharda — nie więcej niż 50 GB.
Optymalizacja klastra

Jednak całkowicie nie pozbyliśmy się problemów. Niestety, nadal pojawiały się małe indeksy: nie osiągały one określonej wielkości, nie rotowały się i były usuwane podczas globalnego czyszczenia indeksów starszych niż trzy dni, ponieważ zrezygnowaliśmy z rotacji według daty. Prowadziło to do utraty danych, ponieważ indeks znikał całkowicie z klastra, a próba zapisu do nieistniejącego indeksu łamała logikę curatora, którego używaliśmy do zarządzania. Alias do zapisu przekształcał się w indeks i łamał logikę rollovera, powodując niekontrolowany wzrost niektórych indeksów do 600 GB.
Na przykład dla konfiguracji rotacji:
curator-elk-rollover.yaml
---
actions:
1:
action: rollover
options:
name: "nginx_write"
conditions:
max_docs: 100000000
2:
action: rollover
options:
name: "python_error_write"
conditions:
max_docs: 10000000
W przypadku braku rollover alias występował błąd:
ERROR alias "nginx_write" not found.
ERROR Failed to complete action: rollover. : Unable to perform index rollover with alias "nginx_write".
Rozwiązanie tego problemu zostawiliśmy na następną iterację i zajęliśmy się inną kwestią: przeszliśmy na logikę pull w pracy Logstasha, zajmującego się przetwarzaniem przychodzących logów (usuwaniem zbędnych informacji i wzbogacaniem ich). Umieściliśmy go w dockera, który uruchamiamy przez docker-compose, tam również umieściliśmy logstash-exportera, który przekazuje metryki do Prometheusa do bieżącego monitorowania przepływu logów. Dzięki temu umożliwiliśmy sobie płynne zmienianie liczby instancji logstasha odpowiedzialnych za przetwarzanie każdego typu logów.
Podczas gdy doskonaliliśmy klaster, frekwencja na cian.ru wzrosła do 12,8 mln unikalnych użytkowników miesięcznie. W rezultacie nasze przekształcenia nie nadążały nieco za zmianami na produkcji, i napotkaliśmy problem, że „ciepłe” węzły nie radziły sobie z obciążeniem i spowalniały cały przepływ logów. Dane „gorące” otrzymywaliśmy bez zakłóceń, ale w dostawę pozostałych musieliśmy ingerować i przeprowadzać ręczną rotację, aby równomiernie rozdzielić indeksy.
Przy tym skalowanie i zmiana ustawień instancji logstasha w klastrze komplikowały się tym, że był to lokalny docker-compose, a wszystkie działania były wykonywane ręcznie (aby dodać nowe końcówki, należało ręcznie przejść przez wszystkie serwery i wszędzie wykonać docker-compose up -d).
Przegrupowanie logów
W wrześniu tego roku nadal kontynuowaliśmy rozdzielanie monolitu, obciążenie klastra wzrastało, a przepływ logów zbliżał się do 30 tysięcy wiadomości na sekundę.

Następną iterację rozpoczęliśmy od aktualizacji sprzętu. Z pięciu koordynatorów przeszliśmy na trzech, wymieniliśmy węzły danych i zyskaliśmy pod względem kosztów i pojemności magazynu. Dla węzłów używamy dwóch konfiguracji:
- Dla „gorących” węzłów: E3-1270 v6 / 960Gb SSD / 32 Gb x 3 x 2 (3 dla Hot1 i 3 dla Hot2).
- Dla „ciepłych” węzłów: E3-1230 v6 / 4Tb SSD / 32 Gb x 4.
Na tej iteracji wydzieliliśmy indeks z logami dostępu mikroserwisów, który zajmuje tyle samo miejsca, co logi frontowe nginx, do drugiej grupy z trzech „gorących” węzłów. Dane na „gorących” węzłach przechowujemy teraz przez 20 godzin, a następnie przenosimy je na „ciepłe” do pozostałych logów.
Problem znikania małych indeksów rozwiązaliśmy poprzez dostosowanie ich rotacji. Teraz indeksy rotują się co 23 godziny, nawet jeśli są tam niewielkie dane. To nieznacznie zwiększyło liczbę shardów (jest ich teraz około 800), ale z perspektywy wydajności klastra jest to do zaakceptowania.
W wyniku tego w klastrze są teraz sześć „gorących” i tylko cztery „ciepłe” węzły. Powoduje to niewielkie opóźnienia w zapytaniach w długich odstępach czasu, ale zwiększenie liczby węzłów w przyszłości rozwiąże ten problem.
W tej iteracji naprawiliśmy również problem braku półautomatycznego skalowania. W tym celu uruchomiliśmy infrastrukturę klastra Nomad — podobną do tej, która już działa w naszym środowisku produkcyjnym. Na razie liczba Logstash nie zmienia się automatycznie w zależności od obciążenia, ale do tego też dojdziemy.

Plany na przyszłość
Zrealizowana konfiguracja świetnie się skalował, a obecnie przechowujemy 13,3 TB danych — wszystkie logi za 4 dni, co jest niezbędne do pilnego rozpatrzenia alertów. Część logów przekształcamy w metryki, które przechowujemy w Graphite. Aby ułatwić pracę inżynierów, mamy metryki dla klastra infrastrukturalnego oraz skrypty do półautomatycznej naprawy typowych problemów. Po zwiększeniu liczby węzłów danych, które planujemy na przyszły rok, przejdziemy do przechowywania danych przez 4 do 7 dni. To wystarczy na operacyjną pracę, ponieważ zawsze staramy się jak najszybciej badać incydenty, a do długoterminowych badań są dane telemetryczne.
W październiku 2019 roku liczba odwiedzin cian.ru wzrosła już do 15,3 miliona unikalnych użytkowników miesięcznie. To była poważna próba architektonicznego rozwiązania do dostarczania logów.
Obecnie przygotowujemy się do aktualizacji ElasticSearch do wersji 7. Jednak w tym celu będziemy musieli zaktualizować mapping wielu indeksów w ElasticSearch, ponieważ przeszły one z wersji 5.5 i zostały uznane za przestarzałe w wersji 6 (w wersji 7 ich po prostu nie ma). To oznacza, że w trakcie aktualizacji z pewnością wystąpią jakieś nieprzewidziane problemy, które na czas rozwiązania pozostawią nas bez logów. Z wersji 7 najbardziej czekamy na Kibana z ulepszonym interfejsem i nowymi filtrami.
Główny cel osiągnęliśmy: przestaliśmy tracić logi i skróciliśmy czas przestoju infrastruktury z 2-3 awarii w tygodniu do kilku godzin prac serwisowych w miesiącu. Cała ta praca w produkcji jest niemal niewidoczna. Jednak teraz możemy dokładnie określić, co dzieje się z naszym serwisem, możemy szybko działać w spokojnym trybie i nie martwić się, że logi zostaną utracone. Ogólnie rzecz biorąc, jesteśmy zadowoleni, szczęśliwi i przygotowujemy się do nowych wyzwań, o których opowiemy później.
Źródło: habr.com
