In unserem Blog gab es bereits Artikel über die
Fähigkeiten von Operatoren in Kubernetes und darüber
, wie Sie
selbst einen einfachen Operator schreiben können . Dieses Mal möchten wir Sie auf unsere Open Source-Lösung aufmerksam machen, die die Erstellung von Operatoren auf ein super einfaches Niveau bringt - machen Sie sich mit dem
Shell-Operator vertraut!
Warum?
Die Idee eines Shell-Operators ist ganz einfach: Abonnieren Sie Ereignisse von Kubernetes-Objekten, und starten Sie beim Empfang dieser Ereignisse ein externes Programm, das Informationen zum Ereignis enthält:

Die Notwendigkeit dafür entstand bei uns, als während des Betriebs von Clustern kleine Aufgaben auftauchten, die wir wirklich auf die richtige Weise automatisieren wollten. Alle diese kleinen Aufgaben wurden mit Hilfe einfacher Bash-Skripte gelöst, obwohl Operatoren, wie Sie wissen, besser in Golang geschrieben sind. Offensichtlich wäre eine Investition in eine umfassende Betreiberentwicklung für jede dieser kleinen Aufgaben ineffizient.
Bediener in 15 Minuten
Schauen wir uns ein Beispiel an, was in einem Kubernetes-Cluster automatisiert werden kann und wie der Shell-Operator hilft. Ein Beispiel wäre das Folgende: Duplizieren eines Geheimnisses für den Zugriff auf die Docker-Registrierung.
Pods, die Bilder aus der privaten Registrierung verwenden, müssen in ihrem Manifest einen Link zu einem Geheimnis mit Daten enthalten, um auf die Registrierung zugreifen zu können. Dieses Geheimnis muss in jedem Namespace erstellt werden, bevor Pods erstellt werden. Es ist durchaus möglich, dies manuell zu tun, aber wenn wir dynamische Umgebungen einrichten, gibt es viel Namespace für eine Anwendung. Und wenn die Anwendungen auch nicht 2-3 sind ... wird die Anzahl der Geheimnisse sehr groß. Und noch etwas zu Geheimnissen: Ich möchte den Schlüssel ändern, um von Zeit zu Zeit auf die Registrierung zuzugreifen. Infolgedessen sind
manuelle Vorgänge als Lösung
völlig ineffektiv - Sie müssen die Erstellung und Aktualisierung von Geheimnissen automatisieren.
Einfache Automatisierung
Wir werden ein Shell-Skript schreiben, das alle N Sekunden ausgeführt wird und den Namespace auf ein Geheimnis überprüft. Wenn es kein Geheimnis gibt, wird es erstellt. Der Vorteil dieser Lösung ist, dass sie wie ein Shell-Skript in Cron aussieht - ein klassischer und verständlicher Ansatz. Der Nachteil ist, dass in der Zeit zwischen den Starts ein neuer Namespace erstellt werden kann, der einige Zeit ohne Geheimnis bleibt, was zu Fehlern beim Starten von Pods führt.
Automatisierung mit Shell-Operator
Damit unser Skript ordnungsgemäß funktioniert, muss der klassische Cron-Start durch den Start ersetzt werden, wenn das Namespace-Ereignis hinzugefügt wird. In diesem Fall können Sie ein Geheimnis erstellen, bevor Sie es verwenden. Mal sehen, wie dies mit dem Shell-Operator implementiert wird.
Lassen Sie uns zuerst das Skript analysieren. Skripte in Bezug auf den Shell-Operator werden als Hooks bezeichnet. Jeder Hook beim Start mit dem Flag
--config
den Shell-Operator über seine Bindungen, d. H. durch welche Ereignisse muss es gestartet werden. In unserem Fall verwenden wir
onKubernetesEvent
:
Es wird hier beschrieben, dass wir an Ereignissen zum Hinzufügen (
add
) von Objekten vom Typ
namespace
interessiert sind.
Jetzt müssen Sie den Code hinzufügen, der ausgeführt wird, wenn das Ereignis eintritt:
Großartig! Das Ergebnis ist ein kleines, schönes Skript. Um es wiederzubeleben, bleiben zwei Schritte: Das Image vorbereiten und im Cluster ausführen.
Ein Bild mit einem Haken vorbereiten
Wenn Sie sich das Skript ansehen, können Sie sehen, dass die
jq
kubectl
und
jq
. Dies bedeutet, dass das Image Folgendes enthalten muss: unseren Hook, einen Shell-Operator, der Ereignisse überwacht und den Hook startet, sowie die vom Hook verwendeten Befehle (kubectl und jq).
Hub.docker.com hat bereits ein fertiges Image, in dem Shell-Operator, Kubectl und JQ gepackt sind. Es bleibt, den Hook mit einer einfachen
Dockerfile
:
$ cat Dockerfile FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9 ADD namespace-hook.sh /hooks $ docker build -t registry.example.com/my-operator:v1 . $ docker push registry.example.com/my-operator:v1
Clusterstart
Schauen wir uns noch einmal den Hook an und schreiben Sie diesmal auf, welche Aktionen und mit welchen Objekten er im Cluster ausgeführt wird:
- Abonniert Namespace-Ereignisse
- Erstellt ein Geheimnis in anderen Namespaces als dem, in dem es ausgeführt wird.
Es stellt sich heraus, dass der Pod, in dem unser Image gestartet wird, über Berechtigungen für diese Aktionen verfügen muss. Dies kann durch Erstellen eines eigenen ServiceAccount erfolgen. Die Berechtigung muss in Form von ClusterRole und ClusterRoleBinding erfolgen, da Wir interessieren uns für Objekte aus dem gesamten Cluster.
Die endgültige Beschreibung in YAML lautet ungefähr so:
--- apiVersion: v1 kind: ServiceAccount metadata: name: monitor-namespaces-acc --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: monitor-namespaces rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "create", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: monitor-namespaces roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: monitor-namespaces subjects: - kind: ServiceAccount name: monitor-namespaces-acc namespace: example-monitor-namespaces
Sie können das zusammengestellte Image in Form einer einfachen Bereitstellung ausführen:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: my-operator spec: template: spec: containers: - name: my-operator image: registry.example.com/my-operator:v1 serviceAccountName: monitor-namespaces-acc
Der Einfachheit halber wird ein separater Namespace erstellt, in dem der Shell-Operator gestartet und die erstellten Manifeste angewendet werden:
$ kubectl create ns example-monitor-namespaces $ kubectl -n example-monitor-namespaces apply -f rbac.yaml $ kubectl -n example-monitor-namespaces apply -f deployment.yaml
Das ist alles: Der Shell-Operator startet, abonniert Namespace-Erstellungsereignisse und startet den Hook bei Bedarf.

So wurde aus einem
einfachen Shell-Skript ein echter Operator für Kubernetes, der als integraler Bestandteil des Clusters fungiert. Und das alles - ohne den komplizierten Prozess der Entwicklung von Betreibern auf Golang:

Filtern
Das Verfolgen von Objekten ist gut, es ist jedoch häufig erforderlich, auf
Änderungen an einigen Eigenschaften eines Objekts zu reagieren, z. B. auf eine Änderung der Anzahl der Replikate in einer Bereitstellung oder auf eine Änderung der Beschriftungen eines Objekts.
Wenn ein Ereignis eintrifft, empfängt der Shell-Operator das JSON-Manifest des Objekts. Sie können die für uns interessanten Eigenschaften in diesem JSON auswählen und den Hook
nur ausführen, wenn sie sich ändern. Dazu wird das Feld
jqFilter
, in dem Sie den jq-Ausdruck angeben müssen, der auf das JSON-Manifest angewendet wird.
Um beispielsweise auf Änderungen an Beschriftungen für Bereitstellungsobjekte zu reagieren, müssen Sie das
labels
aus dem
metadata
filtern. Die Konfiguration sieht folgendermaßen aus:
cat <<EOF { "onKubernetesEvent": [ { "kind": "deployment", "event":["update"], "jqFilter": ".metadata.labels" } ]} EOF
Dieser Ausdruck in jqFilter verwandelt das lange JSON-Manifest von Deployment in kurzes JSON mit Bezeichnungen:

Der Shell-Operator löst nur dann einen Hook aus, wenn sich dieser kurze JSON ändert und Änderungen an anderen Eigenschaften ignoriert werden.
Hook-Startkontext
In der Hook-Konfiguration können Sie verschiedene Optionen für Ereignisse angeben, z. B. zwei Optionen für Ereignisse aus Kubernetes und zwei Zeitpläne:
{"onKubernetesEvent":[ {"name":"OnCreatePod", "kind": "pod", "event":["add"] }, {"name":"OnModifiedNamespace", "kind": "namespace", "event":["update"], "jqFilter": ".metadata.labels" } ], "schedule": [ { "name":"every 10 min", "crontab":"0 */10 * * * *" }, {"name":"on Mondays at 12:10", "crontab": "0 10 12 * * 1" ]}
Ein kleiner Exkurs: Ja, der Shell-Operator unterstützt das Ausführen von Skripten im Crontab-Stil . Weitere Informationen finden Sie in der Dokumentation .Um zu unterscheiden, warum der Hook gestartet wurde, erstellt der Shell-Operator eine temporäre Datei und übergibt den Pfad dazu an den
BINDING_CONTEXT_TYPE
in der Variablen
BINDING_CONTEXT_TYPE
. Die Datei enthält eine JSON-Beschreibung des Grundes, aus dem der Hook gestartet wurde. Beispielsweise beginnt alle 10 Minuten ein Hook mit dem folgenden Inhalt:
[{ "binding": "every 10 min"}]
... und am Montag geht es damit los:
[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]
Für
onKubernetesEvent
wird seitdem mehr JSON
onKubernetesEvent
es enthält eine Beschreibung des Objekts:
[ { "binding": "onCreatePod", "resourceEvent": "add", "resourceKind": "pod", "resourceName": "foo", "resourceNamespace": "bar" } ]
Der Inhalt der Felder kann anhand ihrer Namen verstanden und ausführlicher in der
Dokumentation gelesen werden. Ein Beispiel für das Abrufen des Ressourcennamens aus dem Feld
resourceName
mithilfe von jq wurde bereits in einem Hook gezeigt, der Geheimnisse repliziert:
jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH
Ebenso können Sie die restlichen Felder erhalten.
Was weiter?
Im Projekt-Repository im
Verzeichnis / examples gibt es Beispiele für Hooks, die im Cluster ausgeführt werden können. Wenn Sie Ihre Hooks schreiben, können Sie sie als Grundlage nehmen.
Das Sammeln von Metriken mit Prometheus wird unterstützt. Die verfügbaren Metriken werden im Abschnitt
METRICS beschrieben .
Wie Sie sich vorstellen können, ist der Shell-Operator in Go geschrieben und unter der Open Source-Lizenz (Apache 2.0) verteilt. Wir sind dankbar für jede Hilfe bei der Entwicklung des
Projekts auf GitHub : Sternchen, Probleme und Pull-Anfragen.
Wir öffnen den Schleier der Geheimhaltung und informieren darüber, dass der Shell-Operator ein
kleiner Teil unseres Systems ist, der die im Kubernetes-Cluster installierten Add-Ons auf dem neuesten Stand halten und verschiedene automatische Aktionen ausführen kann. Wir
haben am Montag auf der HighLoad ++ 2019 in St. Petersburg ausführlicher über dieses System
gesprochen - das Video und das Transkript dieses Berichts werden in Kürze veröffentlicht.
Wir haben vor, den Rest dieses Systems zu öffnen: Addon-Operator und unsere Sammlung von Hooks und Modulen. Der Addon-Operator ist übrigens bereits
auf GitHub verfügbar , aber die Dokumentation dazu ist noch in Arbeit. Die Veröffentlichung der Modulsammlung ist für den Sommer geplant.
Bleib dran!
PS
Lesen Sie auch in unserem Blog: