Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität

2019 lief, und wir hatten immer noch keine Standardlösung zur Aggregation von Logs in Kubernetes. In diesem Artikel möchten wir anhand von Beispielen aus der Praxis unsere Suche, die aufgetretenen Probleme und deren Lösungen teilen.

Allerdings möchte ich vorweg erwähnen, dass verschiedene Kunden unter der Sammlung von Logs sehr unterschiedliche Dinge verstehen:

  • manche möchten Sicherheits- und Audit-Logs sehen;
  • andere wünschen eine zentrale Protokollierung der gesamten Infrastruktur;
  • und einige sind zufrieden, nur die Anwendungsprotokolle zu sammeln, indem sie beispielsweise die Lastverteilersysteme ausschließen.

Wie wir verschiedene "Wünsche" umgesetzt haben und mit welchen Schwierigkeiten wir konfrontiert waren, - darunter mehr.

Theorie: über Werkzeuge für Logs

Hintergrund über die Komponenten eines Protokollierungssystems

Die Protokollierung hat einen langen Weg zurückgelegt, auf dem Methoden zur Sammlung und Analyse von Logs entwickelt wurden, die wir heute anwenden. Schon in den 1950er Jahren entstand im Fortran ein Analogon der Standard-Ein- und Ausgabeströme, die den Programmierern bei der Fehlersuche ihrer Programme halfen. Das waren die ersten Computertagebücher, die das Leben der Programmierer jener Zeit erleichterten. Heute sehen wir in ihnen die erste Komponente des Protokollierungssystems - eine Quelle oder "Produzent" (producer) von Logs.

Die Informatik stand nicht still: Computernetzwerke, die ersten Cluster… Komplexe Systeme, die aus mehreren Computern bestanden, begannen zu arbeiten. Jetzt waren Systemadministratoren gezwungen, Logs von mehreren Maschinen zu sammeln, und in besonderen Fällen konnten sie auch Meldungen des Betriebssystemkerns hinzufügen, falls eine Untersuchung eines Systemausfalls erforderlich war. Um zentrale Log-Sammelsysteme zu beschreiben, erschien Anfang der 2000er Jahre RFC 3164, das remote_syslog standardisierte. So entstand ein weiterer wichtiger Bestandteil: ein Log-Collector (Sammler) und deren Speicherung.

Mit dem Anstieg des Logvolumens und der weitverbreiteten Einführung von Webtechnologien stellte sich die Frage, wie man Logs den Benutzern bequem anzeigen kann. Anstelle einfacher Konsolenwerkzeuge (awk/sed/grep) kamen fortschrittlichere Log-Viewer – die dritte Komponente.

Mit der Zunahme des Log-Volumens wurde auch klar, dass Logs notwendig sind, aber nicht alle. Zudem erfordern unterschiedliche Logs unterschiedliche Sicherheitsstufen: Einige können nach einem Tag verloren gehen, andere müssen fünf Jahre lang aufbewahrt werden. So wurde ein Filterkomponente für das Logging-System hinzugefügt, nennen wir ihn Filter.

Die Speicher haben ebenfalls einen ernsthaften Sprung gemacht: Von gewöhnlichen Dateien wurden relationale Datenbanken und anschließend dokumentenorientierte Speicher (wie Elasticsearch) übernommen. So hat sich das Speicher im Vergleich zum Collector weiterentwickelt.

Letztendlich hat sich das Konzept des Logs zu einem abstrakten Ereignisfluss erweitert, den wir für die Geschichte festhalten möchten. Genauer gesagt — für den Fall, dass wir eine Untersuchung durchführen oder einen Analysebericht erstellen müssen ...

Insgesamt hat sich das Sammeln von Logs in relativ kurzer Zeit zu einem wichtigen Teilsystem entwickelt, das man zu Recht als einen der Unterbereiche der Big Data bezeichnen kann.

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität
Wo früher einfache print-Befehle für ein "Logging-System" ausreichten, hat sich die Situation nun erheblich geändert.

Kubernetes und Logs

Als Kubernetes in die Infrastruktur kam, blieb das bereits bestehende Problem des Log-Sammelns nicht außen vor. In gewisser Weise wurde es sogar noch schmerzhafter: Das Management der Infrastrukturplattform wurde nicht nur vereinfacht, sondern gleichzeitig auch komplizierter. Viele alte Dienste begannen mit der Migration auf Mikrodienste. Im Kontext der Logs zeigte sich dies in der wachsenden Anzahl von Log-Quellen, ihrem speziellen Lebenszyklus und der Notwendigkeit, die Beziehungen aller Systemkomponenten durch die Logs zu verfolgen ...

Vorweg kann ich feststellen, dass es derzeit leider keinen standardisierten Logging-Ansatz für Kubernetes gibt, der sich vorteilhaft von allen anderen unterscheidet. Die beliebtesten Ansätze in der Community lassen sich wie folgt zusammenfassen:

  • jemand richtet einen Stack EFK (Elasticsearch, Fluentd, Kibana);
  • jemand anderes probiert den neu veröffentlichten Loki oder verwendet Logging-Operator;
  • uns (und vielleicht nicht nur uns? ..) oft zufrieden mit einer eigenen Entwicklung — loghouse

In der Regel verwenden wir solche Kombinationen in K8s-Clustern (für Self-Hosted-Lösungen):

Ich werde jedoch nicht auf die Anleitungen zu ihrer Installation und Konfiguration eingehen. Stattdessen konzentriere ich mich auf ihre Nachteile und die allgemeine Situation mit den Logs.

Praxis mit Logs in K8s

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität

„Alltagslogs“, wie viele von euch gibt es?..

Die zentralisierte Sammlung von Logs aus einer ausreichend großen Infrastruktur erfordert erhebliche Ressourcen, die für das Sammeln, Speichern und Verarbeiten von Logs aufgewendet werden. Während der Nutzung verschiedener Projekte sind wir auf unterschiedliche Anforderungen und die daraus resultierenden Probleme gestoßen.

Versuchen wir ClickHouse

Lassen Sie uns ein zentrales Speicher für ein Projekt betrachten, das eine Anwendung enthält, die ziemlich aktiv Logs generiert: über 5000 Zeilen pro Sekunde. Wir beginnen mit der Arbeit an seinen Logs und speichern sie in ClickHouse.

Sobald maximaler Echtzeitsupport erforderlich ist, wird ein 4-Kern-Server mit ClickHouse bereits durch das Speichersystem überlastet:

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität

Diese Art der Belastung hängt damit zusammen, dass wir versuchen, so schnell wie möglich in ClickHouse zu schreiben. Die Datenbank reagiert darauf mit erhöhtem Speicher-IO, was zu solchen Fehlern führen kann:

DB::Exception: Zu viele Teile (300). Die Zusammenführungen verarbeiten sich deutlich langsamer als die Einfügungen.

Das Problem ist, dass MergeTree-Tabellen in ClickHouse (in denen die Logdaten liegen) ihre eigenen Schwierigkeiten bei Schreiboperationen haben. Die eingefügten Daten generieren eine temporäre Partition, die dann mit der Haupttabelle zusammengeführt wird. Dadurch wird das Schreiben sehr festplattenintensiv, und es gibt eine Begrenzung, über die wir oben informiert wurden: Pro Sekunde können nicht mehr als 300 Subpartitionen zusammengeführt werden (tatsächlich sind das 300 Einfügungen pro Sekunde).

Um ein solches Verhalten zu vermeiden, sollte man in ClickHouse so große Blöcke wie möglich und nicht häufiger als einmal alle 2 Sekunden schreiben. Allerdings erfordert das Schreiben in großen Mengen, dass wir seltener in ClickHouse schreiben. Das könnte wiederum zu einem Überlauf des Puffers und dem Verlust von Logs führen. Die Lösung — erhöhen Sie den Puffer von Fluentd, aber dann steigt auch der Speicherbedarf.

Hinweis: Eine andere Problematik unserer Lösung mit ClickHouse hing damit zusammen, dass die Partitionierung in unserem Fall (loghouse) durch externe Tabellen umgesetzt ist, die verbunden sind Merge-Tabelle. Dies führt dazu, dass bei der Abfrage großer Zeitintervalle übermäßig viel Arbeitsspeicher benötigt wird, da die Metatabelle alle Partitionen durchläuft – auch die, die offensichtlich keine benötigten Daten enthalten. Dieser Ansatz kann jedoch bereits jetzt als veraltet für die aktuellen Versionen von ClickHouse angesehen werden (c 18.16).

Letztendlich wird klar, dass nicht jedes Projekt über die Ressourcen verfügt, um Echtzeit-Logs in ClickHouse zu sammeln (genauer gesagt, ihre Verteilung wäre nicht sinnvoll). Außerdem muss ein Puffer, auf den wir noch zurückkommen werden. Der oben beschriebene Fall ist real. Zu diesem Zeitpunkt konnten wir keine zuverlässige und stabile Lösung anbieten, die den Kunden zufriedenstellte und das Sammeln von Logs mit minimaler Verzögerung ermöglichte...

Und Elasticsearch?

Es ist bekannt, dass Elasticsearch mit hohen Lasten umgehen kann. Lassen Sie es uns im selben Projekt ausprobieren. Jetzt sieht die Last folgendermaßen aus:

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität

Elasticsearch konnte den Datenstrom verarbeiten, jedoch belastet das Schreiben solcher Volumina die CPU stark. Dies lässt sich durch die Organisation eines Clusters lösen. Technisch gesehen ist das kein Problem, jedoch ergibt sich, dass wir allein für den Betrieb des Logging-Systems bereits etwa 8 Kerne nutzen und eine zusätzliche hochbelastete Komponente im System haben...

Fazit: Diese Variante kann gerechtfertigt sein, aber nur, wenn das Projekt groß ist und die Führung bereit ist, erhebliche Ressourcen in ein zentrales Logging-System zu investieren.

Dann stellt sich die berechtigte Frage:

Welche Logs sind tatsächlich notwendig?

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität Versuchen wir, den Ansatz zu ändern: Logs sollten gleichzeitig informativ sein und nicht jedes Ereignis im System abdecken.

Angenommen, wir haben einen florierenden Online-Shop. Welche Logs sind wichtig? Maximale Informationen zu sammeln, beispielsweise von einem Zahlungsanbieter, ist eine hervorragende Idee. Aber von einem Dienst zur Bildbearbeitung im Produktkatalog sind uns nicht alle Logs wichtig: es genügen nur Fehlerprotokolle und eine erweiterte Überwachung (zum Beispiel der Prozentsatz der 500er-Fehler, die dieser Bestandteil generiert).

So sind wir zu der Erkenntnis gelangt, dass zentrales Logging nicht immer gerechtfertigt ist. Sehr oft möchte der Kunde alle Logs an einem Ort sammeln, obwohl von allen Logs in Wirklichkeit nur etwa 5 % der Nachrichten erforderlich sind, die für das Geschäft kritisch sind:

  • Manchmal reicht es aus, nur die Größe des Container-Logs und den Fehler-Tracker (z. B. Sentry) einzustellen.
  • Für die Untersuchung von Vorfällen kann oft eine Fehlerbenachrichtigung und ein großes lokales Log ausreichend sein.
  • Wir hatten Projekte, die sich ausschließlich mit funktionalen Tests und Error-Tracking-Systemen begnügten. Der Entwickler benötigte keine Logs an sich – er konnte alles über die Fehlermeldungen sehen.

Eine Anekdote aus der Praxis

Ein gutes Beispiel könnte eine andere Geschichte sein. Wir erhielten eine Anfrage vom Sicherheitsteam eines unserer Kunden, das bereits eine kommerzielle Lösung verwendete, die lange vor der Einführung von Kubernetes entwickelt wurde.

Es war erforderlich, das System zur zentralen Protokollsammlung mit dem Unternehmenssensor zur Problemerkennung – QRadar – zu „vernetzen“. Dieses System kann Logs über das Syslog-Protokoll empfangen und über FTP abrufen. Allerdings gelang es nicht sofort, es mit dem Plugin remote_syslog für fluentd zu integrieren. (wie sich herausstellte, waren wir nicht die Einzigen). Die Probleme bei der Konfiguration von QRadar lagen beim Sicherheitsteam des Kunden.

Infolgedessen wurden kritische Logs für das Geschäft über FTP an QRadar ausgelagert, während ein anderer Teil direkt von den Knoten über remote syslog umgeleitet wurde. Dafür haben wir sogar ein einfaches Chart geschrieben – vielleicht hilft es jemandem, eine ähnliche Aufgabe zu lösen… Dank des entstandenen Schemas konnte der Kunde kritische Logs erhalten und analysieren (mit seinem bevorzugten Tool), während wir die Kosten für das Protokollierungssystem senken konnten, indem wir nur den letzten Monat speicherten.

Ein weiteres Beispiel ist ziemlich anschaulich, wie man es nicht machen sollte. Einer unserer Kunden erstellte für die Verarbeitung jede von Ereignissen, die von Nutzern kommen, eine mehrzeilige unstrukturierte Ausgabe von Informationen in das Log. Wie leicht zu erraten ist, war es extrem unpraktisch, solche Logs zu lesen und zu speichern.

Kriterien für Logs

Solche Beispiele führen zu der Erkenntnis, dass neben der Auswahl eines Protokollierungssystems auch die Logs selbst entworfen werden müssen! Welche Anforderungen gibt es hier?

  • Logs sollten in einem maschinenlesbaren Format (z. B. JSON) vorliegen.
  • Logs sollten kompakt sein und die Möglichkeit bieten, den Logging-Level zu ändern, um mögliche Probleme zu debuggen. In Produktionsumgebungen sollten Systeme mit einem mäßigen Logging-Level betrieben werden. Warnung oder Fehler.
  • Logs müssen normalisiert sein, das heißt, dass alle Felder im Log-Objekt denselben Typ haben sollten.

Unstrukturierte Logs können zu Problemen beim Hochladen von Logs in das Repository und zu einem vollständigen Stopp ihrer Verarbeitung führen. Ein Beispiel dafür ist der Fehler 400, mit dem viele in den Logs von fluentd konfrontiert wurden:

2019-10-29 13:10:43 +0000 [warn]: Dump eines Fehlerereignisses: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Von Elasticsearch abgelehnt"

Der Fehler bedeutet, dass Sie ein Feld an einen Index mit einem vorgefertigten Mapping senden, dessen Typ instabil ist. Ein einfaches Beispiel ist ein Feld im nginx-Log mit der Variable $upstream_status. Darin kann sowohl eine Zahl als auch ein String vorkommen. Zum Beispiel:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29\/Okt\/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, wie 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\/Okt\/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, wie 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"}

In den Logs ist zu sehen, dass der Server 10.100.0.10 mit einem 404-Fehler geantwortet hat und die Anfrage an ein anderes Content-Repository weitergeleitet wurde. Infolgedessen hat sich der Wert in den Logs wie folgt verändert:

"upstream_response_time": "0.001, 0.007"

Diese Situation ist so verbreitet, dass sie sogar einer gesonderten Erwähnung in der Dokumentation.

Wie steht es um die Zuverlässigkeit?

Es gibt Fälle, in denen es absolut notwendig ist, alle Logs ohne Ausnahme zu haben. Und damit haben die typischen Schemen zum Sammeln von Logs für K8s, die oben vorgeschlagen oder diskutiert wurden, Probleme.

Zum Beispiel kann fluentd keine Logs von kurzlebigen Containern sammeln. In einem unserer Projekte lebte ein Container mit einer Datenbankmigration weniger als 4 Sekunden und wurde dann gemäß der entsprechenden Annotation gelöscht:

"helm.sh/hook-delete-policy": hook-succeeded

Aufgrund dessen gelangte das Log der Migration nicht in das Repository. In diesem Fall kann die Richtlinie before-hook-creation.

Ein weiteres Beispiel ist die Logrotation von Docker. Angenommen, es gibt eine Anwendung, die aktiv in die Logs schreibt. Unter normalen Bedingungen schaffen wir es, alle Logs zu verarbeiten, aber sobald ein Problem auftritt – zum Beispiel wie oben beschrieben mit dem falschen Format – stoppt die Verarbeitung und Docker rotiert die Datei. Das Ergebnis ist, dass geschäftskritische Logs verloren gehen können.

Genau deshalb Es ist wichtig, Logströme zu unterscheiden, indem die Übertragung der wertvollsten Daten direkt in die Anwendung eingebaut wird, um ihre Integrität zu gewährleisten. Außerdem wäre es hilfreich, eine Art „Akku“ für Logszu erstellen, die eine kurze Nichtverfügbarkeit des Repositories überstehen kann, während kritische Nachrichten gespeichert werden.

Schließlich sollte nicht vergessen werden, dass jede Teilsystemqualität überwacht werden muss. Andernfalls kann man leicht auf eine Situation stoßen, in der fluentd sich in einem Zustand befindet CrashLoopBackOff und sendet nichts, was den Verlust wichtiger Informationen droht.

Das DBMS Tarantool ist ein attraktives, zukunftsträchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.

In diesem Artikel betrachten wir keine SaaS-Lösungen wie Datadog. Viele der hier beschriebenen Probleme wurden bereits von kommerziellen Unternehmen, die sich auf das Sammeln von Logs spezialisiert haben, gelöst, aber nicht alle können aus verschiedenen Gründen SaaS nutzen. (die Hauptgründe sind Kosten und die Einhaltung von 152-FZ).

Die zentrale Sammlung von Logs scheint zunächst eine einfache Aufgabe zu sein, ist aber keineswegs so. Es ist wichtig zu beachten, dass:

  • Nur kritische Komponenten sollten detailliert protokolliert werden, während für andere Systeme die Überwachung und Fehlersammlung eingestellt werden kann.
  • Logs in der Produktion sollten minimal gehalten werden, um unnötige Belastung zu vermeiden.
  • Logs sollten maschinenlesbar, normalisiert und in einem strengen Format sein.
  • Besonders kritische Logs sollten separat und in einem eigenen Stream gesendet werden, der von den Hauptlogs getrennt ist.
  • Es ist ratsam, einen Log-Akku in Betracht zu ziehen, der vor plötzlichen Lastspitzen schützt und die Belastung der Speicherung gleichmäßiger gestaltet.

Logs in Kubernetes (und nicht nur) heute: Erwartungen und Realität
Diese einfachen Regeln, wenn sie überall angewendet würden, würden den oben beschriebenen Schemata erlauben, zu funktionieren – selbst wenn ihnen wichtige Komponenten (wie ein Akku) fehlen. Wenn jedoch solche Prinzipien nicht befolgt werden, wird die Aufgabe Sie und die Infrastruktur leicht zu einem weiteren hochbelasteten (und gleichzeitig wenig effektiven) Systemkomponenten führen.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

60GB SSD 8Gb DDR4