Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

8. April auf der Konferenz Saint HighLoad++ 2019Im Rahmen des Abschnitts „DevOps und Operations“ wurde ein Bericht „Erweiterung und Ergänzung von Kubernetes“ erstellt, an dessen Erstellung drei Mitarbeiter der Firma Flant beteiligt waren. Darin sprechen wir über zahlreiche Situationen, in denen wir die Fähigkeiten von Kubernetes erweitern und ergänzen wollten, für die wir aber keine fertige und einfache Lösung gefunden haben. Wir verfügen über die notwendigen Lösungen in Form von Open-Source-Projekten, denen auch dieser Vortrag gewidmet ist.

Aus Tradition präsentieren wir gerne Video des Berichts (50 Minuten, viel informativer als der Artikel) und die Hauptzusammenfassung in Textform. Gehen!

Kern und Ergänzungen in K8s

Kubernetes verändert die Branche und längst etablierte Verwaltungsansätze:

  • Dank ihm AbstraktionenWir arbeiten nicht mehr mit Konzepten wie dem Einrichten einer Konfiguration oder dem Ausführen eines Befehls (Chef, Ansible...), sondern verwenden die Gruppierung von Containern, Diensten usw.
  • Wir können Bewerbungen vorbereiten, ohne über die Nuancen der Bewerbung nachzudenken bestimmte Website, auf dem es gestartet wird: Bare Metal, Cloud eines der Anbieter usw.
  • Mit K8s waren Sie noch nie so zugänglich Best Practices zur Organisation der Infrastruktur: Skalierungstechniken, Selbstheilung, Fehlertoleranz usw.

Allerdings läuft natürlich nicht alles so reibungslos: Kubernetes brachte auch seine eigenen neuen Herausforderungen mit sich.

Kubernetes nicht ist ein Mähdrescher, der alle Probleme aller Benutzer löst. Kern Kubernetes ist nur für eine Reihe der minimal notwendigen Funktionen verantwortlich, die in vorhanden sind Jeder Cluster:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Der Kubernetes-Kern definiert einen grundlegenden Satz von Grundelementen zum Gruppieren von Containern, Verwalten des Datenverkehrs usw. Wir haben ausführlicher darüber gesprochen Bericht vor 2 Jahren.

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Andererseits bietet K8s großartige Möglichkeiten zur Erweiterung der verfügbaren Funktionen, die dazu beitragen, andere zu schließen - Spezifisch — Benutzerbedürfnisse. Ergänzungen zu Kubernetes liegen in der Verantwortung von Cluster-Administratoren, die alles Notwendige installieren und konfigurieren müssen, um ihren Cluster „in den richtigen Zustand“ zu bringen [um ihre spezifischen Probleme zu lösen]. Was sind das für Ergänzungen? Schauen wir uns einige Beispiele an.

Beispiele für Add-ons

Nachdem wir Kubernetes installiert haben, werden wir vielleicht überrascht sein, dass die für die Interaktion von Pods sowohl innerhalb eines Knotens als auch zwischen Knoten so notwendige Vernetzung nicht von alleine funktioniert. Der Kubernetes-Kernel garantiert nicht die notwendigen Verbindungen, sondern bestimmt das Netzwerk Интерфейс (CNI) für Add-ons von Drittanbietern. Wir müssen eines dieser Add-ons installieren, das für die Netzwerkkonfiguration verantwortlich ist.

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Ein naheliegendes Beispiel sind Datenspeicherlösungen (lokale Festplatte, Netzwerkblockgerät, Ceph...). Anfangs waren sie im Kern, aber mit dem Aufkommen CSI Die Situation ändert sich in etwas Ähnliches wie bereits beschrieben: Die Schnittstelle befindet sich in Kubernetes und ihre Implementierung erfolgt in Modulen von Drittanbietern.

Weitere Beispiele sind:

  • Eintritt-Controller (Siehe ihre Rezension in unser aktueller Artikel).
  • Zertifizierungsmanager:

    Kubernetes erweitern und ergänzen (Rezension und Videobericht)

  • Betreiber ist eine ganze Klasse von Add-ons (einschließlich des erwähnten Zertifikatsmanagers), sie definieren Grundelemente und Controller. Die Logik ihrer Arbeit wird nur durch unsere Vorstellungskraft begrenzt und ermöglicht es uns, vorgefertigte Infrastrukturkomponenten (z. B. ein DBMS) in Grundelemente umzuwandeln, mit denen viel einfacher zu arbeiten ist (als mit einer Reihe von Containern und ihren Einstellungen). Es wurde eine Vielzahl von Operatoren geschrieben – auch wenn viele davon noch nicht produktionsreif sind, ist es nur eine Frage der Zeit:

    Kubernetes erweitern und ergänzen (Rezension und Videobericht)

  • Metriken – eine weitere Darstellung, wie Kubernetes die Schnittstelle (Metrics API) von der Implementierung (Add-Ons von Drittanbietern wie Prometheus-Adapter, Datadog-Cluster-Agent usw.) trennte.
  • für Überwachung und Statistik, wo in der Praxis nicht nur benötigt werden Prometheus und Grafana, aber auch Kube-State-Metriken, Node-Exporter usw.

Und dies ist keine vollständige Liste der Ergänzungen... Beispielsweise installieren wir derzeit bei der Firma Flant 29 Ergänzungen (alle davon erstellen insgesamt 249 Kubernetes-Objekte). Einfach ausgedrückt: Wir können das Leben eines Clusters ohne Ergänzungen nicht sehen.

Automatisierung

Bediener sind darauf ausgelegt, Routinevorgänge, denen wir jeden Tag begegnen, zu automatisieren. Hier sind Beispiele aus der Praxis, für die das Schreiben eines Operators eine hervorragende Lösung wäre:

  1. Es gibt eine private (d. h. eine Anmeldung erfordernde) Registrierung mit Bildern für die Anwendung. Es wird davon ausgegangen, dass jedem Pod ein spezielles Geheimnis zugewiesen wird, das die Authentifizierung in der Registrierung ermöglicht. Unsere Aufgabe besteht darin, sicherzustellen, dass dieses Geheimnis im Namespace gefunden wird, damit Pods Bilder herunterladen können. Es kann viele Anwendungen geben (von denen jede ein Geheimnis benötigt), und es ist sinnvoll, die Geheimnisse selbst regelmäßig zu aktualisieren, damit die Möglichkeit entfällt, Geheimnisse manuell anzulegen. Hier kommt der Operator zur Rettung: Wir erstellen einen Controller, der auf das Erscheinen des Namespace wartet und basierend auf diesem Ereignis dem Namespace ein Geheimnis hinzufügt.
  2. Der Zugriff von Pods auf das Internet ist standardmäßig verboten. Aber manchmal kann es erforderlich sein: Es ist logisch, dass der Zugriffsberechtigungsmechanismus einfach funktioniert, ohne dass besondere Fähigkeiten erforderlich sind, beispielsweise durch das Vorhandensein einer bestimmten Bezeichnung im Namespace. Wie kann uns der Betreiber hier helfen? Es wird ein Controller erstellt, der darauf wartet, dass die Bezeichnung im Namespace erscheint, und die entsprechende Richtlinie für den Internetzugriff hinzufügt.
  3. Eine ähnliche Situation: Angenommen, wir müssten etwas hinzufügen verderben, wenn es eine ähnliche Bezeichnung hat (mit einer Art Präfix). Die Aktionen mit dem Betreiber sind offensichtlich...

In jedem Cluster müssen Routineaufgaben gelöst werden, und richtig Dies kann mithilfe von Operatoren erfolgen.

Als wir alle beschriebenen Geschichten zusammenfassten, kamen wir zu dem Schluss Für komfortables Arbeiten in Kubernetes benötigen Sie: A) Add-ons installieren, B) Operatoren entwickeln (zur Lösung alltäglicher Verwaltungsaufgaben).

Wie schreibe ich eine Erklärung für Kubernetes?

Im Allgemeinen ist das Schema einfach:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

... aber dann stellt sich heraus:

  • Die Kubernetes-API ist eine eher nicht triviale Sache, deren Beherrschung viel Zeit in Anspruch nimmt;
  • Programmieren ist auch nicht jedermanns Sache (die Go-Sprache wurde als bevorzugte Sprache ausgewählt, da es dafür ein spezielles Framework gibt - Betreiber-SDK);
  • Ähnlich verhält es sich mit dem Framework selbst.

Unterm Strich: einen Controller schreiben (Betreiber) muss erhebliche Ressourcen ausgeben Material studieren. Dies wäre für „große“ Betreiber gerechtfertigt – beispielsweise für das MySQL-DBMS. Aber wenn wir uns an die oben beschriebenen Beispiele erinnern (Enthüllung von Geheimnissen, Zugriff auf Pods im Internet...), die wir auch richtig machen wollen, dann werden wir verstehen, dass der aufgewendete Aufwand das Ergebnis, das wir jetzt brauchen, überwiegen wird:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Im Allgemeinen entsteht ein Dilemma: Geben Sie viele Ressourcen aus und finden Sie das richtige Werkzeug zum Verfassen von Stellungnahmen, oder machen Sie es auf die altmodische Art und Weise (aber schnell). Um dieses Problem zu lösen – einen Kompromiss zwischen diesen Extremen zu finden – haben wir unser eigenes Projekt erstellt: Shell-Operator (siehe auch seine aktuelle Ankündigung auf der Nabe).

Shell-Betreiber

Wie funktioniert er? Der Cluster verfügt über einen Pod, der eine Go-Binärdatei mit einem Shell-Operator enthält. Neben ihm steht eine Reihe von Haken (Weitere Details dazu – siehe unten). Der Shell-Operator selbst abonniert bestimmte Entwicklungen in der Kubernetes-API, bei deren Auftreten die entsprechenden Hooks gestartet werden.

Woher weiß der Shell-Operator, welche Hooks er bei welchen Ereignissen aufrufen muss? Diese Informationen werden von den Hooks selbst an den Shell-Operator übermittelt, und zwar auf sehr einfache Weise.

Ein Hook ist ein Bash-Skript oder eine andere ausführbare Datei, die ein einzelnes Argument akzeptiert --config und antwortet mit JSON. Letzterer bestimmt, welche Objekte für ihn von Interesse sind und auf welche Ereignisse (für diese Objekte) reagiert werden soll:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Ich werde die Implementierung eines unserer Beispiele auf dem Shell-Operator veranschaulichen – das Zerlegen von Geheimnissen für den Zugriff auf eine private Registrierung mit Anwendungsbildern. Es besteht aus zwei Stufen.

Übung: 1. Schreiben Sie einen Hook

Zunächst werden wir den Haken verarbeiten --config, was darauf hinweist, dass wir an Namespaces und insbesondere am Zeitpunkt ihrer Erstellung interessiert sind:

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

Wie würde die Logik aussehen? Auch ganz einfach:

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

Der erste Schritt besteht darin, herauszufinden, welcher Namespace erstellt wurde, und der zweite darin, ihn mit zu erstellen kubectl Geheimnis für diesen Namespace.

Übung: 2. Das Bild zusammensetzen

Jetzt muss nur noch der erstellte Hook an den Shell-Operator übergeben werden – wie geht das? Der Shell-Operator selbst liegt als Docker-Image vor, daher besteht unsere Aufgabe darin, den Hook zu einem speziellen Verzeichnis in diesem Image hinzuzufügen:

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

Jetzt müssen Sie es nur noch zusammenbauen und schieben:

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

Der letzte Schliff besteht darin, das Image im Cluster bereitzustellen. Dazu schreiben wir Einsatz:

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. Angabe des neu erstellten Bildes;
  2. Dies ist eine Systemkomponente, die (mindestens) Rechte benötigt, um Ereignisse in Kubernetes zu abonnieren und Geheimnisse zu Namespaces zuzuweisen. Daher erstellen wir ein ServiceAccount (und einen Satz Regeln) für den Hook.

Ergebnis – wir haben unser Problem gelöst zu Verwandten für Kubernetes auf eine Weise, die einen Operator zum Zerlegen von Geheimnissen erstellt.

Weitere Shell-Operator-Funktionen

Um die Objekte des von Ihnen gewählten Typs einzuschränken, mit denen der Hook funktioniert, Sie können gefiltert werden, Auswahl nach bestimmten Etiketten (oder Verwendung matchExpressions):

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

Bereitgestellt Deduplizierungsmechanismus, mit dem Sie mithilfe eines JQ-Filters große JSON-Objekte in kleine umwandeln können, wobei nur die Parameter übrig bleiben, die wir auf Änderungen überwachen möchten.

Wenn ein Hook aufgerufen wird, übergibt ihn der Shell-Operator Objektdaten, das für jeden Bedarf verwendet werden kann.

Die Ereignisse, die Hooks auslösen, sind nicht auf Kubernetes-Ereignisse beschränkt: Der Shell-Operator bietet Unterstützung dafür Calling-Hooks nach Zeit (ähnlich Crontab in einem herkömmlichen Planer) sowie ein besonderes Ereignis onStartup. Alle diese Ereignisse können kombiniert und demselben Hook zugewiesen werden.

Und zwei weitere Features des Shell-Operators:

  1. Es funktioniert asynchron. Da ein Kubernetes-Ereignis (z. B. die Erstellung eines Objekts) empfangen wurde, könnten andere Ereignisse (z. B. das Löschen desselben Objekts) im Cluster aufgetreten sein, und Hooks müssen dies berücksichtigen. Wenn der Hook mit einem Fehler ausgeführt wurde, ist dies standardmäßig der Fall abrufen bis zum erfolgreichen Abschluss (dieses Verhalten kann geändert werden).
  2. Es exportiert Metriken für Prometheus, mit dem Sie nachvollziehen können, ob der Shell-Operator funktioniert, die Anzahl der Fehler für jeden Hook und die aktuelle Warteschlangengröße ermitteln.

Um diesen Teil des Berichts zusammenzufassen:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Add-ons installieren

Für ein komfortables Arbeiten mit Kubernetes wurde auch die Notwendigkeit der Installation von Add-Ons erwähnt. Ich erzähle Ihnen davon am Beispiel des Weges unseres Unternehmens, wie wir es jetzt tun.

Wir begannen mit Kubernetes mit mehreren Clustern zu arbeiten, die einzige Ergänzung war Ingress. Es musste in jedem Cluster anders installiert werden, und wir haben mehrere YAML-Konfigurationen für verschiedene Umgebungen erstellt: Bare Metal, AWS ...

Je mehr Cluster es gab, desto mehr Konfigurationen gab es. Darüber hinaus haben wir diese Konfigurationen selbst verbessert, wodurch sie recht heterogen geworden sind:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Um alles in Ordnung zu bringen, begannen wir mit einem Skript (install-ingress.sh), das als Argument den Typ des Clusters nahm, auf dem wir bereitstellen werden, generierte die erforderliche YAML-Konfiguration und rollte sie auf Kubernetes aus.

Kurz gesagt war unser weiterer Weg und die damit verbundene Überlegung wie folgt:

  • Um mit YAML-Konfigurationen zu arbeiten, ist eine Template-Engine erforderlich (in den ersten Phasen ist dies einfach sed);
  • Mit der Zunahme der Clusteranzahl entstand die Notwendigkeit einer automatischen Aktualisierung (die früheste Lösung bestand darin, das Skript in Git abzulegen, es mit cron zu aktualisieren und auszuführen);
  • Für Prometheus war ein ähnliches Skript erforderlich (install-prometheus.sh), zeichnet sich jedoch dadurch aus, dass es viel mehr Eingabedaten sowie deren Speicherung (im positiven Sinne – zentralisiert und in einem Cluster) erfordert und einige Daten (Passwörter) automatisch generiert werden könnten:

    Kubernetes erweitern und ergänzen (Rezension und Videobericht)

  • Das Risiko, dass bei einer wachsenden Anzahl von Clustern ein Fehler auftritt, nahm ständig zu, sodass wir erkannten, dass die Installer (d. h. zwei Skripte: für Ingress und Prometheus) Staging war erforderlich (mehrere Zweige in Git, mehrere Crons, um sie in den entsprechenden Stable- oder Test-Clustern zu aktualisieren);
  • с kubectl apply Es ist schwierig geworden, damit zu arbeiten, da es nicht deklarativ ist und nur Objekte erstellen, aber keine Entscheidungen über deren Status treffen oder sie löschen kann.
  • Uns fehlten einige Funktionen, die wir zu diesem Zeitpunkt noch gar nicht implementiert hatten:
    • volle Kontrolle über das Ergebnis von Cluster-Updates,
    • automatische Ermittlung einiger Parameter (Eingabe für Installationsskripte) basierend auf Daten, die vom Cluster abgerufen werden können (Discovery),
    • seine logische Entwicklung in Form kontinuierlicher Entdeckung.

All diese gesammelten Erfahrungen haben wir im Rahmen unseres anderen Projekts umgesetzt - Addon-Operator.

Addon-Betreiber

Es basiert auf dem bereits erwähnten Shell-Operator. Das ganze System sieht so aus:

Den Shell-Operator-Hooks wird Folgendes hinzugefügt:

  • Wertespeicher,
  • Helmkarte,
  • Komponente, die überwacht den Wertespeicher und – im Falle von Änderungen – fordert Helm auf, das Diagramm erneut zu rollen.

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

So können wir auf ein Ereignis in Kubernetes reagieren, einen Hook starten und von diesem Hook aus Änderungen am Speicher vornehmen, woraufhin das Diagramm erneut heruntergeladen wird. Im resultierenden Diagramm trennen wir den Satz von Hooks und das Diagramm in eine Komponente, die wir aufrufen Modul:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Es kann viele Module geben, und zu ihnen fügen wir globale Hooks, einen globalen Wertespeicher und eine Komponente hinzu, die diesen globalen Speicher überwacht.

Wenn nun in Kubernetes etwas passiert, können wir mithilfe eines globalen Hooks darauf reagieren und etwas im globalen Store ändern. Diese Änderung wird bemerkt und führt dazu, dass alle Module im Cluster ausgerollt werden:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Dieses Schema erfüllt alle oben genannten Anforderungen für die Installation von Add-ons:

  • Helm ist für die Vorlage und Deklarativität verantwortlich.
  • Das Problem der automatischen Aktualisierung wurde mithilfe eines globalen Hooks gelöst, der nach einem Zeitplan zur Registrierung geht und, wenn er dort ein neues Systemabbild sieht, es ausrollt (d. h. „selbst“).
  • Das Speichern von Einstellungen im Cluster wird mit implementiert Konfigurationskarte, das die Primärdaten für die Speicher enthält (beim Start werden sie in die Speicher geladen).
  • Probleme bei der Passwortgenerierung, -erkennung und der kontinuierlichen Erkennung wurden mithilfe von Hooks gelöst.
  • Das Staging wird mithilfe von Tags erreicht, die Docker standardmäßig unterstützt.
  • Das Ergebnis wird anhand von Metriken überwacht, anhand derer wir den Status verstehen können.

Dieses gesamte System ist in Go in Form einer einzigen Binärdatei implementiert, die Addon-Operator genannt wird. Dadurch sieht das Diagramm einfacher aus:

Kubernetes erweitern und ergänzen (Rezension und Videobericht)

Die Hauptkomponente in diesem Diagramm ist eine Reihe von Modulen (unten grau hervorgehoben). Jetzt können wir mit ein wenig Aufwand ein Modul für das benötigte Add-on schreiben und sicherstellen, dass es in jedem Cluster installiert wird, aktualisiert wird und auf die Ereignisse reagiert, die es im Cluster benötigt.

„Flant“ verwendet Addon-Operator auf über 70 Kubernetes-Clustern. Aktueller Status - Alpha-Version. Jetzt bereiten wir die Dokumentation für die Veröffentlichung der Beta vor, allerdings vorerst im Repository Beispiele vorhanden, auf deren Grundlage Sie Ihr eigenes Add-on erstellen können.

Wo bekomme ich die Module für Addon-Operator? Die Veröffentlichung unserer Bibliothek ist für uns der nächste Schritt; wir planen, dies im Sommer zu tun.

Videos und Folien

Video vom Auftritt (~50 Minuten):

Präsentation des Berichts:

PS

Weitere Berichte auf unserem Blog:

Folgende Publikationen könnten Sie auch interessieren:

Source: habr.com

Kommentar hinzufügen