Neun Kubernetes-Leistungstipps

Neun Kubernetes-Leistungstipps

Hallo alle! Mein Name ist Oleg Sidorenkov, ich arbeite bei DomClick als Leiter des Infrastrukturteams. Wir nutzen den Cube seit mehr als drei Jahren zum Verkauf und haben in dieser Zeit viele verschiedene interessante Momente damit erlebt. Heute erzähle ich Ihnen, wie Sie mit dem richtigen Ansatz noch mehr Leistung aus Vanilla Kubernetes für Ihren Cluster herausholen können. Auf die Plätze, fertig, Los!

Sie alle wissen sehr gut, dass Kubernetes ein skalierbares Open-Source-System für die Container-Orchestrierung ist; Nun ja, oder 5 Binärdateien, die Wunder bewirken, indem sie den Lebenszyklus Ihrer Microservices in einer Serverumgebung verwalten. Darüber hinaus handelt es sich um ein recht flexibles Werkzeug, das wie ein Lego-Konstrukteur zusammengebaut werden kann, um eine maximale Anpassung an verschiedene Aufgaben zu ermöglichen.

Und alles scheint in Ordnung zu sein: Werfen Sie Server in den Cluster, wie Brennholz in einen Feuerraum, und kennen Sie keine Trauer. Aber wenn Sie sich für die Umwelt einsetzen, werden Sie denken: „Wie kann ich das Feuer im Ofen aufrechterhalten und den Wald bereuen?“ Mit anderen Worten: Wie lassen sich Wege finden, die Infrastruktur zu verbessern und Kosten zu senken?

1. Behalten Sie den Überblick über Team- und Anwendungsressourcen

Neun Kubernetes-Leistungstipps

Eine der banalsten, aber effektivsten Methoden ist die Einführung von Anforderungen/Limits. Trennen Sie Anwendungen nach Namespaces und Namespaces nach Entwicklungsteams. Legen Sie in der Anwendung vor der Bereitstellung Werte für den Verbrauch von Prozessorzeit, Arbeitsspeicher und kurzlebigem Speicher fest.

resources:
   requests:
     memory: 2Gi
     cpu: 250m
   limits:
     memory: 4Gi
     cpu: 500m

Aus Erfahrung sind wir zu dem Schluss gekommen: Es lohnt sich nicht, die Anfragen aus Limits um mehr als das Doppelte zu erhöhen. Das Volumen des Clusters wird auf der Grundlage von Anforderungen berechnet. Wenn Sie den Unterschied zwischen den Ressourcen für Anwendungen beispielsweise auf das 5- bis 10-fache festlegen, stellen Sie sich vor, was mit Ihrem Knoten passieren wird, wenn er mit Pods gefüllt ist und plötzlich eine Last erhält . Nichts Gutes. Zumindest Drosselung und maximal Abschied vom Worker und eine zyklische Belastung der restlichen Knoten, nachdem sich die Pods zu bewegen beginnen.

Darüber hinaus mit der Hilfe limitranges Sie können zu Beginn Ressourcenwerte für den Container festlegen – Minimum, Maximum und Standard:

➜  ~ kubectl describe limitranges --namespace ops
Name:       limit-range
Namespace:  ops
Type        Resource           Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
----        --------           ---   ---   ---------------  -------------  -----------------------
Container   cpu                50m   10    100m             100m           2
Container   ephemeral-storage  12Mi  8Gi   128Mi            4Gi            -
Container   memory             64Mi  40Gi  128Mi            128Mi          2

Denken Sie daran, die Namespace-Ressourcen zu begrenzen, damit ein Befehl nicht alle Ressourcen des Clusters beanspruchen kann:

➜  ~ kubectl describe resourcequotas --namespace ops
Name:                   resource-quota
Namespace:              ops
Resource                Used          Hard
--------                ----          ----
limits.cpu              77250m        80
limits.memory           124814367488  150Gi
pods                    31            45
requests.cpu            53850m        80
requests.memory         75613234944   150Gi
services                26            50
services.loadbalancers  0             0
services.nodeports      0             0

Wie man der Beschreibung entnehmen kann resourcequotasWenn der ops-Befehl Pods bereitstellen möchte, die weitere 10 CPUs verbrauchen, lässt der Scheduler dies nicht zu und gibt einen Fehler aus:

Error creating: pods "nginx-proxy-9967d8d78-nh4fs" is forbidden: exceeded quota: resource-quota, requested: limits.cpu=5,requests.cpu=5, used: limits.cpu=77250m,requests.cpu=53850m, limited: limits.cpu=10,requests.cpu=10

Um ein ähnliches Problem zu lösen, können Sie beispielsweise ein Tool schreiben als diese, das den Status von Befehlsressourcen speichern und festschreiben kann.

2. Wählen Sie den besten Dateispeicher

Neun Kubernetes-Leistungstipps

Hier möchte ich auf das Thema persistente Volumes und das Disk-Subsystem von Kubernetes-Worker-Knoten eingehen. Ich hoffe, dass in der Produktion niemand den „Cube“ auf der HDD nutzt, aber manchmal reicht selbst eine normale SSD schon nicht aus. Wir standen vor einem solchen Problem, dass die Protokolle die Festplatte durch E/A-Vorgänge zerstörten, und es gibt hier nicht sehr viele Lösungen:

  • Verwenden Sie leistungsstarke SSDs oder wechseln Sie zu NVMe (wenn Sie Ihre eigene Hardware verwalten).

  • Verringern Sie die Protokollierungsstufe.

  • Führen Sie ein „intelligentes“ Ausbalancieren von Hülsen durch, die die Scheibe vergewaltigen (podAntiAffinity).

Der Screenshot oben zeigt, was unter nginx-ingress-controller mit einer Festplatte passiert, wenn die access_logs-Protokollierung aktiviert ist (~12 Protokolle/Sek.). Ein solcher Zustand kann natürlich zur Verschlechterung aller Anwendungen auf diesem Knoten führen.

Was PV betrifft, habe ich leider nicht alles ausprobiert. Arten Persistente Bände. Nutzen Sie die für Sie beste Option. In unserem Land kam es in der Vergangenheit vor, dass ein kleiner Teil der Dienste RWX-Volumes benötigte, und schon vor langer Zeit begann man, für diese Aufgabe NFS-Speicher zu verwenden. Günstig und ... ausreichend. Natürlich haben wir mit ihm Scheiße gegessen – seien Sie gesund, aber wir haben gelernt, ihn zu stimmen, und sein Kopf tut nicht mehr weh. Und wenn möglich, wechseln Sie zum S3-Objektspeicher.

3. Erstellen Sie optimierte Bilder

Neun Kubernetes-Leistungstipps

Am besten verwenden Sie Container-optimierte Images, damit Kubernetes diese schneller abrufen und effizienter ausführen kann. 

Optimierung bedeutet, dass Bilder:

  • nur eine Anwendung enthalten oder nur eine Funktion ausführen;

  • geringe Größe, da große Bilder schlechter über das Netzwerk übertragen werden;

  • über Integritäts- und Bereitschaftsendpunkte verfügen, mit denen Kubernetes im Falle eines Ausfalls Maßnahmen ergreifen kann;

  • Verwenden Sie Container-freundliche Betriebssysteme (wie Alpine oder CoreOS), die resistenter gegen Konfigurationsfehler sind.

  • Verwenden Sie mehrstufige Builds, sodass Sie nur kompilierte Anwendungen und nicht die zugehörigen Quellen bereitstellen können.

Es gibt viele Tools und Dienste, mit denen Sie Bilder im Handumdrehen überprüfen und optimieren können. Es ist wichtig, sie immer aktuell und sicher zu halten. Als Ergebnis erhalten Sie:

  1. Reduzierte Netzwerklast im gesamten Cluster.

  2. Verkürzte Container-Startzeit.

  3. Kleinere Größe Ihrer gesamten Docker-Registrierung.

4. Verwenden Sie einen DNS-Cache

Neun Kubernetes-Leistungstipps

Wenn wir über hohe Lasten sprechen, ist das Leben ohne Optimierung des DNS-Systems des Clusters ziemlich mies. Es war einmal, als die Kubernetes-Entwickler ihre kube-dns-Lösung unterstützten. Es wurde auch in unserem Land implementiert, aber diese Software passte nicht besonders gut und lieferte nicht die erforderliche Leistung, obwohl die Aufgabe scheinbar einfach ist. Dann tauchten Coredns auf, zu denen wir wechselten und die Trauer nicht kannte, später wurde es der Standard-DNS-Dienst in K8s. Irgendwann sind wir mit dem DNS-System auf 40 RPS angewachsen, und auch diese Lösung reichte nicht aus. Aber durch einen glücklichen Zufall kam Nodelocaldns heraus, auch bekannt als Node Local Cache NodeLocal DNSCache.

Warum verwenden wir es? Es gibt einen Fehler im Linux-Kernel, der bei mehreren Zugriffen über Conntrack-NAT über UDP zu einer Race-Bedingung beim Schreiben in die Conntrack-Tabellen führt und ein Teil des Datenverkehrs über NAT verloren geht (jede Fahrt durch den Dienst ist NAT). Nodelocaldns löst dieses Problem, indem es NAT beseitigt und die TCP-Konnektivität auf Upstream-DNS aktualisiert sowie Upstream-DNS-Abfragen lokal zwischenspeichert (einschließlich eines kurzen negativen 5-Sekunden-Cache).

5. Skalieren Sie Pods automatisch horizontal und vertikal

Neun Kubernetes-Leistungstipps

Können Sie mit Sicherheit sagen, dass alle Ihre Microservices für eine zwei- bis dreifache Erhöhung der Auslastung bereit sind? Wie können Sie Ihren Anwendungen Ressourcen richtig zuweisen? Es kann überflüssig sein, ein paar Pods laufen zu lassen, die über die Arbeitslast hinausgehen, und wenn sie hintereinander betrieben werden, besteht die Gefahr von Ausfallzeiten aufgrund eines plötzlichen Anstiegs des Datenverkehrs zum Dienst. Die goldene Mitte hilft, den Multiplikationszauber solcher Dienste zu erreichen Horizontaler Pod-Autoscaler и Vertikaler Pod-Autoscaler.

VPA ermöglicht es Ihnen, die Anforderungen/Limits Ihrer Container in einem Pod basierend auf der tatsächlichen Nutzung automatisch zu erhöhen. Wie kann es nützlich sein? Wenn Sie Pods haben, die aus irgendeinem Grund nicht horizontal skaliert werden können (was nicht ganz zuverlässig ist), können Sie versuchen, VPA bei der Änderung seiner Ressourcen zu vertrauen. Seine Funktion ist ein Empfehlungssystem, das auf historischen und aktuellen Daten vom Metric-Server basiert. Wenn Sie also Anfragen/Limits nicht automatisch ändern möchten, können Sie einfach die empfohlenen Ressourcen für Ihre Container überwachen und die Einstellungen optimieren, um CPU und Speicher zu sparen im Cluster.

Neun Kubernetes-LeistungstippsBild entnommen aus https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Der Scheduler in Kubernetes basiert immer auf Anfragen. Unabhängig davon, welchen Wert Sie dort eingeben, sucht der Planer auf dieser Grundlage nach einem geeigneten Knoten. Der Grenzwertwert wird vom Kublet benötigt, um zu wissen, wann ein Pod gedrosselt oder getötet werden muss. Und da der einzige wichtige Parameter der Anforderungswert ist, wird VPA damit arbeiten. Wenn Sie Ihre Anwendung vertikal skalieren, definieren Sie, welche Anforderungen gestellt werden sollen. Und was passiert dann mit den Grenzwerten? Auch dieser Parameter wird proportional skaliert.

Hier sind zum Beispiel die typischen Pod-Einstellungen:

resources:
   requests:
     memory: 250Mi
     cpu: 200m
   limits:
     memory: 500Mi
     cpu: 350m

Die Empfehlungs-Engine ermittelt, dass Ihre Anwendung 300 Mio. CPU und 500 Mio. benötigt, um ordnungsgemäß ausgeführt zu werden. Sie erhalten diese Einstellungen:

resources:
   requests:
     memory: 500Mi
     cpu: 300m
   limits:
     memory: 1000Mi
     cpu: 525m

Wie oben erwähnt, handelt es sich dabei um eine proportionale Skalierung basierend auf dem Verhältnis von Anfragen/Limits im Manifest:

  • CPU: 200m → 300m: Verhältnis 1:1.75;

  • Speicher: 250Mi → 500Mi: Verhältnis 1:2.

Hinsichtlich HPA, dann ist der Funktionsmechanismus transparenter. Für Metriken wie Prozessor und Arbeitsspeicher werden Schwellenwerte festgelegt. Wenn der Durchschnitt aller Replikate den Schwellenwert überschreitet, skaliert die Anwendung um +1 Pod, bis der Wert unter den Schwellenwert fällt oder bis die maximale Anzahl von Replikaten erreicht ist.

Neun Kubernetes-LeistungstippsBild entnommen aus https://levelup.gitconnected.com/kubernetes-autoscaling-101-cluster-autoscaler-horizontal-pod-autoscaler-and-vertical-pod-2a441d9ad231

Zusätzlich zu den üblichen Metriken wie CPU und Speicher können Sie Schwellenwerte für Ihre benutzerdefinierten Prometheus-Metriken festlegen und damit arbeiten, wenn Sie der Meinung sind, dass dies die genaueste Methode ist, um zu bestimmen, wann Ihre Anwendung skaliert werden soll. Sobald sich die Anwendung unter dem angegebenen Metrikschwellenwert stabilisiert, beginnt HPA mit der Skalierung der Pods auf die minimale Anzahl von Replikaten oder bis die Last den Schwellenwert erreicht.

6. Vergessen Sie nicht die Knotenaffinität und Pod-Affinität

Neun Kubernetes-Leistungstipps

Nicht alle Knoten laufen auf derselben Hardware und nicht alle Pods müssen rechenintensive Anwendungen ausführen. Mit Kubernetes können Sie die Spezialisierung von Knoten und Pods festlegen Knotenaffinität и Pod-Affinität.

Wenn Sie über Knoten verfügen, die für rechenintensive Vorgänge geeignet sind, ist es für maximale Effizienz besser, Anwendungen an die entsprechenden Knoten zu binden. Verwenden Sie dazu nodeSelector mit Knotenbeschriftung.

Nehmen wir an, Sie haben zwei Knoten: einen mit CPUType=HIGHFREQ und eine große Anzahl schneller Kerne, ein weiterer mit MemoryType=HIGHMEMORY mehr Speicher und schnellere Leistung. Der einfachste Weg besteht darin, einem Knoten eine Pod-Bereitstellung zuzuweisen HIGHFREQdurch Hinzufügen zum Abschnitt spec ein Selektor wie dieser:

…
nodeSelector:
	CPUType: HIGHFREQ

Eine kostspieligere und spezifischere Möglichkeit hierfür ist die Verwendung nodeAffinity im Bereich affinity Abschnitt spec. Es gibt zwei Möglichkeiten:

  • requiredDuringSchedulingIgnoredDuringExecution: harte Einstellung (Scheduler stellt Pods nur auf bestimmten Knoten bereit (und nirgendwo sonst));

  • preferredDuringSchedulingIgnoredDuringExecution: weiche Einstellung (der Scheduler versucht, die Bereitstellung auf bestimmten Knoten durchzuführen, und wenn dies fehlschlägt, versucht er, die Bereitstellung auf dem nächsten verfügbaren Knoten durchzuführen).

Sie können eine bestimmte Syntax für die Verwaltung von Knotenbezeichnungen angeben, z. B. In, NotIn, Exists, DoesNotExist, Gt oder Lt. Bedenken Sie jedoch, dass komplexe Methoden in langen Etikettenlisten die Entscheidungsfindung in kritischen Situationen verlangsamen. Mit anderen Worten: Machen Sie es nicht zu kompliziert.

Wie oben erwähnt, können Sie mit Kubernetes die Bindung aktueller Pods festlegen. Das heißt, Sie können dafür sorgen, dass bestimmte Pods mit anderen Pods in derselben Verfügbarkeitszone (relevant für Clouds) oder Knoten zusammenarbeiten.

В podAffinity Feld affinity Abschnitt spec Es stehen die gleichen Felder zur Verfügung wie im Fall von nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution и preferredDuringSchedulingIgnoredDuringExecution. Der einzige Unterschied besteht darin matchExpressions bindet Pods an einen Knoten, auf dem bereits ein Pod mit dieser Bezeichnung ausgeführt wird.

Mehr Kubernetes bietet ein Feld podAntiAffinity, was im Gegensatz dazu keinen Pod an einen Knoten mit bestimmten Pods bindet.

Über Ausdrücke nodeAffinity Derselbe Rat kann gegeben werden: Versuchen Sie, die Regeln einfach und logisch zu halten, und versuchen Sie nicht, die Pod-Spezifikation mit einem komplexen Regelsatz zu überladen. Es ist sehr einfach, eine Regel zu erstellen, die nicht den Bedingungen des Clusters entspricht, was den Planer zusätzlich belastet und die Gesamtleistung beeinträchtigt.

7. Makel und Toleranzen

Es gibt eine andere Möglichkeit, den Scheduler zu verwalten. Wenn Sie über einen großen Cluster mit Hunderten von Knoten und Tausenden von Mikrodiensten verfügen, ist es sehr schwierig zu verhindern, dass bestimmte Pods von bestimmten Knoten gehostet werden.

Dabei hilft der Mechanismus der Taints – Verbotsregeln. Sie können beispielsweise verhindern, dass bestimmte Knoten in bestimmten Szenarien Pods ausführen. Um Taint auf einen bestimmten Knoten anzuwenden, verwenden Sie die Option taint in kubectl. Geben Sie Schlüssel und Wert an und markieren Sie dann den Taint NoSchedule oder NoExecute:

$ kubectl taint nodes node10 node-role.kubernetes.io/ingress=true:NoSchedule

Es ist auch erwähnenswert, dass der Taint-Mechanismus drei Haupteffekte unterstützt: NoSchedule, NoExecute и PreferNoSchedule.

  • NoSchedule bedeutet, dass es einen entsprechenden Eintrag in der Pod-Spezifikation gibt tolerations, kann es nicht auf dem Knoten bereitgestellt werden (in diesem Beispiel node10).

  • PreferNoSchedule - vereinfachte Version NoSchedule. In diesem Fall versucht der Planer, keine Pods zuzuweisen, die keinen passenden Eintrag haben. tolerations pro Knoten, dies ist jedoch keine feste Grenze. Wenn im Cluster keine Ressourcen vorhanden sind, beginnen die Pods mit der Bereitstellung auf diesem Knoten.

  • NoExecute – Dieser Effekt löst eine sofortige Evakuierung von Pods aus, die keinen passenden Eintrag haben tolerations.

Seltsamerweise kann dieses Verhalten mithilfe des Toleranzmechanismus außer Kraft gesetzt werden. Dies ist praktisch, wenn es einen „verbotenen“ Knoten gibt und Sie darauf nur Infrastrukturdienste platzieren müssen. Wie kann man das machen? Lassen Sie nur Hülsen zu, für die eine entsprechende Toleranz besteht.

So würde die Pod-Spezifikation aussehen:

spec:
   tolerations:
     - key: "node-role.kubernetes.io/ingress"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"

Dies bedeutet nicht, dass der Pod beim nächsten erneuten Bereitstellen genau diesen Knoten treffen wird. Dies ist nicht der Knotenaffinitätsmechanismus und nodeSelector. Durch die Kombination mehrerer Funktionen können Sie jedoch ein sehr flexibles Scheduler-Setup erreichen.

8. Legen Sie die Pod-Bereitstellungspriorität fest

Nur weil Sie Pod-zu-Knoten-Bindungen konfiguriert haben, bedeutet das nicht, dass alle Pods mit der gleichen Priorität behandelt werden sollten. Beispielsweise möchten Sie möglicherweise einige Pods vor anderen bereitstellen.

Kubernetes bietet verschiedene Möglichkeiten, Pod-Priorität und -Vorrang festzulegen. Die Einstellung besteht aus mehreren Teilen: Objekt PriorityClass und Feldbeschreibungen priorityClassName in der Pod-Spezifikation. Betrachten Sie ein Beispiel:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 99999
globalDefault: false
description: "This priority class should be used for very important pods only"

Wir erstellen PriorityClassGeben Sie ihm einen Namen, eine Beschreibung und einen Wert. Höher value, desto höher die Priorität. Der Wert kann eine beliebige 32-Bit-Ganzzahl kleiner oder gleich 1 sein. Höhere Werte sind für geschäftskritische System-Pods reserviert, die normalerweise nicht vorbelegt werden können. Die Räumung erfolgt nur, wenn der Pod mit der höchsten Priorität nirgendwo umkehren kann. Dann werden einige Pods von einem bestimmten Knoten evakuiert. Wenn Ihnen dieser Mechanismus zu starr ist, können Sie die Option hinzufügen preemptionPolicy: Never, und dann gibt es keine Vorbelegung, der Pod steht an erster Stelle in der Warteschlange und wartet darauf, dass der Scheduler freie Ressourcen dafür findet.

Als nächstes erstellen wir einen Pod, in dem wir den Namen angeben priorityClassName:

apiVersion: v1
kind: Pod
metadata:
  name: static-web
  labels:
    role: myrole
 spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
  priorityClassName: high-priority
          

Sie können so viele Prioritätsklassen erstellen, wie Sie möchten. Es wird jedoch empfohlen, sich davon nicht mitreißen zu lassen (beschränken Sie sich beispielsweise auf niedrige, mittlere und hohe Priorität).

So können Sie bei Bedarf die Effizienz der Bereitstellung kritischer Dienste wie Nginx-Ingress-Controller, Coredns usw. steigern.

9. Optimieren Sie Ihren ETCD-Cluster

Neun Kubernetes-Leistungstipps

ETCD kann als das Gehirn des gesamten Clusters bezeichnet werden. Es ist sehr wichtig, den Betrieb dieser Datenbank auf einem hohen Niveau zu halten, da die Geschwindigkeit der Operationen im „Cube“ davon abhängt. Eine ziemlich standardmäßige und gleichzeitig gute Lösung wäre, einen ETCD-Cluster auf den Masterknoten zu behalten, um eine minimale Verzögerung für kube-apiserver zu haben. Wenn dies nicht möglich ist, platzieren Sie den ETCD so nah wie möglich mit guter Bandbreite zwischen den Teilnehmern. Achten Sie auch darauf, wie viele Knoten aus ETCD herausfallen können, ohne dass der Cluster Schaden nimmt.

Neun Kubernetes-Leistungstipps

Bedenken Sie, dass eine übermäßige Erhöhung der Teilnehmerzahl im Cluster die Fehlertoleranz auf Kosten der Leistung erhöhen kann. Alles sollte in Maßen erfolgen.

Wenn wir über die Einrichtung des Dienstes sprechen, gibt es einige Empfehlungen:

  1. Verfügen Sie über eine gute Hardware, basierend auf der Größe des Clusters (Sie können lesen). hier).

  2. Passen Sie ein paar Parameter an, wenn Sie einen Cluster auf zwei DCs verteilt haben oder Ihr Netzwerk und Ihre Festplatten zu wünschen übrig lassen (Sie können lesen). hier).

Abschluss

In diesem Artikel werden die Punkte beschrieben, die unser Team einzuhalten versucht. Dabei handelt es sich nicht um eine Schritt-für-Schritt-Beschreibung von Aktionen, sondern um Optionen, die zur Optimierung des Overheads eines Clusters nützlich sein können. Es ist klar, dass jeder Cluster auf seine Weise einzigartig ist und Optimierungslösungen stark variieren können. Daher wäre es interessant, Feedback von Ihnen zu erhalten: Wie überwachen Sie Ihren Kubernetes-Cluster, wie verbessern Sie seine Leistung? Teilen Sie Ihre Erfahrungen in den Kommentaren mit, es wird interessant sein, es zu erfahren.

Source: habr.com