
Am 8. April auf der Konferenz , 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, (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:

Im Kern von Kubernetes wird eine grundlegende Set an Primitiven definiert â zur Gruppierung von Containern, Verkehrsmanagement usw. DarĂŒber haben wir in .
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 () fĂŒr Drittanbieter-Plugins. Wir mĂŒssen eines dieser Plugins installieren, das fĂŒr die Netzwerkkonfiguration verantwortlich sein wird.

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 Ă€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 ).
- :

- â 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:

- 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 , 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:
- 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.
- 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.
- Ăhnliche Situation: Angenommen, wir mĂŒssen einem Knoten ein bestimmtes hinzufĂŒ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:

⊠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 â );
- 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:

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: (siehe auch seine 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:

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 /hooksJetzt bleibt nur noch, ihn zu erstellen und hochzuladen:
$ docker build -t registry.example.com/my-operator:v1 .
$ docker push registry.example.com/my-operator:v1Der 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 # 2Dabei sind zwei Punkte zu beachten:
- die Angabe des gerade erstellten Images;
- 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:
- 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).
- 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:

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:

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:
- 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 applyEs 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
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.

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:

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:

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:

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 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 , 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):

PrÀsentation des Vortrags:
P.S.
Weitere VortrÀge in unserem Blog:
- «»; (Dmitry Stolyarov; 8. November 2018 auf HighLoad++);
- «»; (Dmitry Stolyarov; 28. Mai 2018 auf der RootConf);
- «»; (Dmitry Stolyarov; 7. November 2017 auf HighLoad++);
- «»; (Dmitry Stolyarov; 6. Juni 2017 auf RootConf).
Vielleicht interessieren Sie auch die folgenden Publikationen:
- «».
Quelle: habr.com



