Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Am 8. April auf der Konferenz Saint HighLoad++ 2019, im Rahmen der Sektion „DevOps und Betrieb“ prĂ€sentierten drei Mitarbeiter des Unternehmens „Flant“ den Vortrag „Erweiterung und ErgĂ€nzung von Kubernetes“. Darin erlĂ€utern wir zahlreiche Situationen, in denen wir die Möglichkeiten von Kubernetes erweitern und ergĂ€nzen wollten, fĂŒr die wir jedoch keine fertige und einfache Lösung finden konnten. Die notwendigen Lösungen sind in Form von Open Source-Projekten entstanden, denen auch dieser Vortrag gewidmet ist.

Traditionell freuen wir uns, das Video mit dem Vortrag (50 Minuten, deutlich informativer als der Artikel) sowie eine schriftliche Zusammenfassung zu prÀsentieren. Los geht's!

Kern und Erweiterungen in K8s

Kubernetes verÀndert die Branche und etablierte AnsÀtze im Management:

  • Dank seiner Abstraktionen, agieren wir nicht mehr mit Begriffen wie Konfigurationseinstellungen oder dem AusfĂŒhren von Befehlen (Chef, Ansible
), sondern nutzen Container-Gruppierungen, Dienste usw.
  • Wir können Anwendungen vorbereiten, ohne uns ĂŒber die Feinheiten der konkreten Plattform, auf der sie betrieben werden, Gedanken zu machen: Bare Metal, Cloud eines Anbieters usw.
  • Mit K8s sind diese Möglichkeiten zugĂ€nglicher denn je geworden. beste Praktiken fĂŒr die Infrastrukturorganisation: Skalierungstechniken, Selbstheilung, Ausfallsicherheit usw.

Allerdings ist nicht alles so reibungslos: Mit Kubernetes kommen auch neue Herausforderungen.

Kubernetes nicht ist ein Kombi, der alle Probleme aller Benutzer löst. Kern Kubernetes kĂŒmmert sich nur um die grundlegenden Funktionen, die in jedem Cluster vorhanden sind:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Im Kern von Kubernetes wird eine grundlegende Set an Primitiven definiert – zur Gruppierung von Containern, Verkehrsmanagement usw. DarĂŒber haben wir in einem vor zwei Jahren gehaltenen Vortrag berichtet..

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Andererseits bietet K8s großartige Möglichkeiten zur Erweiterung der verfĂŒgbaren Funktionen, die helfen, auch andere – spezifische – Anforderungen der Benutzer zu erfĂŒllen. Die ErgĂ€nzungen in Kubernetes werden von den Cluster-Administratoren verwaltet, die alles Notwendige installieren und konfigurieren mĂŒssen, damit ihr Cluster die passende Form annimmt [um ihre spezifischen Aufgaben zu lösen]. Was sind das fĂŒr ErgĂ€nzungen? Lassen Sie uns einige Beispiele betrachten.

Beispiele fĂŒr ErgĂ€nzungen

Nachdem wir Kubernetes eingerichtet haben, könnten wir ĂŒberrascht sein, dass das Netzwerk, das fĂŒr die Kommunikation von Pods sowohl innerhalb als auch zwischen Knoten erforderlich ist, nicht von selbst funktioniert. Der Kubernetes-Kern garantiert nicht die erforderlichen Verbindungen — stattdessen definiert er die Netzwerk schnittstelle (CNI) fĂŒr Drittanbieter-Plugins. Wir mĂŒssen eines dieser Plugins installieren, das fĂŒr die Netzwerkkonfiguration verantwortlich sein wird.

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Ein naher Vergleich sind Lösungen fĂŒr die Datenspeicherung (lokale Festplatte, Netzwerk-BlockgerĂ€t, Ceph
). UrsprĂŒnglich waren sie im Kern integriert, aber mit dem Erscheinen von CSI Ă€ndert sich die Situation zu der bereits beschriebenen: In Kubernetes ist die Schnittstelle vorhanden, und ihre Implementierung erfolgt ĂŒber Drittanbieter-Module.

Weitere Beispiele sind:

  • Ingress-Controller (eine Übersicht finden Sie in der unserem kĂŒrzlichen Artikel).
  • cert-manager:

    Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

  • Operatoren — ist eine ganze Klasse von Erweiterungen (zu denen auch der erwĂ€hnte cert-manager gehört), die Primitive und Controller definieren. Ihre Funktionsweise ist nur durch unsere Vorstellungskraft begrenzt und ermöglicht es, fertige Infrastrukturkomponenten (z. B. Datenbanksysteme) in Primitive umzuwandeln, mit denen es sich viel einfacher arbeiten lĂ€sst (als mit einer Sammlung von Containern und deren Konfigurationen). Es gibt eine riesige Anzahl von Operatoren — viele davon sind zwar noch nicht produktionsbereit, aber das ist nur eine Frage der Zeit:

    Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

  • Metriken — eine weitere Illustration, wie in Kubernetes die Schnittstelle (Metrics API) von der Implementierung (drittanbieter Erweiterungen wie der Prometheus-Adapter, Datadog Cluster-Agent
) getrennt wurde.
  • FĂŒr Überwachung und Statistik, wo in der Praxis nicht nur benötigt werden Prometheus und Grafana, sondern auch kube-state-metrics, node-exporter usw.

Und das ist lĂ€ngst nicht die vollstĂ€ndige Liste der Erweiterungen
 Zum Beispiel installieren wir in der Firma „Flant“ mittlerweile 29 Erweiterungen (insgesamt erzeugen sie 249 Kubernetes-Objekte). Anders gesagt, wir sehen das Leben des Clusters ohne Erweiterungen nicht.

Automatisierung

Operatoren sind entwickelt worden, um alltĂ€gliche Routineaufgaben zu automatisieren, mit denen wir tĂ€glich konfrontiert sind. Hier sind einige praktische Beispiele, fĂŒr die es eine ausgezeichnete Lösung ist, einen Operator zu schreiben:

  1. Es gibt ein privates (d.h. ein Login erforderndes) Registry mit Images fĂŒr Anwendungen. Es wird vorausgesetzt, dass jedem Pod ein spezielles Secret zugeordnet wird, das die Authentifizierung im Registry ermöglicht. Unsere Aufgabe besteht darin, sicherzustellen, dass sich dieses Secret im Namespace befindet, damit die Pods die Images herunterladen können. Es kann sehr viele Anwendungen geben (jede davon benötigt ein Secret), und es ist auch sinnvoll, die Secrets regelmĂ€ĂŸig zu aktualisieren, sodass eine manuelle Verteilung der Secrets nicht in Frage kommt. Hier kommt der Operator ins Spiel: Wir erstellen einen Controller, der darauf wartet, dass der Namespace erscheint, und bei diesem Ereignis das Secret in den Namespace hinzufĂŒgt.
  2. StandardmĂ€ĂŸig ist der Internetzugang von Pods blockiert. Manchmal ist dieser jedoch erforderlich: Es ist sinnvoll, dass der Zugriff auf einfache Weise gewĂ€hrt wird, ohne spezifische Kenntnisse zu benötigen, zum Beispiel durch das Vorhandensein eines bestimmten Labels im Namespace. Wie kann uns hier ein Operator helfen? Es wird ein Controller erstellt, der auf das Erscheinen des Labels im Namespace wartet und die entsprechenden Regeln fĂŒr den Internetzugang hinzufĂŒgt.
  3. Ähnliche Situation: Angenommen, wir mĂŒssen einem Knoten ein bestimmtes TainthinzufĂŒgen, wenn er ein entsprechendes Label (mit einem bestimmten PrĂ€fix) hat. Die Handlungen des Operators sind offensichtlich...

In jedem Cluster mĂŒssen routinemĂ€ĂŸige Aufgaben erledigt werden, und der richtige Weg, dies zu tun, ist durch die Verwendung von Operatoren.

Zusammenfassend haben wir zu dem Schluss gekommen, dass ein angenehmes Arbeiten in Kubernetes erfordert: a) Erweiterungen zu installieren, b) Operatoren zu entwickeln (um alltÀgliche Administrationsaufgaben zu lösen).

Wie schreibt man einen Operator fĂŒr Kubernetes?

Im Grunde ist das Schema einfach:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)


 aber hier stellt sich heraus, dass:

  • Die Kubernetes API ist eine ziemlich komplexe Angelegenheit, die viel Zeit zur Einarbeitung benötigt;
  • Programmierung ist nicht fĂŒr jeden geeignet (die Programmiersprache Go wurde als bevorzugt gewĂ€hlt, da es ein spezielles Framework dafĂŒr gibt — Operator SDK);
  • Die Situation ist mit dem Framework selbst Ă€hnlich.

Fazit: Um einen Controller (Operator) zu schreiben, muss man betrĂ€chtliche Ressourcen aufwenden um das Fachwissen zu erlernen. Dies wĂ€re gerechtfertigt fĂŒr „große“ Operatoren — zum Beispiel fĂŒr die Datenbank MySQL. Aber wenn wir uns an die oben beschriebenen Beispiele erinnern (das Bereitstellen von Secrets, der Internetzugang fĂŒr Pods
), die ebenfalls korrekt umgesetzt werden sollen, erkennen wir, dass der Aufwand die derzeit benötigten Ergebnisse ĂŒbersteigt:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Es entsteht also ein Dilemma: viel Ressourcen investieren und das richtige Werkzeug fĂŒr das Schreiben von Operatoren erwerben oder „traditionell“ (aber schnell) handeln. Um einen Kompromiss zwischen diesen Extremen zu finden, haben wir unser Projekt geschaffen: shell-operator (siehe auch seine neue AnkĂŒndigung auf HabrĂ©).

Shell-Operator

Wie funktioniert er? Im Cluster gibt es einen Pod, in dem sich das Go-BinĂ€rprogramm mit dem Shell-Operator befindet. Daneben wird eine Sammlung von Hooks (mehr dazu — siehe unten). Der Shell-Operator abonniert bestimmte Ereignisse Im Kubernetes API gibt es Ereignisse, die das entsprechende Hooks auslösen.

Wie versteht der Shell-Operator, welche Hooks bei welchen Ereignissen ausgelöst werden sollen? Diese Information wird vom Hook selbst an den Shell-Operator ĂŒbermittelt und dies geschieht sehr einfach.

Ein Hook ist ein Bash-Skript oder eine andere ausfĂŒhrbare Datei, die ein einziges Argument unterstĂŒtzt. --config und gibt als Antwort JSON zurĂŒck. Letzteres bestimmt, welche Objekte von Interesse sind und auf welche Ereignisse (fĂŒr diese Objekte) reagiert werden soll:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Ich werde die Implementierung des Shell-Operators anhand eines unserer Beispiele verdeutlichen – die Bereitstellung von Geheimnissen fĂŒr den Zugriff auf ein privates Registry mit Anwendungs-Images. Dieser Prozess besteht aus zwei Schritten.

Praxis: 1. Wir schreiben einen Hook

Zuerst verarbeiten wir im Hook --config, und geben an, dass uns Namespaces interessieren, insbesondere – deren Erstellungszeitpunkt:

[[ $1 == "--config" ]] ; then
  cat << EOF
{
  "onKubernetesEvent": [
    {
      "kind": "namespace",
      "event": ["add"]
    }
  ]
}
EOF



Wie wird die Logik aussehen? Auch das ist recht einfach:



else
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  kubectl create -n ${createdNamespace} -f - << EOF
Kind: Secret
...
EOF
fi

Im ersten Schritt erfahren wir, welcher Namespace erstellt wurde, und im zweiten Schritt erstellen wir ĂŒber kubectl ein Geheimnis fĂŒr diesen Namensraum.

Praxis: 2. Ein Image erstellen

Es bleibt, den erstellten Hook an den shell-operator zu ĂŒbergeben — wie macht man das? Der shell-operator wird als Docker-Image bereitgestellt, daher besteht unsere Aufgabe darin, den Hook in ein spezielles Verzeichnis in diesem Image hinzuzufĂŒgen:

FROM flant/shell-operator:v1.0.0-beta.1
ADD my-handler.sh /hooks

Jetzt bleibt nur noch, ihn zu erstellen und hochzuladen:

$ docker build -t registry.example.com/my-operator:v1 .
$ docker push registry.example.com/my-operator:v1

Der letzte Schritt ist, das Image im Cluster zu deployen. Dazu schreiben wir Deployment:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1 # 1
      serviceAccountName: my-operator              # 2

Dabei sind zwei Punkte zu beachten:

  1. die Angabe des gerade erstellten Images;
  2. es handelt sich um eine systemtechnische Komponente, die (mindestens) die Berechtigungen benötigt, um sich fĂŒr Ereignisse in Kubernetes anzumelden und um Secrets nach Namespaces zu verteilen, daher erstellen wir fĂŒr den Hook ein ServiceAccount (und eine Reihe von Regeln).

Das Ergebnis ist — wir haben unser Problem auf native Kubernetes-Art gelöst, indem wir einen Operator zum Verteilen von Secrets erstellt haben.

Weitere Möglichkeiten des shell-operators

Um die Objekte des von Ihnen ausgewÀhlten Typs, mit denen der Hook arbeiten wird, einzuschrÀnken, können diese gefiltert werden., indem nach bestimmten Labels (oder durch matchExpressions):

"onKubernetesEvent": [
  {
    "selector": {
      "matchLabels": {
        "foo": "bar",
       },
       "matchExpressions": [
         {
           "key": "allow",
           "operation": "In",
           "values": ["wan", "warehouse"],
         },
       ],
     }
     

  }
]

Vorgesehen ein Deduplication-Mechanismus, der — mithilfe eines jq-Filters — große JSON-Objekte in kleine umwandelt, wobei nur die Parameter bleiben, deren Änderungen wir verfolgen möchten.

Bei der Anrufung des Hooks ĂŒbergibt der Shell-Operator Daten ĂŒber das Objekt, die fĂŒr beliebige Zwecke verwendet werden können.

Ereignisse, die die Hooks auslösen, sind nicht auf Kubernetes-Ereignisse beschrĂ€nkt: der Shell-Operator unterstĂŒtzt auch zeitgesteuerte Hooks analog zu crontab in einem traditionellen Scheduler, sowie ein spezielles Ereignis onStartup. All diese Ereignisse können kombiniert und demselben Hook zugewiesen werden.

Und noch zwei Besonderheiten des Shell-Operators:

  1. Er arbeitet asynchronSeit dem Eintreffen eines Ereignisses in Kubernetes (z. B. der Erstellung eines Objekts) können auch andere Ereignisse im Cluster aufgetreten sein (z. B. das Löschen desselben Objekts), die in Hooks berĂŒcksichtigt werden mĂŒssen. Wenn ein Hook mit einem Fehler ausgefĂŒhrt wird, wird er standardmĂ€ĂŸig wiederholt aufgerufen bis er erfolgreich abgeschlossen ist (dieses Verhalten kann geĂ€ndert werden).
  2. Er exportiert Metriken fĂŒr Prometheus, mit denen festgestellt werden kann, ob der Shell-Operator funktioniert, die Anzahl der Fehler pro Hook ermittelt und die aktuelle GrĂ¶ĂŸe der Warteschlange erfahren werden kann.

Zusammenfassend zu diesem Teil des Berichts:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Installation von Erweiterungen

FĂŒr eine komfortable Arbeit mit Kubernetes wurde auch die Notwendigkeit der Installation von Erweiterungen angesprochen. DarĂŒber werde ich am Beispiel des Weges unseres Unternehmens erzĂ€hlen, wie wir das derzeit umsetzen.

Wir haben mit Kubernetes angefangen, indem wir mehrere Cluster hatten, in denen Ingress die einzige Erweiterung war. In jeden Cluster musste er unterschiedlich installiert werden, daher haben wir mehrere YAML-Konfigurationen fĂŒr verschiedene Umgebungen erstellt: Bare Metal, AWS


Die Anzahl der Cluster nahm zu – und damit auch die Anzahl der Konfigurationen. DarĂŒber hinaus haben wir diese Konfigurationen verbessert, wodurch sie ziemlich heterogen wurden:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Um alles in Ordnung zu bringen, haben wir mit einem Skript begonnen (install-ingress.sh), das den Typ des Clusters als Argument nahm, in das wir deployen wollten, die benötigte YAML-Konfiguration generierte und diese in Kubernetes ausrollte.

Kurz gesagt, unser weiterer Weg und die damit verbundenen Gedanken waren wie folgt:

  • Zum Arbeiten mit YAML-Konfigurationen benötigt man einen Template-Generator (in den frĂŒhen Phasen war das ein einfaches sed);
  • Mit der wachsenden Anzahl von Clustern entstand die Notwendigkeit fĂŒr automatisierte Updates (die frĂŒheste Lösung war, das Skript in Git zu legen, es ĂŒber cron zu aktualisieren und auszufĂŒhren);
  • Ein Ă€hnliches Skript wurde fĂŒr Prometheus benötigt (install-prometheus.sh), es ist jedoch bemerkenswert, dass es viel mehr Eingabedaten erfordert, sowie deren Speicherung (idealerweise zentralisiert und im Cluster), wobei einige Daten (Passwörter) automatisch generiert werden konnten:

    Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

  • Das Risiko, etwas Falsches auf einer wachsenden Anzahl von Clustern auszurollen, stieg stĂ€ndig, weshalb wir erkannten, dass die Installer (d.h. zwei Skripte: eines fĂŒr Ingress und eines fĂŒr Prometheus) Staging benötigten (mehrere Branches in Git, mehrere cron-Jobs zur Aktualisierung in den jeweiligen: stabilen oder Test-Clustern);
  • mit kubectl apply Es wurde schwierig zu arbeiten, da es nicht deklarativ ist und nur Objekte erstellen kann, aber keine Entscheidungen ĂŒber deren Status treffen oder sie löschen kann;
  • es fehlten einige Funktionen, die wir zu diesem Zeitpunkt noch nicht implementiert hatten:
    • vollstĂ€ndige Kontrolle ĂŒber das Ergebnis der Clusteraktualisierungen,
    • automatische Bestimmung bestimmter Parameter (Eingaben fĂŒr Installationsskripte) basierend auf Daten, die aus dem Cluster gewonnen werden können (Discovery),
    • eine logische Weiterentwicklung in Form von Continuous Discovery.

Alle diese Erfahrungen haben wir in einem anderen unserer Projekte umgesetzt — addon-operator.

Addon-Operator

Dessen Grundlage ist der bereits erwĂ€hnte Shell-Operator. Das gesamte System sieht folgendermaßen aus:

Zu den Hooks des Shell-Operators werden hinzugefĂŒgt:

  • Werte-Speicher,
  • Helm-Chart,
  • eine Komponente, die den Werte-Speicher ĂŒberwacht und — im Falle von VerĂ€nderungen — Helm auffordert, das Chart neu auszuspielen.

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

So können wir auf ein Ereignis in Kubernetes reagieren, einen Hook auslösen und aus diesem Hook Änderungen im Speicher vornehmen, nach denen das Chart neu ausgerollt wird. In dem resultierenden Schema heben wir eine Reihe von Hooks und das Chart in eine Komponente hervor, die wir "Modul" nennen. Modul:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Es können zahlreiche Module vorhanden sein, zu denen wir globale Hooks, ein globales Werte-Repository und eine Komponente hinzufĂŒgen, die dieses globale Repository ĂŒberwacht.

Jetzt, wenn in Kubernetes etwas passiert, können wir mit einem globalen Hook darauf reagieren und etwas im globalen Repository Ă€ndern. Diese Änderung wird bemerkt und löst die EinfĂŒhrung aller Module im Cluster aus:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Dieses Schema erfĂŒllt alle vorher genannten Anforderungen fĂŒr die Installation von Add-ons:

  • Die Template-Verwaltung und DeklarativitĂ€t wird von Helm ĂŒbernommen.
  • Die Frage des automatischen Updates wird durch einen globalen Hook gelöst, der regelmĂ€ĂŸig ins Registry schaut und, falls ein neues System-Image vorhanden ist, dieses neu ausrollt (also "sich selbst").
  • Die Speicherung der Einstellungen im Cluster erfolgt ĂŒber ConfigMap, in dem die primĂ€ren Daten fĂŒr die Speicher (beim Start werden sie in die Speicher geladen) aufgezeichnet werden.
  • Die Probleme der Passwortgenerierung, Discovery und Continuous Discovery werden durch Hooks gelöst.
  • Das Staging wird durch Tags erreicht, die Docker standardmĂ€ĂŸig unterstĂŒtzt.
  • Die Kontrolle des Ergebnisses erfolgt durch Metriken, anhand derer wir den Status verstehen können.

Das gesamte System ist als ein einziges Go-BinĂ€rprogramm implementiert, das den Namen addon-operator trĂ€gt. Dadurch wirkt das Schema ĂŒbersichtlicher:

Erweiterung und ErgĂ€nzung von Kubernetes (Überblick und Video-PrĂ€sentation)

Die Hauptkomponente in diesem Schema ist eine Sammlung von Modulen (in grau am unteren Rand hervorgehoben). Nun können wir mit geringem Aufwand ein Modul fĂŒr die gewĂŒnschte Erweiterung schreiben und sind zuversichtlich, dass es in jeden Cluster installiert wird, aktualisiert wird und auf die erforderlichen Ereignisse im Cluster reagiert.

Flant verwendet addon-operator in ĂŒber 70 Kubernetes-Clustern. Der aktuelle Status ist Alpha-Version. Derzeit bereiten wir die Dokumentation vor, um die Beta-Version herauszubringen; bis dahin sind im Repository Beispiele verfĂŒgbar, auf deren Grundlage Sie Ihr eigenes addon erstellen können.

Woher bekommt man die Module fĂŒr den addon-operator? Die Veröffentlichung unserer Bibliothek ist der nĂ€chste Schritt fĂŒr uns, den wir fĂŒr den Sommer planen.

Videos und Folien

Video vom Vortrag (~50 Minuten):

Video abspielen

PrÀsentation des Vortrags:

P.S.

Weitere VortrÀge in unserem Blog:

Vielleicht interessieren Sie auch die folgenden Publikationen:

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster