In dieser Geschichte geht es darum, wie wir Container in der Produktion verwenden, insbesondere unter Kubernetes. Der Artikel widmet sich dem Sammeln von Metriken und Protokollen aus Containern sowie dem Erstellen von Bildern.

Wir sind vom Fintech-Unternehmen Exness, das Dienstleistungen für den Online-Handel und Fintech-Produkte für B2B und B2C entwickelt. Wir haben viele verschiedene Teams in unserer Forschungs- und Entwicklungsabteilung und über 100 Mitarbeiter in der Entwicklungsabteilung.
Wir stellen das Team dar, das für die Plattform verantwortlich ist, auf der unsere Entwickler Code sammeln und ausführen können. Insbesondere sind wir für das Sammeln, Speichern und Bereitstellen von Metriken, Protokollen und Ereignissen aus Anwendungen verantwortlich. Wir betreiben derzeit rund 50 Docker-Container in der Produktion, unterstützen unseren XNUMX TB großen Big Data-Speicher und bieten Architekturlösungen, die auf unserer Infrastruktur aufbauen: Kubernetes, Rancher und verschiedene öffentliche Cloud-Anbieter.
Unsere Motivation
Was brennt? Niemand kann antworten. Wo ist der Herd? Es ist schwer zu verstehen. Wann hat es Feuer gefangen? Es ist möglich, das herauszufinden, aber nicht sofort.

Warum stehen manche Container, während andere umgefallen sind? Welcher Container war schuld? Denn von außen sind die Behälter identisch, innen steckt aber jeweils ein eigener Neo.

Unsere Entwickler sind schlaue Köpfe. Sie erbringen gute Dienstleistungen, die dem Unternehmen Gewinn bringen. Aber es kommt zu Problemen, wenn Container mit Anwendungen verrückt spielen. Ein Container verbraucht zu viel CPU, ein anderer das Netzwerk, der dritte E/A-Operationen und beim vierten ist völlig unklar, was er mit Sockets macht. Alles stürzt ein und das Schiff sinkt.
Agenten
Um zu verstehen, was im Inneren vor sich ging, beschlossen wir, die Agenten direkt in die Behälter zu platzieren.

Bei diesen Agenten handelt es sich um Eindämmungsprogramme, die Container in einem Zustand halten, in dem sie sich nicht gegenseitig beschädigen. Die Agenten sind standardisiert, was einen standardisierten Ansatz für die Containerwartung ermöglicht.
In unserem Fall müssen Agenten Protokolle in einem Standardformat bereitstellen, getaggt und gedrosselt. Sie sollten uns außerdem standardisierte Messgrößen liefern, die aus der Perspektive einer Geschäftsanwendung erweiterbar sind.
Под агентами также подразумеваются утилиты для эксплуатации и обслуживания, умеющие работать в разных системах оркестрирования, поддерживающие разные images (Debian, Alpin, Centos usw.).
Schließlich sollten Agenten einfaches CI/CD unterstützen, einschließlich Dockerfiles. Andernfalls gerät das Schiff ins Wanken, weil die Container auf schiefen Schienen angeliefert werden.
Der Montageprozess und das Gerät des Zielbildes
Um alles standardisiert und handhabbar zu halten, ist es notwendig, sich an eine Art Standardmontageprozess zu halten. Aus diesem Grund haben wir uns entschieden, Container für Container zu sammeln – das ist Rekursion.

Hier werden die Behälter durch durchgezogene Umrisse dargestellt. Gleichzeitig haben wir beschlossen, Ausschüttungen vorzunehmen, damit „das Leben nicht wie ein Zuckerschlecken aussieht“. Warum dies so war, erklären wir im Folgenden.
Das Ergebnis ist ein Build-Tool – ein Container einer bestimmten Version, der auf bestimmte Versionen von Distributionen und bestimmte Versionen von Skripten verweist.
Wie wenden wir es an? Wir haben einen Docker Hub, in dem sich der Container befindet. Wir spiegeln es innerhalb unseres Systems, um externe Abhängigkeiten zu vermeiden. Das Ergebnis ist ein gelb markierter Container. Wir erstellen eine Vorlage, um alle benötigten Distributionen und Skripte im Container zu installieren. Danach erstellen wir ein gebrauchsfertiges Image: Entwickler fügen Code und einige ihrer speziellen Abhängigkeiten darin ein.
Was ist das Gute an diesem Ansatz?
- Erstens die vollständige Versionskontrolle der Build-Tools – Build-Container, Skriptversionen und Distributionen.
- Zweitens haben wir eine Standardisierung erreicht: Wir erstellen Vorlagen, Zwischenbilder und gebrauchsfertige Bilder auf die gleiche Weise.
- Drittens ermöglichen uns Container Portabilität. Heute verwenden wir Gitlab und morgen werden wir zu TeamCity oder Jenkins wechseln und unsere Container auf die gleiche Weise ausführen können.
- Viertens: Minimieren Sie Abhängigkeiten. Dass wir die Distributionen in den Container packen, ist kein Zufall, denn so müssen wir sie nicht jedes Mal aus dem Internet herunterladen.
- Fünftens hat sich die Montagegeschwindigkeit erhöht – das Vorhandensein lokaler Bildkopien ermöglicht es Ihnen, keine Zeit mit dem Herunterladen zu verschwenden, da ein lokales Bild vorhanden ist.
Mit anderen Worten: Wir haben einen kontrollierten und flexiblen Montageprozess erreicht. Wir verwenden dieselben Tools, um jeden Container mit vollständiger Versionierung zu erstellen.
So funktioniert unser Montageprozess

Der Build wird mit einem Befehl gestartet, der Vorgang wird im Image (rot hervorgehoben) ausgeführt. Der Entwickler hat eine Docker-Datei (gelb hervorgehoben), wir rendern sie und ersetzen Variablen durch Werte. Und nebenbei fügen wir Kopf- und Fußzeilen hinzu – das sind unsere Agenten.
Der Header fügt Verteilungen aus den entsprechenden Bildern hinzu. Und die Fußzeile installiert unsere Dienste im Inneren, konfiguriert den Start der Arbeitslast, die Protokollierung und andere Agenten, ersetzt den Einstiegspunkt usw.

Wir haben lange überlegt, ob wir einen Betreuer ernennen sollen. Am Ende entschieden wir, dass wir ihn brauchten. Wir haben uns für S6 entschieden. Der Supervisor ermöglicht die Containerverwaltung: Er ermöglicht die Verbindung mit dem Container, wenn der Hauptprozess abstürzt, und ermöglicht die manuelle Steuerung des Containers, ohne ihn neu zu erstellen. Protokolle und Metriken sind Prozesse, die innerhalb des Containers ausgeführt werden. Sie müssen auch irgendwie kontrolliert werden, und das tun wir mit Hilfe eines Vorgesetzten. Schließlich kümmert sich S6 um die Verwaltung, Signalverarbeitung und andere Aufgaben.
Da wir unterschiedliche Orchestrierungssysteme im Einsatz haben, muss der Container nach seiner Erstellung und Einführung verstehen, in welcher Umgebung er sich befindet, und entsprechend reagieren. Zum Beispiel:
Dadurch können wir ein Image erstellen und es in verschiedenen Orchestrierungssystemen ausführen. Beim Start werden die Besonderheiten dieses Orchestrierungssystems berücksichtigt.

Für denselben Container erhalten wir unterschiedliche Prozessbäume in Docker und Kubernetes:

Die Nutzlast wird unter dem S6-Supervisor ausgeführt. Achten Sie auf Collector und Events – das sind unsere Agenten, die für Protokolle und Metriken verantwortlich sind. Kubernetes hat sie nicht, Docker jedoch schon. Warum?
Wenn wir uns die „Pod“-Spezifikation (im Folgenden „Kubernetes-Pod“) ansehen, sehen wir, dass der Ereigniscontainer in einem Pod ausgeführt wird, der über einen separaten Collector-Container verfügt, der die Funktion des Sammelns von Metriken und Protokollen übernimmt. Wir können die Funktionen von Kubernetes nutzen: Container in einem einzigen Pod, in einem einzigen Prozess und/oder Netzwerkbereich ausführen. Stellen Sie Ihre Agenten vor und führen Sie einige Funktionen aus. Und wenn derselbe Container in Docker gestartet wird, erhält er bei der Ausgabe dieselben Funktionen, d. h. er kann Protokolle und Metriken bereitstellen, da die Agenten darin gestartet werden.
Metriken und Protokolle
Die Bereitstellung von Metriken und Protokollen ist eine komplexe Aufgabe. Mit der Lösung sind mehrere Aspekte verbunden.
Die Infrastruktur ist für die Nutzlastausführung und nicht für die Massenübermittlung von Protokollen ausgelegt. Dies bedeutet, dass dieser Prozess mit minimalen Anforderungen an die Containerressourcen durchgeführt werden sollte. Wir möchten unseren Entwicklern helfen: „Nehmen Sie einen Docker Hub-Container, führen Sie ihn aus und wir können Protokolle liefern.“
Der zweite Aspekt ist die Begrenzung des Protokollvolumens. Wenn in mehreren Containern ein Spitzenwert im Protokollvolumen auftritt (die Anwendung gibt in einer Schleife einen Stapeltrace aus), erhöht sich die Belastung der CPU, der Kommunikationskanäle und des Protokollverarbeitungssystems, was sich auf den Betrieb des Hosts als Ganzes und anderer Container auf dem Host auswirkt und manchmal zu einem Hostabsturz führt.
Der dritte Aspekt besteht darin, dass möglichst viele Methoden zur Metrikerfassung sofort unterstützt werden müssen. Vom Lesen von Dateien und Abfragen von Prometheus-Endpunkten bis hin zur Verwendung anwendungsspezifischer Protokolle.
Und der letzte Aspekt ist, dass es notwendig ist, den Ressourcenverbrauch zu minimieren.
Wir haben uns für eine Open-Source-Lösung in Go namens Telegraf entschieden. Dies ist ein universeller Connector, der mehr als 140 Arten von Eingangskanälen (Eingangs-Plugins) und 30 Arten von Ausgangs-Plugins unterstützt. Wir haben es verbessert und erklären Ihnen nun am Beispiel von Kubernetes, wie wir es einsetzen.

Nehmen wir an, ein Entwickler stellt eine Workload bereit und Kubernetes erhält eine Anfrage zum Erstellen eines Pods. An diesem Punkt wird für jeden Pod automatisch ein Container namens Collector erstellt (wir verwenden einen Mutations-Webhook). Der Sammler ist unser Agent. Beim Start konfiguriert sich dieser Container selbst für die Zusammenarbeit mit Prometheus und dem Protokollsammlungssystem.
- Dazu verwendet es die Pod-Annotationen und erstellt abhängig von deren Inhalt beispielsweise einen Prometheus-Endpunkt.
- Entscheidet basierend auf der Pod-Spezifikation und den containerspezifischen Einstellungen, wie Protokolle übermittelt werden.
Wir sammeln Protokolle über die Docker-API: Entwickler müssen sie nur in stdout oder stderr einfügen und Collector kümmert sich darum. Protokolle werden in Blöcken und mit einer gewissen Verzögerung gesammelt, um eine mögliche Überlastung des Hosts zu verhindern.
Metriken werden über Workload-Instanzen (Prozesse) in Containern hinweg gesammelt. Alles wird markiert: Namespace, Pod usw. und dann in das Prometheus-Format konvertiert – und steht zur Erfassung zur Verfügung (außer Protokolle). Wir senden auch Protokolle, Metriken und Ereignisse an Kafka und weiter:
- Protokolle sind in Graylog verfügbar (zur visuellen Analyse);
- Protokolle, Metriken und Ereignisse werden zur langfristigen Speicherung an Clickhouse gesendet.
In AWS funktioniert alles genau gleich, nur dass wir Graylog durch Kafka und Cloudwatch ersetzen. Wir senden die Protokolle dorthin und alles funktioniert sehr bequem: Es ist sofort klar, zu welchem Cluster und Container sie gehören. Dasselbe gilt für Google Stackdriver. Das heißt, unser Schema funktioniert sowohl vor Ort mit Kafka als auch in der Cloud.
Wenn wir kein Kubernetes mit Pods haben, ist das Schema etwas komplizierter, funktioniert aber nach den gleichen Prinzipien.

Innerhalb des Containers werden dieselben Prozesse ausgeführt und mithilfe von S6 orchestriert. Alle gleichen Prozesse werden im selben Container ausgeführt.
Infolgedessen
Wir haben eine Komplettlösung zum Erstellen und Starten von Images in der Produktion entwickelt, mit Optionen zum Sammeln und Übermitteln von Protokollen und Messdaten:
- Wir haben einen standardisierten Ansatz zum Erstellen von Bildern entwickelt und darauf basierend CI-Vorlagen entwickelt.
- Datenerfassungsagenten sind unsere Telegraf-Erweiterungen. Wir haben sie in der Produktion gut getestet;
- Wir verwenden Mutations-Webhooks, um Container mit Agenten in Pods einzufügen.
- Integriert in das Kubernetes/Rancher-Ökosystem;
- Wir können dieselben Container in verschiedenen Orchestrierungssystemen ausführen und das erwartete Ergebnis erhalten.
- Erstellte eine vollständig dynamische Containerverwaltungskonfiguration.
Mitverfasser: Ilja Prudnikow
Source: habr.com
