
By the year 2019, we still didn’t have a standard solution for log aggregation in Kubernetes. In this article, we would like to share our searches, encountered problems, and their solutions using examples from real practice.
However, I must note that different clients have very different understandings of log collection:
- some want to see security and audit logs;
- some want centralized logging for the entire infrastructure;
- while others only need to collect application logs, excluding, for example, load balancers.
About how we implemented various 'wants' and what difficulties we encountered — below.
Theory: on tools for logs
Background on logging system components
Logging has come a long way, leading to the development of methodologies for log collection and analysis, which we use today. Even in the 1950s, Fortran had an analog of standard input-output streams that helped programmers debug their programs. These were the first computer logs that made life easier for programmers of that time. Today, we see the first component of the logging system in them — the source or 'producer' of logs.
Computer science did not stand still: computer networks and the first clusters emerged... Complex systems consisting of multiple computers began to operate. Now system administrators had to collect logs from several machines, and in special cases, they could also add kernel messages in case of needing to investigate a system failure. To describe centralized log collection systems, in the early 2000s, , which standardized remote_syslog. This introduced another important component: the log collector and their storage.
With the increase in log volume and the widespread adoption of web technologies, the question arose of how to conveniently display logs to users. More advanced log viewers emerged to replace simple console tools (awk/sed/grep) — the third component.
W związku ze wzrostem ilości logów stało się jasne także coś innego: logi są potrzebne, ale nie wszystkie. Różne logi wymagają różnego poziomu przechowywania: niektóre można stracić po dniu, a inne — trzeba przechowywać przez 5 lat. W ten sposób do systemu logowania dodano komponent filtrowania i routingu strumieni danych — nazwijmy go filtrem.
Magazyny również przeszły poważną ewolucję: z zwykłych plików przeniosły się do relacyjnych baz danych, a następnie do magazynów dokumentów (na przykład Elasticsearch). W ten sposób magazyn oddzielił się od kolektora.
Ostatecznie sama koncepcja logu rozszerzyła się do pewnego abstrakcyjnego strumienia wydarzeń, które chcemy zachować dla historii. A dokładniej — na wypadek, gdy zajdzie konieczność przeprowadzenia dochodzenia lub sporządzenia analizy…
W rezultacie, w stosunkowo krótkim czasie, zbieranie logów rozwinęło się w ważny podsystem, który z pełnym prawem można nazwać jednym z działów Big Data.

Jeśli kiedyś zwykłe printy mogły być wystarczające dla „systemu logowania”, to teraz sytuacja mocno się zmieniła.
Kubernetes i logi
Kiedy do infrastruktury wszedł Kubernetes, istniejący już problem zbierania logów nie omijał także jego. W pewnym sensie stał się nawet bardziej dotkliwy: zarządzanie platformą infrastrukturalną zostało nie tylko uproszczone, ale także jednocześnie skomplikowane. Wiele starych usług zaczęło migrację na mikrousługowe tory. W kontekście logów wyraziło się to w rosnącej liczbie źródeł logów, ich szczególnym cyklu życia oraz konieczności śledzenia przez logi powiązań wszystkich komponentów systemu…
Wyprzedzając fakty, mogę stwierdzić, że obecnie, niestety, nie ma standardyzowanej wersji logowania dla Kubernetes, która korzystnie wyróżniałaby się spośród innych. Najbardziej popularne w społeczności schemy sprowadzają się do następujących:
- ktoś wdraża stos EFK (Elasticsearch, Fluentd, Kibana);
- ktoś — próbuje niedawno wydanego lub używa ;
- nas (a może nie tylko nas?..) w dużej mierze zadowala własny rozwój — …
Zazwyczaj wykorzystujemy takie zestawy w klastrach K8s (dla rozwiązań self-hosted):
- ;
- .
Jednak nie będę skupiał się na instrukcjach dotyczących ich instalacji i konfiguracji. Zamiast tego, skoncentruję się na ich wadach i bardziej ogólnych wnioskach dotyczących sytuacji z logami jako całością.
Praktyka z logami w K8s

„Codzienne logi”, ilu was jest?..
Centralizowane zbieranie logów z dość dużej infrastruktury wymaga znacznych zasobów, które zostaną wykorzystane do zbierania, przechowywania i przetwarzania logów. W trakcie eksploatacji różnych projektów napotkaliśmy różne wymagania i wynikające z nich problemy.
Spróbujmy ClickHouse
Przyjrzyjmy się centralnemu magazynowi w projekcie z aplikacją, która generuje logi dość aktywnie: ponad 5000 wierszy na sekundę. Rozpoczniemy pracę z jego logami, zapisując je w ClickHouse.
Gdy tylko wymagany będzie maksymalny czas rzeczywisty, 4-rdzeniowy serwer z ClickHouse już będzie przeciążony pod względem systemu dyskowego:

Tego rodzaju obciążenie związane jest z tym, że próbujemy jak najszybciej pisać do ClickHouse. Na to baza danych reaguje zwiększonym obciążeniem dysku, przez co może zgłaszać takie błędy:
DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts
Chodzi o to, że w ClickHouse (przechowują dane logów) mają swoje trudności przy operacjach zapisu. Wstawiane do nich dane generują tymczasową partycję, która następnie łączy się z główną tabelą. W rezultacie zapis staje się bardzo wymagający dla dysku, a także ogranicza go ostrzeżenie, o którym wspomnieliśmy powyżej: w ciągu 1 sekundy może połączyć się nie więcej niż 300 subpartycji (faktycznie to 300 insertów na sekundę).
Aby uniknąć takiego zachowania, w jak największych paczkach i nie częściej niż raz na 2 sekundy. Jednak zapis dużymi paczkami oznacza, że musimy rzadziej pisać do ClickHouse. To z kolei może prowadzić do przepełnienia bufora i utraty logów. Rozwiązaniem jest zwiększenie bufora Fluentd, ale wtedy wzrośnie także zużycie pamięci.
Uwaga: Inny problematyczny aspekt naszego rozwiązania z ClickHouse był związany z tym, że partycjonowanie w naszym przypadku (loghouse) zostało zrealizowane poprzez zewnętrzne tabele, powiązane To extract data over large time intervals, excessive memory is required because the metadata table scans all partitions — even those that do not contain the necessary data. However, this approach can now be confidently deemed outdated for current versions of ClickHouse. ).
Ultimately, it becomes clear that not every project has sufficient resources for real-time log collection in ClickHouse (more precisely, their distribution would not be sensible). Additionally, it will be necessary to use an accumulator, to which we will return later. The situation described above is real. At that time, we could not offer a reliable and stable solution that would satisfy the client and allow collecting logs with minimal latency…
What about Elasticsearch?
It is known that Elasticsearch handles large loads. Let's try it in the same project. Now the load looks as follows:

Elasticsearch managed to process the data stream; however, writing such volumes to it heavily utilizes the CPU. This is resolved by organizing a cluster. Technically, this is not a problem, but it turns out that we are already using about 8 cores just for the log collection system and have an additional high-load component in the system…
Conclusion: such an option may be justified, but only if the project is large and its management is ready to allocate significant resources for a centralized logging system.
Then a natural question arises:
Which logs are actually needed?
Let’s try to change the approach: logs should be both informative and not cover every event in the system.
Suppose we have a successful online store. What logs are important? Collecting maximum information, for example, from the payment gateway — is a great idea. However, from the image cropping service in the product catalog, not all logs are critical for us: only errors and extended monitoring (for instance, the percentage of 500 errors generated by this component) are sufficient.
Thus, we arrive at the conclusion that centralized logging is not justified at all times.Very often, the client wants to collect all logs in one place, while in reality, only about 5% of the log messages are critically important for the business:
- Czasami wystarczy skonfigurować, powiedzmy, tylko rozmiar logów kontenera i zbieracza błędów (na przykład Sentry).
- Do badania incydentów często wystarczy powiadomienie o błędzie oraz istotny lokalny log.
- Mieliśmy projekty, które całkowicie polegały jedynie na testach funkcjonalnych i systemach zbierania błędów. Programiści nie potrzebowali logów jako takich – wszystko widzieli w śladach błędów.
Ilustracja z życia
Dobrze ilustruje to inna historia. Otrzymaliśmy zapytanie od zespołu bezpieczeństwa jednego z klientów, który już używał komercyjnego rozwiązania, opracowanego dawno przed wdrożeniem Kubernetes.
Zachodziła potrzeba „połączenia” systemu centralnego zbierania logów z korporacyjnym czujnikiem wykrywania problemów – QRadar. Ten system potrafi przyjmować logi przez protokół syslog i pobierać je z FTP. Integracja z wtyczką remote_syslog dla fluentd nie została jednak od razu zrealizowana. (jak się okazało, ). Problemy z konfiguracją QRadar wystąpiły po stronie zespołu bezpieczeństwa klienta.
W efekcie część logów kluczowych dla biznesu była przesyłana na FTP QRadar, a druga część – kierowana bezpośrednio z węzłów przez remote syslog. W tym celu nawet napisaliśmy możliwe, że pomoże to komuś rozwiązać podobne zadanie… Dzięki uzyskanej schemie klient mógł uzyskać i analizować krytyczne logi (za pomocą swojego ulubionego narzędzia), a my mogliśmy zmniejszyć koszty systemu logowania, zachowując jedynie ostatni miesiąc.
Kolejny przykład jest dość wymowny w tym, jak nie należy postępować. Jeden z naszych klientów prowadził wieloliniowe każdej nieustrukturyzowane wyjście informacji do logu w odpowiedzi na zdarzenie z użytkownika. Jak łatwo się domyślić, takie logi były niezwykle niepraktyczne do czytania i przechowywania. Kryteria dla logów
Podobne przykłady prowadzą do wniosku, że oprócz wyboru systemu zbierania logów trzeba
zaplanować również same logi! Jakie są tutaj wymagania?Logi muszą być w formacie czytelnym dla maszyn (na przykład JSON).
- Logi powinny być kompaktowe i mieć możliwość zmiany stopnia logowania, aby debugować potencjalne problemy. W środowiskach produkcyjnych należy uruchamiać systemy z poziomem logowania takim jak
- Error. Ostrzeżenie lub Error.
- Logi muszą być znormalizowane, co oznacza, że w obiekcie loga wszystkie ciągi powinny mieć ten sam typ pola.
Niezorganizowane logi mogą doprowadzić do problemów z ładowaniem logów w magazynie oraz całkowitym zatrzymaniem ich przetwarzania. Jako ilustrację — przykład z błędem 400, z którym wielu z pewnością się spotkało w logach fluentd:
2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"
Błąd ten oznacza, że wysyłasz do indeksu z gotowym mappingiem pole, którego typ jest niestabilny. Najprostszym przykładem jest pole w logu nginx z zmienną $upstream_status. Może ono zawierać zarówno liczbę, jak i ciąg. Na przykład:
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}
W logach widać, że serwer 10.100.0.10 odpowiedział błędem 404, a żądanie zostało skierowane do innego magazynu treści. W rezultacie, w logach wartość stała się następująca:
"upstream_response_time": "0.001, 0.007"
Ta sytuacja jest tak powszechna, że zasłużyła na osobne .
A co z niezawodnością?
Zdarzają się sytuacje, w których wszystkie logi są niezbędne bez wyjątku. Z tymi typowymi schematami zbierania logów dla K8s, wymienionymi/rozważanymi powyżej, są pewne problemy.
Na przykład, fluentd nie może zbierać logów z krótkotrwałych kontenerów. W jednym z naszych projektów kontener z migracją baz danych żył mniej niż 4 sekundy, a następnie został usunięty — zgodnie z odpowiednią adnotacją:
"helm.sh/hook-delete-policy": hook-succeeded
Z tego powodu log wykonania migracji nie trafił do magazynu. W tej sytuacji pomocna może być polityka before-hook-creation.
Inny przykład — rotacja logów Docker. Powiedzmy, że jest aplikacja, która aktywnie zapisuje w logach. W normalnych warunkach udaje nam się przetworzyć wszystkie logi, ale kiedy pojawia się problem — na przykład jak opisano powyżej z nieprawidłowym formatem — przetwarzanie zostaje zatrzymane, a Docker rotuje plik. W efekcie mogą zostać utracone krytyczne dla biznesu logi.
Dlatego ważne jest oddzielanie strumieni logów, wbudowując wysyłkę najbardziej wartościowych bezpośrednio w aplikację, aby zapewnić ich bezpieczeństwo. Ponadto, dobrze byłoby stworzyć jakiś „akumulator” logów, który będzie mógł przetrwać krótką niedostępność magazynu przy zachowaniu krytycznych wiadomości.
Na koniec, nie zapominajmy, że każdą podsystemę ważne jest monitorowanie wysokiej jakości. W przeciwnym razie łatwo wystąpić w sytuacji, w której fluentd znajduje się w stanie CrashLoopBackOff i nic nie wysyła, a to oznacza utratę ważnych informacji.
Wnioski
W tym artykule nie omawiamy rozwiązań SaaS, takich jak Datadog. Wiele z opisanych tutaj problemów zostało już rozwiązanych przez firmy komercyjne specjalizujące się w zbieraniu logów, ale nie wszyscy mogą korzystać z SaaS z różnych powodów. (główne to koszt i przestrzeganie 152-FZ).
Centralne zbieranie logów wydaje się prostym zadaniem, ale wcale takie nie jest. Ważne jest, aby pamiętać, że:
- Szczegółowe logowanie powinno dotyczyć tylko krytycznych komponentów, a dla pozostałych systemów można skonfigurować monitorowanie i zbieranie błędów.
- Logi w produkcji powinny być minimalne, aby nie obciążały nadmiernie systemu.
- Logi muszą być czytelne maszynowo, znormalizowane i mieć ścisły format.
- Naprawdę krytyczne logi należy wysyłać osobnym strumieniem, który powinien być oddzielony od głównego.
- Warto przemyśleć akumulator logów, który może uratować przed wzrostami obciążenia i uczynić obciążenie przechowywania bardziej równomiernym.

Te proste zasady, jeśli będą stosowane wszędzie, pozwoliłyby na funkcjonowanie opisanych wyżej schematów — nawet mimo że brakuje w nich ważnych komponentów (akumulatora). Jeśli jednak nie będziesz przestrzegać takich zasad, zadanie łatwo doprowadzi cię i infrastrukturę do kolejnego wysoko obciążonego (a jednocześnie mało efektywnego) komponentu systemu.
P.S.
Przeczytaj także na naszym blogu:
- «»;
- «»;
- «».
Źródło: habr.com
