Anmerkung des Übersetzers.: Die Autoren dieses Artikels sind Ingenieure eines kleinen tschechischen Unternehmens namens pipetail. Ihnen ist es gelungen, eine bemerkenswerte Liste von [manchmal banalen, aber dennoch] so aktuellen Problemen und Missverständnissen im Zusammenhang mit dem Betrieb von Kubernetes-Clustern zusammenzustellen.

Im Laufe der Jahre, in denen wir Kubernetes genutzt haben, hatten wir die Gelegenheit, mit einer großen Zahl von Clustern zu arbeiten (sowohl verwalteten als auch nicht verwalteten - auf GCP, AWS und Azure). Im Laufe der Zeit haben wir festgestellt, dass einige Fehler ständig wiederkehren. Aber das ist nichts, wofür man sich schämen müsste: Wir selbst haben die meisten davon gemacht!
In dem Artikel werden die häufigsten Fehler zusammengetragen und erläutert, wie man sie beheben kann.
1. Ressourcen: Anfragen und Limits
Dieser Punkt verdient auf jeden Fall die größte Aufmerksamkeit und den ersten Platz auf der Liste.
CPU-Anfragen sind normalerweise entweder gar nicht festgelegt oder haben einen sehr niedrigen Wert (um so viele Pods wie möglich auf jedem Knoten unterzubringen). Dadurch kommt es zu einer Überlastung der Knoten. Bei hoher Auslastung werden die CPU-Ressourcen des Knotens vollständig beansprucht und die spezifische Arbeitslast erhält nur das, was sie durch CPU-Throttlingangefordert hat. Dies führt zu erhöhten Latenzen in der Anwendung, Zeitüberschreitungen und anderen unangenehmen Folgen. (Mehr dazu finden Sie in einem anderen unserer letzten Übersetzungen: „“ — Anm. d. Ü.)
BestEffort (äußerst nicht empfohlen):
resources: {}Extrem niedrige CPU-Anfrage (äußerst nicht empfohlen):
resources:
Requests:
cpu: "1m"Andererseits kann ein CPU-Limit dazu führen, dass Pods unangemessen Taktzyklen überspringen, selbst wenn die CPU des Knotens nicht vollständig ausgelastet ist. Auch dies kann zu erhöhten Latenzen führen. Es gibt anhaltende Debatten über den Parameter CPU CFS-Quota im Linux-Kernel und das CPU-Throttling basierend auf den festgelegten Limits sowie das Abschalten der CFS-Quota... Leider können CPU-Limits mehr Probleme verursachen, als sie lösen können. Weitere Informationen hierzu finden Sie unter dem Link unten.
Übermäßige Zuweisung (Overcommitting) von Speicher kann zu umfassenderen Problemen führen. Das Erreichen der CPU-Grenze führt zum Überspringen von Taktzyklen, während das Erreichen der Speicherkapazität dazu führt, dass ein Pod „getötet“ wird. Haben Sie schon einmal erlebt, OOMkill? Да, речь идет именно о нем.
Möchten Sie die Wahrscheinlichkeit dieses Ereignisses minimieren? Verteilen Sie keine übermäßigen Speichermengen und verwenden Sie garantiert QoS (Quality of Service), indem Sie den Memory-Request auf das Limit setzen (siehe Beispiel unten). Weitere Informationen dazu finden Sie in (Leitender Ingenieur bei Zalando).
Burstable (höhere Wahrscheinlichkeit, OOMkilled zu werden):
resources:
requests:
memory: "128Mi"
cpu: "500m"
limits:
memory: "256Mi"
cpu: 2Guaranteed:
resources:
requests:
memory: "128Mi"
cpu: 2
limits:
memory: "128Mi"
cpu: 2Was könnte potenziell bei der Ressourcenzuweisung helfen?
Mit Hilfe von metrics-server kann den aktuellen CPU-Verbrauch und den Arbeitsspeicherverbrauch der Pods (und der Container innerhalb dieser) anzeigen. Wahrscheinlich nutzen Sie ihn bereits. Führen Sie einfach die folgenden Befehle aus:
kubectl top pods
kubectl top pods --containers
kubectl top nodesSie zeigen jedoch nur die aktuelle Nutzung an. Damit können Sie eine grobe Vorstellung von den Größenordnungen erhalten, aber letztlich benötigen Sie eine Historie der Metrikänderungen über die Zeit (um Fragen zu beantworten wie: 'Wie hoch war die Spitzenlast der CPU?', 'Wie war die Last gestern Morgen?' usw.). Dafür können Sie Prometheus, DataDog und andere Tools verwenden. Diese greifen einfach auf die Metriken des metrics-server zu und speichern sie, sodass der Benutzer diese abfragen und entsprechende Grafiken erstellen kann.
das Öffnen eines Links in einem neuen Tab oder in einem neuen Container. automatisieren diesen Prozess. Er verfolgt die Geschichte der CPU- und Speicherverwendung und passt neue Requests und Limits basierend auf diesen Informationen an.
Die effiziente Nutzung von Rechenressourcen ist eine anspruchsvolle Aufgabe. Es ist wie ein ständiges Spiel von Tetris. Wenn Sie zu viel für Rechenleistung bei niedriger durchschnittlicher Nutzung (sagen wir, ~10 %) bezahlen, empfehlen wir, Produkte basierend auf AWS Fargate oder Virtual Kubelet in Betracht zu ziehen. Diese basieren auf einem Billing-Modell ohne Server/Zahlung nach Nutzung, was in solchen Situationen günstiger sein kann.
2. Liveness- und Readiness-Probes
Standardmäßig sind die Liveness- und Readiness-Prüfungen in Kubernetes nicht aktiviert. Und manchmal vergessen die Leute, sie zu aktivieren…
Aber wie kann man den Dienst bei einem nicht behobenen Fehler sonst neu starten? Und wie erfährt der Lastenausgleich, dass ein Pod bereit ist, Traffic zu empfangen? Oder dass er in der Lage ist, mehr Traffic zu verarbeiten?
Diese Proben werden oft miteinander verwechselt:
- Lebensfähigkeit - die 'Liveness'-Überprüfung, die den Pod bei einem fehlerhaften Abschluss neu startet;
- Bereitstellung — Überprüfung der Bereitschaft, sie trennt den Pod im Falle eines Fehlers vom Kubernetes-Dienst (das kann man überprüfen mit
kubectl get endpoints) und der Traffic bleibt solange aus, bis die nächste Überprüfung erfolgreich abgeschlossen ist.
Beide Überprüfungen WERDEN ÜBER DEN GESAMTEN LEBENSZYKLUS DES PODS AUSGEFÜHRT. Das ist sehr wichtig.
Es ist ein weit verbreitetes Missverständnis, dass Readiness-Probes nur zu Beginn gestartet werden, damit der Load-Balancer erkennen kann, dass der Pod bereit ist (Bereit) und mit der Verarbeitung von Datenverkehr beginnen kann. Dies ist jedoch nur eine Anwendungsmöglichkeit.
Eine andere Möglichkeit besteht darin, zu erkennen, dass der Datenverkehr auf den Pod übermäßig hoch ist und ihn überlastet (oder der Pod ressourcenintensive Berechnungen durchführt). In diesem Fall hilft die Readiness-Prüfung die Last auf den Pod zu reduzieren und ihn zu 'kühlen'. Ein erfolgreicher Abschluss der Readiness-Prüfung in der Zukunft erlaubt es wieder die Last auf den Pod zu erhöhen. In diesem Falle (im Falle eines Fehlschlags der Readiness-Prüfung) wäre ein Fehlschlag der Liveness-Prüfung sehr kontraproduktiv. Warum einen Pod neu starten, der gesund ist und sich voll und ganz einsetzt?
Deshalb ist es in einigen Fällen besser, auf vollständige Überprüfungen zu verzichten, als sie mit falsch eingerichteten Parametern zu aktivieren. Wie bereits erwähnt, wenn die Liveness-Prüfung die Readiness-Prüfung kopiert, dann sind Sie in großen Schwierigkeiten. Eine mögliche Lösung ist, , und außen vor zu lassen.
Beide Prüftypen sollten nicht fehlschlagen, wenn die gemeinsamen Abhängigkeiten ausfallen, sonst führt das zu einem kaskadierenden (lawinenartigen) Ausfall aller Pods. Mit anderen Worten, .
3. LoadBalancer für jeden HTTP-Dienst
Wahrscheinlich haben Sie in Ihrem Cluster HTTP-Dienste, die Sie in die Außenwelt weiterleiten möchten.
Wenn Sie den Dienst als type: LoadBalanceröffnen, wird sein Controller (abhängig vom Dienstanbieter) einen externen LoadBalancer bereitstellen und aushandeln (der nicht unbedingt auf L7, eher wahrscheinlich auf L4 arbeitet), und das könnte sich auf die Kosten auswirken (externe statische IPv4-Adresse, Rechenleistung, Abrechnung pro Sekunde) aufgrund der Notwendigkeit, eine große Anzahl solcher Ressourcen zu erstellen.
In diesem Fall ist es viel logischer, einen einzigen externen Lastenausgleich zu nutzen, während Sie Dienste als type: NodePortöffnen. Oder noch besser, etwas wie nginx-ingress-controller (oder traefik), das als einziger auftreten wird. NodePort einem mit dem externen Lastenausgleich verbundenen Endpunkt und leitet den Datenverkehr im Cluster weiter mit Hilfe von ingress-Ressourcen von Kubernetes.
Andere intra-cluster (Micro-)Dienste, die miteinander interagieren, können über Dienste des Typs ClusterIP und den integrierten Dienstentdeckungsmechanismus über DNS „kommunizieren“. Verwenden Sie jedoch nicht deren öffentliche DNS/IP, da dies die Latenz beeinflussen und zu höheren Kosten für Cloud-Dienste führen kann.
4. Automatisches Skalieren des Clusters ohne Berücksichtigung seiner Besonderheiten
Bei der Hinzufügung und Entfernung von Knoten im Cluster sollte man sich nicht auf einige grundlegende Metriken wie die CPU-Nutzung dieser Knoten verlassen. Die Planung des Pods sollte viele Faktoren berücksichtigen, Einschränkungen, wie Pod-/Knotenaffinität, Taints und Toleranzen, Ressourcenanforderungen, QoS usw. Die Verwendung eines externen Autoscalers, der diese Nuancen nicht berücksichtig, kann zu Problemen führen.
Stellen Sie sich vor, dass ein bestimmter Pod geplant werden muss, aber alle verfügbaren CPU-Ressourcen angefragt/ausgelastet sind und der Pod in einem Zustand feststeckt Pending. Der externe Autoscaler sieht die aktuelle durchschnittliche CPU-Auslastung (nicht die angeforderte) und initiiert keine Skalierung (Scale-Out) — fügt keinen weiteren Knoten hinzu. Infolgedessen wird dieser Pod nicht geplant.
In diesem Zusammenhang ist das umgekehrte Skalieren (Scale-In) — das Entfernen eines Knotens aus dem Cluster — immer schwieriger zu realisieren. Stellen Sie sich vor, Sie haben einen stateful Pod (mit angeschlossenem persistenten Speicher). Persistent-Volumes gehören normalerweise zu einer bestimmten Verfügbarkeitszone und werden nicht im Region repliziert. Wenn der externe Autoscaler also einen Knoten mit diesem Pod entfernt, kann der Scheduler diesen Pod nicht auf einem anderen Knoten planen, da dies nur in der Verfügbarkeitszone möglich ist, in der sich der permanente Speicher befindet. Der Pod bleibt im Zustand hängen Pending.
In der Kubernetes-Community ist der sehr beliebt. Er arbeitet im Cluster, unterstützt die APIs der wichtigsten Cloud-Anbieter, berücksichtigt alle Einschränkungen und kann in den oben genannten Fällen skalieren. Er kann auch Scale-In durchführen, während alle festgelegten Einschränkungen beibehalten werden, wodurch Geld gespart wird (das sonst für ungenutzte Kapazitäten ausgegeben worden wäre).
5. Vernachlässigung der IAM/RBAC-Funktionen
Seien Sie vorsichtig, IAM-Nutzer mit dauerhaften Geheimnissen für Maschinen und Anwendungen zu verwenden.. Organisieren Sie den temporären Zugriff, indem Sie Rollen und Dienstkonten verwenden (Dienstkonten).
Wir stellen oft fest, dass Zugangsschlüssel (und Geheimnisse) in der App-Konfiguration 'hardcodiert' sind und Geheimnisse trotz Zugang zu Cloud IAM nicht gedreht werden. Verwenden Sie IAM-Rollen und Dienstkonten anstelle von Benutzern, wo es angebracht ist.

Vergessen Sie kube2iam und gehen Sie direkt zu IAM-Rollen für Dienstkonten (wie in der Štěpán Vraný):
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
name: my-serviceaccount
namespace: defaultEine Annotation. Gar nicht so schwer, oder?
Außerdem sollten Sie Dienstkonten und Instanzprofile nicht mit Rechten ausstatten, admin und cluster-admin, wenn sie diese nicht benötigen. Dies ist etwas komplizierter zu realisieren, insbesondere in RBAC K8s, aber es lohnt sich auf jeden Fall.
6. Verlassen Sie sich nicht auf automatische Anti-Affinität für Pods
Stellen Sie sich vor, Sie haben drei Replikate eines bestimmten Deployments auf einem Knoten. Der Knoten stürzt ab und mit ihm alle Replikate. Eine unerfreuliche Situation, nicht wahr? Aber warum waren alle Replikate auf einem Knoten? Sollte Kubernetes nicht hohe Verfügbarkeit (HA) gewährleisten?!
Leider hält der Kubernetes-Scheduler nicht eigenständig die Regeln für die getrennte Existenz (anti-affinity) von Pods ein. Diese müssen ausdrücklich festgelegt werden:
// опущено для краткости
labels:
app: zk
// опущено для краткости
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname" Das war's. Jetzt werden Pods auf verschiedenen Knoten geplant (dieses Kriterium wird nur während der Planung geprüft, nicht während des Betriebs — daher auch requiredDuringSchedulingIgnoredDuringExecution).
Hier sprechen wir über podAntiAffinity auf verschiedenen Knoten: topologyKey: "kubernetes.io/hostname", — und nicht in verschiedenen Verfügbarkeitszonen. Um eine vollständige HA zu erreichen, wird es notwendig sein, tiefer in dieses Thema einzutauchen.
7. Ignorieren von PodDisruptionBudgets
Stellen Sie sich vor, Sie haben eine Produktionslast in einem Kubernetes-Cluster. Gelegentlich müssen Knoten und der Cluster selbst aktualisiert (oder außer Betrieb genommen) werden. PodDisruptionBudget (PDB) ist eine Art Garantievertrag über Wartung zwischen Cloud-Administratoren und Nutzern.
PDB verhindert Unterbrechungen in den Diensten, die durch unzureichende Knoten verursacht werden:
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeperIn diesem Beispiel erklären Sie als Clusterbenutzer den Administratoren: „Hey, ich habe einen Zookeeper-Dienst, und egal was Sie tun, ich möchte, dass mindestens 2 Replikate dieses Dienstes immer verfügbar sind.“
Mehr dazu können Sie lesen .
8. Mehrere Benutzer oder Umgebungen in einem gemeinsamen Cluster
Kubernetes-Namespace (Namespaces) bieten keine starke Isolation.
Es ist ein verbreiteter Irrtum, dass, wenn nicht-produktive Workloads in einem Namespace und produktive Workloads in einem anderen bereitgestellt werden, diese keinen Einfluss aufeinander haben… Natürlich kann ein gewisses Maß an Isolation durch Anfragen/Ressourcengrenzen, das Setzen von Quoten und das Festlegen von priorityClassen erreicht werden. Eine gewisse „physische“ Isolation im Datenfluss wird durch Affinitäten, Tolerierungen, Taints (oder Node-Selectoren) gewährleistet, aber diese Art der Trennung ist recht schwierig umzusetzen.
Für diejenigen, die beide Arten von Workloads in einem Cluster kombinieren müssen, wird es kompliziert. Falls dies nicht notwendig ist und Sie es sich leisten können, eine weitere Cluster (zum Beispiel in der Public Cloud) zu betreiben, ist es besser, dies zu tun. Dadurch wird ein deutlich höheres Maß an Isolation erreicht.
9. externalTrafficPolicy: Cluster
Sehr oft beobachten wir, dass der gesamte Verkehr in den Cluster über einen NodePort-Dienst läuft, für den standardmäßig die Richtlinie externalTrafficPolicy: Clusterfestgelegt ist. Das bedeutet, dass NodePort er auf jedem Knoten im Cluster geöffnet ist und jeder von ihnen verwendet werden kann, um mit dem gewünschten Dienst (einem Satz von Pods) zu interagieren.

Die tatsächlichen Pods, die mit dem oben genannten NodePort-Dienst verbunden sind, sind normalerweise nur auf einer Teilmenge dieser Knoten. Mit anderen Worten, wenn ich mich mit einem Knoten verbinde, auf dem sich der benötigte Pod nicht befindet, wird der Verkehr an einen anderen Knoten umgeleitet, was einen Transitabschnitt (hop) hinzufügt und die Latenz erhöht (wenn die Knoten in verschiedenen Verfügbarkeitszonen/Rechenzentren sind, kann die Latenz recht hoch sein; außerdem steigen die Kosten für Egress-Verkehr).
Andererseits, wenn für einen bestimmten Kubernetes-Dienst die Richtlinie externalTrafficPolicy: Localfestgelegt ist, dann wird der NodePort nur auf den Knoten geöffnet, auf denen die benötigten Pods tatsächlich laufen. Bei Verwendung eines externen Lastenausgleichers, der die Zustände (Gesundheitsprüfungen) der Endpunkte überprüft (wie dies AWS ELBtut), wird er den Verkehr nur an die erforderlichen Knoten senden, was sich positiv auf Latenzen, Rechenanforderungen und Egress-Kosten auswirkt (das gesunde Menschenverstand diktiert dasselbe).
Die Wahrscheinlichkeit ist hoch, dass Sie bereits etwas wie traefik oder nginx-ingress-controller als End-NodePort-Punkt (oder LoadBalancer, der ebenfalls NodePort verwendet) zur Weiterleitung von HTTP-Ingress-Traffic verwenden, und die Aktivierung dieser Option kann die Latenz bei solchen Anfragen erheblich reduzieren.
Im Sie können mehr über die externalTrafficPolicy, deren Vor- und Nachteile erfahren.
10. Verlassen Sie sich nicht auf Cluster und missbrauchen Sie den Control Plane nicht.
Früher wurden Server mit Eigennamen bezeichnet: , HAL9000 und Colossus… Heute sind sie durch zufällig generierte Identifier ersetzt worden. Dennoch ist die Gewohnheit geblieben, und jetzt bekommen Cluster Eigennamen.
Eine typische Geschichte (basierend auf realen Ereignissen): alles begann mit einem Proof of Concept, weshalb der Cluster stolz den Namen Testentrug … Jahre vergingen, und er wird BIS HEUTE in der Produktion verwendet, und alle haben Angst, ihn anzufassen.
Es ist nichts Lustiges daran, dass Cluster zu Haustieren werden, weshalb wir empfehlen, sie regelmäßig zu löschen und dabei zu üben, Wiederherstellung nach Ausfällen (dabei hilft — Anm. d. Übersetzer)). Zudem ist es sinnvoll, sich auch mit der Steuerungsebene (Control Plane)zu beschäftigen. Angst, ihn anzufassen, ist kein gutes Zeichen. Etcd tot? Leute, ihr seid wirklich in Schwierigkeiten!
Auf der anderen Seite sollte man sich nicht zu sehr mit ihm beschäftigen. Im Laufe der Zeit kann die Steuerungsebene langsam werden.Wahrscheinlich liegt das an der großen Anzahl von Objekten, die ohne Rotation erstellt werden (eine allgemeine Situation bei der Verwendung von Helm mit Standardeinstellungen, wodurch sein Status in ConfigMaps/Secrets nicht aktualisiert wird – das Ergebnis ist, dass sich in der Steuerungsebene Tausende von Objekten ansammeln) oder am ständigen Bearbeiten von Kube-API-Objekten (für Auto-Scaling, CI/CD, Monitoring, Ereignisprotokolle, Controller usw.).
Darüber hinaus empfehlen wir, die SLA/SLO-Vereinbarungen mit Ihrem Anbieter von Managed Kubernetes zu überprüfen und auf die Garantien zu achten. Der Anbieter kann die Verfügbarkeit der Steuerungsebene (oder ihrer Teilkomponenten) garantieren, jedoch nicht die p99-Latenz der Anfragen, die Sie ihm senden. Mit anderen Worten, es kann eingeführt werden kubectl get nodes, und die Antwort erst nach 10 Minuten zu erhalten, wird nicht als Verstoß gegen die Bedingungen des Dienstleistungsvertrags gewertet.
11. Bonus: Verwendung des Tags latest
Das ist bereits klassisch. In letzter Zeit begegnen wir dieser Technik nicht mehr so oft, da viele, die bitteren Erfahrungen gemacht haben, aufgehört haben, das Tag :latest zu verwenden und angefangen haben, Versionen festzulegen (pin). Hurra!
ECR ; wir empfehlen, sich mit diesem bemerkenswerten Merkmal vertraut zu machen.
Zusammenfassung
Erwarten Sie nicht, dass alles mit einem Fingerschnippen funktioniert: Kubernetes ist kein Allheilmittel. Eine schlecht gestaltete Anwendung (und vielleicht sogar noch schlimmer werden). Unvorsichtigkeit führt zu übermäßiger Komplexität und langsamer, mühsamer Arbeit der Steuerungsebene. Außerdem riskieren Sie, ohne eine Notfallwiederherstellungsstrategie dazustehen. Zählen Sie nicht darauf, dass Kubernetes „out of the box“ für Isolation und hohe Verfügbarkeit sorgt. Nehmen Sie sich etwas Zeit, um Ihre Anwendung wirklich cloud-native zu machen.
Sie können aus den Misserfolgserfahrungen verschiedener Teams lernen in von Henning Jacobs.
Diejenigen, die die in diesem Artikel aufgeführten Fehler ergänzen möchten, können uns über Twitter kontaktieren (, ).
P.S. vom Übersetzer
Lesen Sie auch in unserem Blog:
- «»;
- «»;
- «» (Übersicht und Video der Präsentation);
- «».
Quelle: habr.com
