Beste Praktiken für Kubernetes-Container: Betriebsprüfungen

Beste Praktiken für Kubernetes-Container: Betriebsprüfungen

TL;DR

  • Um eine hohe Beobachtbarkeit von Containern und Mikrodiensten, Protokollen und primären Metriken zu erreichen, reicht es nicht aus.
  • Für eine schnellere Wiederherstellung und höhere Ausfallsicherheit sollten Anwendungen das Prinzip der hohen Beobachtbarkeit (HOP, High Observability Principle) anwenden.
  • Auf der Anwendungsebene ist für HOP erforderlich: angemessenes Logging, sorgfältiges Monitoring, Verfügbarkeitsprüfungen und Leistungs- / Übergangstracing.
  • Verwenden Sie Prüfungen als Element von HOP. readinessProbe und livenessProbe Kubernetes.

Was ist eine Verfügbarkeitsprüfvorlage?

Bei der Gestaltung einer kritisch wichtigen und hochverfügbaren Anwendung ist es sehr wichtig, über Aspekte wie Ausfallsicherheit nachzudenken. Eine Anwendung gilt als ausfallsicher, wenn sie sich nach einem Ausfall schnell erholt. Eine typische Cloud-Anwendung verwendet eine Mikroservice-Architektur – wobei jede Komponente in einem eigenen Container platziert wird. Um sicherzustellen, dass die Anwendung auf k8s hochverfügbar ist, müssen beim Entwurf eines Clusters bestimmte Vorlagen befolgt werden. Dazu gehört die Vorlage für Verfügbarkeitsprüfungen. Sie definiert, wie die Anwendung k8s über ihre Verfügbarkeit informiert. Dies ist nicht nur die Information, ob ein Pod läuft, sondern auch, wie er Anfragen entgegennimmt und darauf antwortet. Je mehr Kubernetes über die Funktionsfähigkeit eines Pods weiß, desto intelligentere Entscheidungen trifft es über die Verkehrslenkung und -lastverteilung. So kann das Prinzip der hohen Beobachtbarkeit der Anwendung ermöglichen, rechtzeitig auf Anfragen zu reagieren.

Prinzip der hohen Beobachtbarkeit (HOP)

Das Prinzip der hohen Beobachtbarkeit ist eines der Prinzipien für das Design von containerisierten Anwendungen. In einer Mikrodienstarchitektur ist es den Diensten egal, wie ihre Anfragen verarbeitet werden (und das ist korrekt), aber wichtig ist, wie Antworten von den empfangenden Diensten erhalten werden. Zum Beispiel sendet ein Container zur Benutzerauthentifizierung eine HTTP-Anfrage an einen anderen und erwartet eine Antwort in einem bestimmten Format – das ist alles. Anfragen können in PythonJS bearbeitet werden, während die Antwort von Python Flask kommt. Container sind füreinander – wie schwarze Kästen mit verborgenem Inhalt. Das NOR-Prinzip verlangt jedoch, dass jeder Dienst mehrere API-Endpunkte offenlegt, die zeigen, wie funktionsfähig er ist, sowie seinen Zustand in Bezug auf Betriebsbereitschaft und Fehlertoleranz. Diese Parameter fordert Kubernetes an, um über die nächsten Schritte in der Routen- und Lastverteilung nachzudenken.

Ein gut gestaltetes Cloud-Anwendung protokolliert seine wichtigsten Ereignisse über die standardmäßigen Ein- und Ausgabeströme STDERR und STDOUT. Daraufhin arbeitet ein Hilfsdienst, zum Beispiel filebeat, logstash oder fluentd, der die Protokolle in ein zentrales Überwachungssystem (z. B. Prometheus) und ein Protokollsammelsystem (Software-Paket ELK) liefert. Das folgende Diagramm zeigt, wie die Cloud-Anwendung gemäß dem Verfügbarkeitsprüfungs-Template und dem Prinzip hoher Beobachtbarkeit arbeitet.

Beste Praktiken für Kubernetes-Container: Betriebsprüfungen

Wie wendet man das Verfügbarkeitsprüfungs-Template in Kubernetes an?

Out of the box überwacht k8s den Zustand von Pods mithilfe eines der Controller (Deployments, ReplicaSets, DaemonSets, StatefulSets usw.). Wenn festgestellt wird, dass ein Pod aus irgendeinem Grund ausgefallen ist, versucht der Controller, ihn neu zu starten oder auf einen anderen Knoten umzuschalten. Ein Pod kann jedoch melden, dass er läuft und funktioniert, während er tatsächlich nicht arbeitet. Ein Beispiel: Ihre Anwendung verwendet Apache als Webserver, und Sie haben die Komponente auf mehrere Pods des Clusters installiert. Da die Bibliothek falsch konfiguriert war, antworten alle Anfragen an die Anwendung mit dem Code 500 (interner Serverfehler). Bei der Bereitstellung zeigt die Überprüfung des Pod-Zustands ein erfolgreiches Ergebnis an, aber die Kunden sehen das anders. Diese unerwünschte Situation beschreiben wir wie folgt:

Beste Praktiken für Kubernetes-Container: Betriebsprüfungen

In unserem Beispiel führt k8s eine Verfügbarkeitsprüfung durch. Bei dieser Art der Überprüfung überwacht kubelet ständig den Zustand des Prozesses im Container. Versteht es, dass der Prozess blockiert ist, startet es ihn neu. Wenn ein Fehler einfach durch einen Neustart der Anwendung behoben werden kann und das Programm so konzipiert ist, dass es bei jedem Fehler abgeschaltet wird, dann genügt es zur Einhaltung der LIVENESS und des Prüfmusters, die Funktionsfähigkeit des Prozesses zu überprüfen. Schade nur, dass nicht alle Fehler durch einen Neustart behoben werden. In diesem Fall bietet k8s zwei tiefere Methoden zur Fehlersuche im Betrieb des Pods an: livenessProbe und readinessProbe.

LivenessProbe

Während livenessProbe führt kubelet drei Arten von Prüfungen durch: es stellt nicht nur fest, ob der Pod funktioniert, sondern auch, ob er bereit ist, Anfragen zu empfangen und angemessen zu reagieren:

  • Eine HTTP-Anfrage an den Pod einrichten. Die Antwort muss einen HTTP-Statuscode im Bereich von 200 bis 399 enthalten. Somit signalisieren die Codes 5xx und 4xx, dass der Pod Probleme hat, selbst wenn der Prozess läuft.
  • Für die Überprüfung von Pods mit nicht-HTTP-Diensten (z.B. dem Mailserver Postfix) muss eine TCP-Verbindung hergestellt werden.
  • Die Ausführung eines beliebigen Kommandos für den Pod (intern). Die Prüfung gilt als erfolgreich, wenn der Rückgabewert des Kommandos 0 ist.

Ein Beispiel dafür, wie es funktioniert. Die Definition des folgenden Pods enthält eine NodeJS-Anwendung, die bei HTTP-Anfragen einen Fehler 500 zurückgibt. Um sicherzustellen, dass der Container nach einem solchen Fehler neu gestartet wird, verwenden wir den Parameter livenessProbe:

apiVersion: v1
kind: Pod
metadata:
 name: node500
spec:
 containers:
   - image: magalix/node500
     name: node500
     ports:
       - containerPort: 3000
         protocol: TCP
     livenessProbe:
       httpGet:
         path: /
         port: 3000
       initialDelaySeconds: 5

Das unterscheidet sich nicht von jeder anderen Pod-Definition, aber wir fügen das Objekt .spec.containers.livenessProbe. Der Parameter httpGet nimmt den Pfad an, an den die HTTP GET-Anfrage gesendet wird (in unserem Beispiel ist das /, aber in Produktionsszenarien kann es auch etwas wie /api/v1/statussein). Außerdem akzeptiert livenessProbe den Parameter initialDelaySeconds, der der Prüfoperation vorschreibt, eine bestimmte Anzahl von Sekunden zu warten. Die Verzögerung ist notwendig, da der Container Zeit zum Starten benötigt, und er bei einem Neustart eine Zeit lang nicht verfügbar sein wird.

Um diese Konfiguration auf den Cluster anzuwenden, verwenden Sie:

kubectl apply -f pod.yaml

Nach ein paar Sekunden können Sie den Inhalt des Pods mit dem folgenden Befehl überprüfen:

kubectl describe pods node500

Am Ende der Ausgabe suchen Sie das hier.

Wie Sie sehen, hat livenessProbe eine HTTP GET-Anfrage initiiert, der Container hat einen Fehler 500 ausgegeben (was auch so programmiert war), kubelet hat ihn neu gestartet.

Wenn Sie sich fragen, wie die NideJS-Anwendung programmiert wurde, hier sind die verwendeten Dateien app.js und Dockerfile:

app.js

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(500, { "Content-type": "text/plain" });
    res.end("Wir sind auf einen Fehler gestoßenn");
});

server.listen(3000, function() {
    console.log('Server läuft auf 3000')
})

Dockerfile

FROM node
COPY app.js /
EXPOSE 3000
ENTRYPOINT [ "node","/app.js" ]

Es ist wichtig, folgendes zu beachten: livenessProbe wird den Container nur bei einem Ausfall neu starten. Wenn der Neustart den Fehler, der das Funktionieren des Containers behindert, nicht behebt, kann kubelet keine Maßnahmen zur Fehlerbehebung ergreifen.

readinessProbe

readinessProbe funktioniert ähnlich wie livenessProbes (GET-Anfragen, TCP-Verbindungen und die Ausführung von Befehlen), abgesehen von den Maßnahmen zur Fehlerbehebung. Ein Container, in dem ein Fehler aufgetreten ist, wird nicht neu gestartet, sondern vom eingehenden Datenverkehr isoliert. Stellen Sie sich vor, einer der Container führt viele Berechnungen durch oder ist stark belastet, was die Antwortzeit auf Anfragen erhöht. Bei livenessProbe erfolgt eine Verfügbarkeitsprüfung der Antwort (über den Parameter timeoutSeconds), wonach kubelet den Container neu startet. Nach dem Start beginnt der Container ressourcenintensive Aufgaben zu erledigen, und er wird erneut neu gestartet. Dies kann kritisch für Anwendungen sein, für die die Antwortgeschwindigkeit wichtig ist. Zum Beispiel wartet ein Fahrzeug unterwegs auf eine Antwort vom Server, die Antwort verzögert sich – und das Fahrzeug kommt zum Stillstand.

Lassen Sie uns eine Definition für readinessProbe schreiben, die die Antwortzeit auf GET-Anfragen auf maximal zwei Sekunden festlegt, während die Anwendung auf GET-Anfragen nach 5 Sekunden antwortet. Die Datei pod.yaml sollte folgendermassen aussehen:

apiVersion: v1
kind: Pod
metadata:
 name: nodedelayed
spec:
 containers:
   - image: afakharany/node_delayed
     name: nodedelayed
     ports:
       - containerPort: 3000
         protocol: TCP
     readinessProbe:
       httpGet:
         path: /
         port: 3000
       timeoutSeconds: 2

Lassen Sie uns das Pod mit kubectl bereitstellen:

kubectl apply -f pod.yaml

Wir warten ein paar Sekunden und schauen uns dann an, wie die readinessProbe reagiert hat:

kubectl describe pods nodedelayed

Am Ende der Ausgabe kann man sehen, dass ein Teil der Ereignisse ähnlich ist wie diesem.

Wie Sie sehen, hat kubectl das Pod nicht neu gestartet, als die Prüfzeit 2 Sekunden überschritt. Stattdessen wurde die Anfrage abgebrochen. Eingehende Verbindungen werden auf andere, funktionierende Pods umgeleitet.

Beachten Sie: Jetzt, da die zusätzliche Belastung von dem Pod entfernt wurde, leitet kubectl die Anfragen wieder an ihn weiter: Die Antworten auf GET-Anfragen werden nicht mehr verzögert.

Zum Vergleich: Hier ist die geänderte Datei app.js:

var http = require('http');

var server = http.createServer(function(req, res) {
   const sleep = (milliseconds) => {
       return new Promise(resolve => setTimeout(resolve, milliseconds))
   }
   sleep(5000).then(() => {
       res.writeHead(200, { "Content-type": "text/plain" });
       res.end("Hellon");
   })
});

server.listen(3000, function() {
   console.log('Server läuft auf Port 3000')
})

TL;DR
Vor der Einführung von Cloud-Anwendungen waren Logs das Hauptmittel zur Überwachung und Überprüfung des Anwendungsstatus. Es gab jedoch keine Mittel, um Maßnahmen zur Fehlerbehebung zu ergreifen. Logs sind auch heute noch nützlich; sie sollten gesammelt und an ein Logging-System zur Analyse der Störfälle und Entscheidungsfindung gesendet werden. [dies alles konnte auch ohne Cloud-Anwendungen mithilfe von Monit erledigt werden, zum Beispiel, aber mit k8s ist es viel einfacher geworden 🙂 – Anm. der Redaktion. ]

Heute müssen Änderungen quasi in Echtzeit vorgenommen werden, wodurch Anwendungen nicht mehr als Black Boxes agieren dürfen. Nein, sie sollten Endpunkte bereitstellen, die es Überwachungssystemen ermöglichen, wertvolle Daten über den Zustand der Prozesse abzufragen und zu sammeln, um im Bedarfsfall sofort reagieren zu können. Dies wird als Entwurfsmuster für die Überprüfbarkeit bezeichnet, das dem Prinzip der hohen Beobachtbarkeit (HOB) folgt.

Kubernetes bietet standardmäßig zwei Arten der Überprüfung: readinessProbe und livenessProbe. Beide nutzen die gleichen Prüfmethoden (HTTP-GET-Anfragen, TCP-Verbindungen und das Ausführen von Befehlen). Sie unterscheiden sich darin, welche Entscheidungen als Reaktion auf Störungen in den Pods getroffen werden. livenessProbe startet den Container neu in der Hoffnung, dass der Fehler nicht mehr auftritt, während readinessProbe den Pod vom eingehenden Verkehr isoliert – bis das Problem behoben ist.

Das richtige Anwendungsdesign sollte sowohl die eine als auch die andere Art der Überprüfung umfassen und sicherstellen, dass ausreichend Daten gesammelt werden, insbesondere wenn eine Ausnahme auftritt. Es sollte auch die erforderlichen API-Endpunkte anzeigen, die dem Überwachungssystem (zum Beispiel Prometheus) wichtige Metriken zum Zustand der Betriebsfähigkeit liefern.

Quelle: habr.com

60GB SSD 8Gb DDR4