3 Jahre mit Kubernetes in Produktion: Hier ist was wir bekommen haben

Ca. übers. : In einem anderen Artikel aus der Kategorie "Lessons Learned" teilt der DevOps-Ingenieur des australischen Unternehmens die wichtigsten Schlussfolgerungen aus der langfristigen Verwendung von Kubernetes in der Produktion für geladene Dienste. Der Autor behandelt Java, CI / CD, Networking und die Komplexität von K8s im Allgemeinen.



Wir haben 2017 mit der Erstellung unseres ersten Kubernetes-Clusters begonnen (mit Version K8s 1.9.4). Wir hatten zwei Cluster. Einer arbeitete an Bare Metal, an virtuellen RHEL-Maschinen, der andere an der AWS EC2-Cloud.



Heute verfügt unsere Infrastruktur über mehr als 400 virtuelle Maschinen, die auf mehrere Rechenzentren verteilt sind. Die Plattform dient als Basis für hochverfügbare geschäftskritische Anwendungen und Systeme, die ein riesiges Netzwerk von fast 4 Millionen aktiven Geräten steuern.



Letztendlich haben Kubernetes unser Leben leichter gemacht, aber der Weg dorthin war dornig und erforderte einen vollständigen Paradigmenwechsel. Nicht nur die Fähigkeiten und Werkzeuge, sondern auch die Herangehensweise an Design und Denken haben sich grundlegend verändert. Wir mussten viele neue Technologien beherrschen und stark in die Infrastrukturentwicklung und Teamentwicklung investieren.



Hier sind die wichtigsten Lehren, die wir aus der Verwendung von Kubernetes in der Produktion über drei Jahre gezogen haben.



1. Eine unterhaltsame Geschichte mit Java-Anwendungen



Wenn es um Microservices und Containerisierung geht, scheuen Ingenieure Java vor allem wegen seiner notorisch unvollständigen Speicherverwaltung. Heute ist die Situation jedoch anders und die Kompatibilität von Java mit Containern hat sich in den letzten Jahren verbessert. Schließlich laufen auch beliebte Systeme wie Apache Kafka und Elasticsearch in Java.



In den Jahren 2017-2018 wurden einige unserer Anwendungen in Java Version 8 ausgeführt. Sie weigerten sich oft, in containerisierten Umgebungen wie Docker zu funktionieren, und stürzten aufgrund von Problemen mit dem Heap-Speicher und unzureichenden Garbage Collectors ab. Wie sich herausstellte, wurden diese Probleme durch die Unfähigkeit von JVM verursacht , Linux-Containerisierungsmechanismen ( cgroupsund namespaces) auszuführen .



Seitdem hat Oracle erhebliche Anstrengungen unternommen, um die Kompatibilität von Java mit der Containerwelt zu verbessern. Bereits in Version 8 von Java schienen experimentelle JVM-Flags diese Probleme zu beheben: XX:+UnlockExperimentalVMOptionsUnd XX:+UseCGroupMemoryLimitForHeap.



trotz aller Verbesserungen würde niemand behaupten, dass Java im Vergleich zu Python immer noch einen schlechten Ruf als übermäßig speicherintensiv und langsam zu starten hat oder gehen. Dies ist hauptsächlich auf die Besonderheiten der Speicherverwaltung in JVM und ClassLoader zurückzuführen.



Wenn wir heute mit Java arbeiten müssen, versuchen wir zumindest, Version 11 oder höher zu verwenden. Und unsere Speicherbeschränkungen in Kubernetes sind 1 GB höher als die maximale Heapspeicherbeschränkung in der JVM (-Xmx) (nur für den Fall). Wenn die JVM 8 GB für den Heap-Speicher verwendet, wird das Kubernetes-Speicherlimit für die Anwendung auf 9 GB festgelegt. Dank dieser Maßnahmen und Verbesserungen ist das Leben etwas einfacher geworden.



2. Aktualisierungen im Zusammenhang mit dem Kubernetes-Lebenszyklus



Das Kubernetes Lifecycle Management (Updates, Ergänzungen) ist umständlich und schwierig, insbesondere wenn der Cluster auf Bare-Metal- oder virtuellen Maschinen basiert . Es stellte sich heraus, dass es für ein Upgrade auf eine neue Version viel einfacher ist, einen neuen Cluster zu erstellen und dann Workloads darauf zu übertragen. Das Aktualisieren vorhandener Websites ist einfach unpraktisch, da es viel Aufwand und sorgfältige Planung erfordert.



Dies liegt daran, dass Kubernetes zu viele "bewegliche" Teile hat, um sie beim Upgrade zu berücksichtigen. Damit der Cluster funktioniert, müssen Sie alle diese Komponenten zusammenfassen - von Docker bis zu CNI-Plugins wie Calico oder Flannel. Projekte wie Kubespray, KubeOne, kops und kube-aws vereinfachen den Prozess etwas, sind aber nicht ohne Nachteile.



Wir haben unsere Cluster mithilfe von Kubespray in virtuellen RHEL-Maschinen bereitgestellt. Er hat sich als ausgezeichnet erwiesen. Kubespray verfügte über Skripte zum Erstellen, Hinzufügen oder Entfernen von Knoten, Aktualisieren einer Version und fast alles, was Sie für die Arbeit mit Kubernetes in der Produktion benötigen. Das Upgrade-Skript wurde jedoch von der Einschränkung begleitet, dass auch kleinere Versionen nicht übersprungen werden sollten. Mit anderen Worten, um zur gewünschten Version zu gelangen, musste der Benutzer alle Zwischenversionen installieren.



Wenn Sie Kubernetes verwenden möchten oder bereits verwenden, sollten Sie sich überlegen, wie Ihre K8-Lebenszyklusschritte aussehen und wie sie in Ihre Lösung passen. Es ist oft einfacher, einen Cluster zu erstellen und auszuführen, als ihn auf dem neuesten Stand zu halten.



3. Erstellen und bereitstellen



Seien Sie darauf vorbereitet, dass Sie die Build- und Deployment-Pipelines überarbeiten müssen. Mit dem Übergang zu Kubernetes haben wir eine radikale Transformation dieser Prozesse erfahren. Wir haben nicht nur Jenkins-Pipelines umstrukturiert, sondern mithilfe von Tools wie Helm neue Strategien für das Erstellen und Arbeiten mit Git entwickelt, Docker-Images markiert und Helm-Diagramme versioniert.



Sie benötigen eine einzige Strategie, um Ihren Code, Kubernetes-Bereitstellungsdateien, Docker-Dateien, Docker-Images, Helmdiagramme und eine Möglichkeit zum Zusammenfügen zu verwalten.



Nach mehreren Iterationen haben wir uns für das folgende Diagramm entschieden:



  • Der Anwendungscode und seine Helmdiagramme befinden sich in verschiedenen Repositorys. Dies ermöglicht es uns, sie unabhängig voneinander zu versionieren ( semantische Versionierung ).
  • , , . , , app-1.2.0 charts-1.1.0. (values) Helm, patch- (, 1.1.0 1.1.1). (RELEASE.txt) .
  • , Apache Kafka Redis ( ), . , Docker- Helm-. Docker- , .


(. .: Open Source- Kubernetes — werf — , .)



4. Liveness Readiness ( )



Die Lebendigkeits- und Bereitschaftsprüfungen von Kubernetes eignen sich hervorragend für die autonome Behandlung von Systemproblemen. Sie können Container bei Fehlern neu starten und den Datenverkehr von "ungesunden" Instanzen umleiten. Unter bestimmten Umständen können diese Überprüfungen jedoch zu einem zweischneidigen Schwert werden und sich auf den Start und die Wiederherstellung von Anwendungen auswirken (dies gilt insbesondere für Stateful-Anwendungen wie Messaging-Plattformen oder Datenbanken).



Unsere Kafka wurde ihr Opfer. Wir hatten eine zustandsbehaftete Menge von 3 Brokerund 3 Zookeepermit replicationFactor= 3 undminInSyncReplica= 2. Das Problem trat beim Neustart von Kafka nach zufälligen Abstürzen oder Abstürzen auf. Beim Start führte Kafka zusätzliche Skripte aus, um beschädigte Indizes zu reparieren. Dies dauerte je nach Schwere des Problems 10 bis 30 Minuten. Diese Verzögerung führte dazu, dass Lebendigkeitstests immer wieder fehlschlugen und Kubernetes Kafka "tötete" und neu startete. Infolgedessen konnte Kafka nicht nur die Indizes korrigieren, sondern sogar starten.



Zu diesem Zeitpunkt bestand die einzige Lösung darin, den Parameter initialDelaySecondsin den Einstellungen für den Lebendigkeitstest so anzupassen , dass die Überprüfungen erst nach dem Start des Containers durchgeführt wurden. Die größte Herausforderung besteht natürlich darin, zu entscheiden, welche Verzögerung eingestellt werden soll. Einzelne Starts nach einem Fehler können bis zu einer Stunde dauern, und dies muss berücksichtigt werden. Auf der anderen Seite, je mehrinitialDelaySecondsDie langsameren Kubernetes reagieren auf Fehler beim Start des Containers.



In diesem Fall ist der Sweet Spot der Wert initialDelaySeconds, der Ihren Ausfallsicherheitsanforderungen am besten entspricht, während die Anwendung dennoch genügend Zeit hat, um in allen Fehlersituationen (Festplattenfehler, Netzwerkprobleme, Systemabstürze usw.) erfolgreich zu starten.



Update : In neueren Versionen von Kubernetes wurde ein dritter Testtyp namens Startsonde angezeigt. Es ist als Alpha-Version seit Version 1.16 und als Beta-Version seit Version 1.18 verfügbar.



Die Startsonde löst das oben genannte Problem, indem sie die Bereitschafts- und Lebendigkeitsprüfungen deaktiviert, bis der Container startet, wodurch die Anwendung normal gestartet werden kann.


5. Arbeiten mit externer IP



Wie sich herausstellt, übt die Verwendung statischer externer IP-Adressen für den Zugriff auf Dienste einen erheblichen Druck auf den Verbindungsverfolgungsmechanismus des Kernels aus. Wenn Sie nicht sorgfältig darüber nachdenken, kann es "brechen".



In unserem Cluster verwenden wir Calicosowohl CNI BGPals auch als Routing-Protokoll sowie zur Interaktion mit Grenzroutern. Der Kube-Proxy-Modus ist aktiviert iptables. Wir öffnen den Zugang zu unserem stark ausgelasteten Dienst in Kubernetes (der täglich Millionen von Verbindungen verarbeitet) über eine externe IP. Aufgrund des SNAT und der Maskierung, die durch softwaredefinierte Netzwerke verursacht werden, benötigt Kubernetes einen Mechanismus, um all diese logischen Abläufe zu verfolgen. Dafür verwendet K8s diese Kernwerkzeuge als onntrackundnetfilter... Mit ihrer Hilfe verwaltet es externe Verbindungen zu einer statischen IP, die dann in die interne IP des Dienstes und schließlich in die IP-Adresse des Pods konvertiert wird. Und das alles mit einer Tabelle conntrackund iptables.



Die Möglichkeiten der Tabelle sind jedoch nicht conntrackunbegrenzt. Wenn das Limit erreicht ist, kann der Kubernetes-Cluster (genauer gesagt der Betriebssystemkern im Kern) keine neuen Verbindungen mehr akzeptieren. In RHEL kann diese Grenze wie folgt überprüft werden:



$  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144


Eine Möglichkeit, diese Einschränkung zu umgehen, besteht darin, mehrere Knoten mit Edge-Routern zu kombinieren, sodass eingehende Verbindungen zu einer statischen IP über den gesamten Cluster verteilt werden. Wenn sich in Ihrem Cluster eine große Flotte von Computern befindet, kann dieser Ansatz die Größe der Tabelle erheblich erhöhen conntrack, um eine sehr große Anzahl eingehender Verbindungen zu verarbeiten.



Dies hat uns völlig verwirrt, als wir 2017 anfingen. Vor relativ kurzer Zeit (im April 2019) veröffentlichte das Calico-Projekt jedoch eine detaillierte Studie unter dem passenden Titel " Warum conntrack nicht mehr dein Freund ist " (es gibt eine solche Übersetzung ins Russische - ca. übersetzt ) .



Benötigen Sie wirklich Kubernetes?



Drei Jahre sind vergangen, aber wir entdecken / lernen jeden Tag etwas Neues. Kubernetes ist eine komplexe Plattform mit eigenen Herausforderungen, insbesondere im Bereich des Startens und des Betriebs der Umgebung. Es wird Ihr Denken, Ihre Architektur und Ihre Einstellung zum Design verändern. Sie müssen sich mit der Vergrößerung und Aufrüstung von Teams befassen. Wenn Sie jedoch



in der Cloud arbeiten und Kubernetes als Dienst verwenden können, ersparen Sie sich die meisten Probleme, die mit der Wartung der Plattform verbunden sind (z. B. die Erweiterung des CIDR des internen Netzwerks und die Aktualisierung von Kubernetes).



Heute haben wir verstanden, dass die Hauptfrage, die wir uns stellen müssen, wirklich istBenötigen Sie Kubernetes? Es hilft Ihnen zu beurteilen, wie global das Problem ist und ob Kubernetes Ihnen hilft, damit umzugehen.



Der Umzug nach Kubernetes ist teuer. Die Vorteile Ihres Anwendungsfalls (und wie viel und wie er die Plattform nutzt) sollten den von Ihnen gezahlten Preis rechtfertigen. In diesem Fall kann Kubernetes Ihre Produktivität erheblich verbessern.



Denken Sie daran, dass Technologie um der Technologie willen bedeutungslos ist.



PS vom Übersetzer



Lesen Sie auch in unserem Blog:






All Articles