Bei Volterra besteht die Aufgabe des SRE-Teams darin, eine globale SaaS-basierte Edge-Plattform zu betreiben. Wir müssen verschiedene Herausforderungen bei der Verwaltung einer großen Anzahl von Anwendungsclustern in verschiedenen Zuständen (z. B. online, offline, Admin-Down usw.) lösen und tun dies, indem wir das Kubernetes-Ökosystem und die Tools mit einem deklarativen Pull-basierten Modell unter Verwendung von GitOps nutzen.
In diesem Blog beschreiben wir:
Verwendung von GitOps zur effektiven Verwaltung und Überwachung einer großen Flotte von Infrastrukturen (physische oder Cloud-Hosts) und K8s-Clustern
Wir werden tiefer in die im großen Maßstab (3.000 Edge-Sites) gewonnenen Erkenntnisse eintauchen, was auch in meinem jüngsten Vortrag bei Cloud Native Rejekts in San Diego behandelt wurde.
Das Architekturdiagramm (Abbildung 1) oben zeigt die logische Konnektivität zwischen unseren REs und CEs, wobei jeder CE redundante (IPSec- oder SSL-VPN-)Verbindungen zum nächstgelegenen RE herstellt.
Als wir vor etwa zwei Jahren mit der Entwicklung unserer Plattform begannen, bat uns unser Produktteam, die folgenden Herausforderungen zu lösen:
Angesichts unserer Anforderungen und Herausforderungen beim Betrieb eines stark verteilten Systems haben wir beschlossen, mehrere Grundsätze festzulegen, an die sich unser SRE-Team halten muss, um nachgelagerte Probleme zu reduzieren:
Im Rahmen der Lebenszyklusverwaltung der Edge-Site mussten wir eine Lösung finden, wie das Host-Betriebssystem bereitgestellt, grundlegende Konfigurationen vorgenommen (z. B. Benutzerverwaltung, Zertifizierungsstelle, Hugepages usw.), K8s gestartet, Workloads verteilt und laufende Konfigurationsänderungen verwaltet werden.
Eine der Optionen, die wir in Betracht gezogen, aber letztendlich verworfen haben, war die Verwendung von KubeSpray+Ansible (zur Verwaltung des Betriebssystems und Bereitstellung von K8s) und Helm/Spinnaker (zur Bereitstellung von Workloads). Der Grund für unsere Ablehnung lag darin, dass wir dafür zwei bis drei Open-Source-Tools hätten verwalten und anschließend erhebliche Änderungen vornehmen müssen, um unsere Anforderungen zu erfüllen. Diese Anforderungen stiegen immer weiter, je mehr Funktionen wir hinzufügten, beispielsweise die automatische Skalierung von Edge-Clustern, die Unterstützung sicherer TPM-Module, differenzielle Upgrades usw.
Da unser Ziel darin bestand, es einfach zu halten und die Anzahl der direkt im Betriebssystem ausgeführten Komponenten (außerhalb von Kubernetes) zu minimieren, beschlossen wir, einen leichten Golang-Daemon namens Volterra Platform Manager (VPM) zu schreiben. Dies ist der einzige systemd-Docker-Container im Betriebssystem und er fungiert als Schweizer Taschenmesser, das viele Funktionen erfüllt:
VPM ist für die Verwaltung des Lebenszyklus des Hostbetriebssystems einschließlich Installation, Upgrades, Patches, Konfiguration usw. verantwortlich. Es gibt viele Aspekte, die konfiguriert werden müssen (z. B. Zuweisung großer Seiten, /etc/hosts usw.).
Management zur Bereitstellung eines Lebenszyklus für das Kubernetes-Manifest. Anstelle von Helm haben wir uns für die Verwendung der K8s-Client-Go-Bibliothek entschieden, die wir in VPM integriert haben, und mehrere Funktionen aus dieser Bibliothek verwendet:
Optimistisch = Ressource erstellen und nicht auf Status warten. Es ist dem Kubernetes-Apply -Befehl sehr ähnlich, bei dem Sie nicht wissen, ob die eigentlichen Pods erfolgreich gestartet wurden.
Pessimistisch = Warten auf den Status der Kubernetes-Ressource. Beispielsweise wartet die Bereitstellung, bis alle Pods bereit sind. Dies ähnelt dem neuen kubectl-Wait- Befehl.
Zusätzlich zu den Konfigurationen im Zusammenhang mit K8s-Manifesten müssen wir auch verschiedene Volterra-Dienste über ihre APIs konfigurieren. Ein Beispiel sind IPsec/SSL-VPN-Konfigurationen – VPM empfängt die Konfiguration von unserer globalen Steuerebene und programmiert sie in einzelnen Knoten.
Mithilfe dieser Funktion können wir eine Box aus der Ferne in den Originalzustand zurücksetzen und den gesamten Installations- und Registrierungsprozess erneut durchführen. Dies ist eine äußerst wichtige Funktion für die Wiederherstellung einer Site, für die ein Konsolen-/physischer Zugriff erforderlich ist.
Auch wenn das Lebenszyklusmanagement von K8 für viele Leute ein großes Diskussionsthema zu sein scheint, macht es für unser Team wahrscheinlich nur 40–50 % des gesamten Arbeitsvolumens aus.
Die Zero-Touch-Bereitstellung der Edge-Site an jedem Standort (Cloud, vor Ort oder Nomadic Edge) ist eine kritische Funktion, da wir weder Zugriff auf einzelne Sites erwarten können, noch so viele Kubernetes-Experten für die Installation und Verwaltung einzelner Sites einsetzen möchten. Es lässt sich einfach nicht auf Tausende skalieren.
Das folgende Diagramm (Abbildung 2) zeigt, wie VPM am Prozess der Registrierung einer neuen Site beteiligt ist:
Wie Sie sehen, ist der gesamte Prozess vollständig automatisiert und der Benutzer muss nichts über die detaillierte Konfiguration wissen oder manuelle Schritte ausführen. Es dauert etwa 5 Minuten, bis das gesamte Gerät online ist und bereit ist, Kunden-Apps und -Anfragen zu verarbeiten.
Das Upgrade ist eines der kompliziertesten Dinge, die wir lösen mussten. Definieren wir, was in Edge-Sites aktualisiert wird:
Es gibt zwei bekannte Methoden, mit denen Updates an Edge-Sites übermittelt werden können:
Unser Ziel für das Upgrade bestand darin, Einfachheit und Zuverlässigkeit zu maximieren – ähnlich wie bei herkömmlichen Mobiltelefon-Upgrades. Darüber hinaus gibt es noch weitere Aspekte, die bei der Upgrade-Strategie berücksichtigt werden müssen. So besteht der Upgrade-Kontext möglicherweise nur beim Site-Betreiber, oder das Gerät ist möglicherweise aufgrund von Verbindungsproblemen usw. für einige Zeit offline oder nicht verfügbar. Diese Anforderungen konnten mit der Pull-Methode leichter erfüllt werden und daher haben wir uns entschieden, diese Methode unseren Bedürfnissen entsprechend zu übernehmen.
Darüber hinaus haben wir uns für GitOps entschieden, da es damit einfacher war, unserem SRE-Team ein standardisiertes Betriebsmodell für die Verwaltung von Kubernetes-Clustern, Workflows und Audit-Änderungen bereitzustellen.
Um die Skalierungsprobleme Tausender von Sites zu lösen, haben wir die in Abbildung 3 dargestellte Architektur für SRE entwickelt:
Zunächst möchte ich betonen, dass wir Git nicht nur zum Speichern von Status oder Manifesten verwenden. Der Grund dafür ist, dass unsere Plattform nicht nur K8s-Manifeste, sondern auch laufende API-Konfigurationen, K8s-Versionen usw. verarbeiten muss. In unserem Fall machen die Manifeste von K8 etwa 60 % der gesamten deklarativen Konfiguration aus. Aus diesem Grund mussten wir darauf aufbauend unsere eigene DSL-Abstraktion entwickeln, die in Git gespeichert ist. Da Git weder eine API noch Funktionen zum Zusammenführen von Parametern bereitstellt, mussten wir zusätzliche Golang-Daemons für SRE entwickeln: Konfigurations-API, Executor und VP-Controller.
Sehen wir uns den Workflow zur Veröffentlichung einer neuen Softwareversion beim Kunden mithilfe unserer SaaS-Plattform an:
Sie können sich hier eine Demo des gesamten Workflows ansehen:
In den vorherigen Abschnitten haben wir beschrieben, wie unsere Tools zum Bereitstellen und Verwalten des Lebenszyklus von Edge-Sites verwendet werden. Um unser Design zu validieren, haben wir beschlossen, eine groß angelegte Umgebung mit dreitausend Kunden-Edge-Sites aufzubauen (siehe Abbildung 4).
Wir haben Terraform verwendet, um 3000 VMs über AWS, Azure, Google und unsere eigene Bare-Metal-Cloud vor Ort bereitzustellen und so die Skalierung zu simulieren. Alle diese VMs waren unabhängige CEs (Customer Edge Sites), die redundante Tunnel zu unseren regionalen Edge Sites (auch PoPs genannt) aufgebaut haben.
Der folgende Screenshot stammt von unserem SRE-Dashboard und zeigt Kantennummern an Positionen, die durch die Kreisgröße dargestellt werden. Zum Zeitpunkt der Erstellung des Screenshots hatten wir etwa 2711 gesunde und 356 ungesunde Randstellen.
Im Rahmen der Skalierung haben wir einige Probleme bei der Konfiguration und im Betrieb festgestellt, die Änderungen an unseren Software-Daemons erforderlich machten. Darüber hinaus sind wir auf viele Probleme mit einem Cloud-Anbieter gestoßen, die zur Eröffnung mehrerer Support-Tickets führten – beispielsweise Latenz bei der API-Antwort, Unfähigkeit, mehr als 500 VMs in einer einzigen Region abzurufen usw.
Die Beobachtbarkeit eines verteilten Systems stellte uns vor viel größere Herausforderungen, als wir das System skalierten.
Für die Metriken haben wir zunächst mit der Prometheus-Föderation begonnen – ein zentrales Prometheus mit globaler Steuerung, das Prometheus in regionalen Rändern (REs) föderiert, das seine Servicemetriken abruft und Metriken aus den verbundenen CEs föderiert. Der Prometheus der obersten Ebene wertete Warnungen aus und diente als Messquelle für weitere Analysen. Wir stießen schnell an die Grenzen dieses Ansatzes (ca. 1000 CEs) und versuchten, die Auswirkungen der wachsenden Zahl von CEs zu minimieren. Wir haben begonnen, vorkalkulierte Reihen für Histogramme und andere Metriken mit hoher Kardinalität zu generieren. Dadurch konnten wir ein oder zwei Tage sparen, danach mussten wir Whitelists für die Messgrößen einsetzen. Am Ende konnten wir die Anzahl der Zeitreihenmetriken von rund 60.000 auf 2.000 für jeden CE-Standort reduzieren.
Nachdem wir die Skalierung auf über 3.000 CE-Sites ausgedehnt und den Betrieb viele Tage lang durchgeführt hatten, wurde uns schließlich klar, dass dies nicht skalierbar war und wir unsere Überwachungsinfrastruktur überdenken mussten. Wir haben uns entschieden, Prometheus auf oberster Ebene (unter globaler Kontrolle) fallen zu lassen und Prometheus in jedem RE in zwei separate Instanzen aufzuteilen. Einer ist für das Scraping lokaler Servicemetriken und der zweite für die Föderierung von CE-Metriken verantwortlich. Beide generieren Warnungen und übertragen Metriken in den zentralen Speicher in Cortex. Cortex wird als Analyse- und Visualisierungsquelle verwendet und ist nicht Teil des Warnflusses der Kernüberwachung. Wir haben mehrere zentralisierte Messlösungen getestet, nämlich Thanos und M3db, und kamen zu dem Schluss, dass Cortex unseren Anforderungen am besten entspricht.
Der folgende Screenshot (Abbildung 7) zeigt den Speicherverbrauch beim Scraping von prometheus-cef zum Zeitpunkt von 3000 Endpunkten. Interessant ist der verbrauchte RAM von 29,7 GB, was angesichts der Größe des Systems eigentlich nicht so viel ist. Eine weitere Optimierung ist möglich, indem das Scraping in mehrere Schritte aufgeteilt wird oder der Remote-Schreibzugriff auf Cortex direkt an den Edge selbst verschoben wird.
Der nächste Screenshot (Abbildung 8) zeigt, wie viele Speicher- und CPU-Ressourcen wir für Cortex-Ingestoren (max. 19 GB RAM) und Distributoren in dieser Größenordnung benötigten. Der größte Vorteil von Cortex ist die horizontale Skalierung, die es uns ermöglicht, mehr Replikate hinzuzufügen als bei Prometheus, wo die Skalierung vertikal erfolgen muss.
Für die Protokollierungsinfrastruktur in CEs und REs verwenden wir Fluentbit-Dienste pro Knoten, die die Dienst- und Systemprotokollereignisse sammeln und an Fluentd im verbundenen RE weiterleiten. Fluentd leitet die Daten an das im RE vorhandene ElasticSearch weiter. Die Daten von ElasticSearch werden von Elastalert ausgewertet und es werden Regeln zum Erstellen von Alertmanager-Warnungen festgelegt. Wir verwenden unsere benutzerdefinierte Integration von Elastalert in Alertmanager, um Warnungen mit denselben Bezeichnungen zu erstellen, die auch Prometheus erstellt.
Die wichtigsten Punkte unserer Überwachungsreise:
- Anfangs hatten wir rund 50.000 Zeitreihen pro CE mit durchschnittlich 15 Labels
- Wir haben es auf durchschnittlich 2000 pro CE optimiert
Einfache While-Listen für Metriknamen und Blacklists für Labelnamen
- Das zentralisierte Prometheus hat das Prometheus aller REs und CEs abgeschafft
- Im Jahr 1000 n. Chr. wurde es unhaltbar, die Menge an Metriken zu verwalten
- Derzeit haben wir Prometheus bei jedem RE (Föderation zu den Promethei der verbundenen CEs) mit RW zu Cortex
- Dezentrale Protokollierungsarchitektur
- Fluentbit als Collector auf jedem Knoten leitet Protokolle an Fluentd (Aggregator) in RE weiter
- ElasticSearch wird in jedem RE eingesetzt und verwendet die Remote-Clustersuche, um Protokolle von einer einzelnen Kibana-Instanz abzufragen.
Ich hoffe, dass dieser Blog Ihnen einen Einblick in die Dinge gibt, die bei der Verwaltung von Tausenden von Edge-Sites und Clustern auf der ganzen Welt berücksichtigt werden müssen. Auch wenn wir die meisten unserer anfänglichen Designanforderungen erfüllen und validieren konnten, liegen noch viele Verbesserungen vor uns …